Skip to content

feat: Replace custom config system with Zod schemas#10231

Open
dblythy wants to merge 9 commits intoparse-community:alphafrom
dblythy:feature/simplifiy-config
Open

feat: Replace custom config system with Zod schemas#10231
dblythy wants to merge 9 commits intoparse-community:alphafrom
dblythy:feature/simplifiy-config

Conversation

@dblythy
Copy link
Member

@dblythy dblythy commented Mar 17, 2026

Summary

Replaces Parse Server's custom configuration system (Flow types + Babel code generator + custom parsers + monkey-patched Commander.js) with Zod schemas as the single source of truth for option definitions, types, defaults, env var mappings, and validation rules.

  • Zod schemas define all 100+ config options across 14 schema files with built-in type validation, defaults, and constraints (.min(), .refine(), .superRefine())
  • Unified validation between CLI and API startup paths — same Zod schema validates in both contexts
  • Nested env var support — e.g. PARSE_SERVER_SCHEMA_STRICT=true maps to { schema: { strict: true } } (closes Parse Server option cannot be set via environment for interfaces #7151)
  • Schema-driven CLI — Commander options auto-generated from Zod schema metadata, replacing the monkey-patched Commander
  • Validator classes following SOLID — SchemaValidator (Zod types/defaults), ControllerValidator (runtime dependency checks), composable via ConfigValidationPipeline
  • Dynamic key supportmasterKey and publicServerURL marked as dynamic in schema metadata, driving Config.loadKeys() automatically
  • Option groups — logical grouping (Core, Security, Database, etc.) for documentation and CLI help
  • Startup applicability — options like cluster marked applicableTo: ['cli'] with warnings when used in wrong context

Deleted (net -920 lines)

  • src/Options/index.js (Flow types) — replaced by Zod type inference
  • src/Options/Definitions.js (auto-generated) — replaced by Zod schemas
  • src/Options/parsers.js — replaced by Zod coercion
  • resources/buildConfigDefinitions.js (Babel plugin) — no longer needed
  • src/cli/utils/commander.js (monkey-patched Commander) — replaced by schema-driven runner
  • 24 dead validate* methods from Config.js (900 → 227 lines)

Closes: #9554

Test plan

  • All 117 new tests pass (npx jest spec/Options/)
  • ParseServer constructor smoke test with defaults, validation errors, dynamic masterKey
  • Env var loading with nested options (PARSE_SERVER_SCHEMA_STRICT, PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_TTL)
  • Config file loading with priority merging (file < env < CLI)
  • Cross-field validation (masterKey !== readOnlyMasterKey, IP validation, limit constraints)
  • Backwards compatibility for existing env var names, config file format, CLI flags

Summary by CodeRabbit

  • Refactor

    • Centralized configuration validation to a single schema-driven flow; consolidated defaults/back-compat handling and runtime validators.
  • New Features

    • Zod-based option schemas, metadata, and loaders (file, env, CLI) with type coercion, grouping, sensitive flags, and dynamic keys.
    • New CLI runner that validates and merges config sources (file < env < CLI) and redacts sensitive startup output.
  • Chores

    • Removed legacy definition-generation and its CI check; added Zod dependency.
  • Tests

    • Expanded comprehensive test suites for schemas, loaders, merging, CLI integration; trimmed legacy parser/definition tests.

Replace the custom Flow types, Babel code generator, and monkey-patched
Commander.js configuration system with Zod schemas as the single source
of truth for option definitions, types, defaults, env var mappings, and
validation rules.

Closes: parse-community#9554
@parse-github-assistant
Copy link

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Mar 17, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the legacy Definitions/parsers generator and CI check with a Zod-driven, TypeScript configuration system: adds schemas, schema-utils, loaders, validators, a Zod CLI runner, a docs generator, many tests, and removes the generated src/Options/Definitions.js, legacy parsers/generator, and the CI definitions check.

Changes

Cohort / File(s) Summary
CI & Generation
/.github/workflows/ci.yml, ci/definitionsCheck.js, resources/buildConfigDefinitions.js
Removed the "Check Definitions" CI job and deleted the old Definitions generator and its CI verifier.
Removed Legacy Definitions & Parsers
src/Options/Definitions.js, src/Options/docs.js, src/Options/parsers.js, spec/buildConfigDefinitions.spec.js, spec/parsers.spec.js
Deleted generated Definitions, legacy JSDoc/docs, parser utilities and their tests.
Zod Schemas & Type re-exports
src/Options/schemas/*, src/Options/schemas/index.ts, src/Options/index.ts
Added modular Zod schemas for options and centralized type re-exports to replace the old Definitions surface.
Schema Utilities & Docs Generator
src/Options/schemaUtils.ts, resources/generateDocs.js, src/defaults.js
Introduced option metadata, env mapping, coercion, defaults extraction, dynamic-key detection, legacy-definition translator, and a docs generator; defaults now derived from schemas.
Loaders & Merge
src/Options/loaders/cliLoader.ts, src/Options/loaders/envLoader.ts, src/Options/loaders/fileLoader.ts, src/Options/loaders/mergeConfig.ts
New loaders: register/extract CLI options from schemas, load nested env vars, read config files (single/apps), and deep-merge sources with CLI > env > file precedence.
Validators & Pipeline
src/Options/validators/*, src/Options/validateConfig.ts
Added ConfigValidator interface, SchemaValidator (Zod), ControllerValidator (cross-field checks), ConfigValidationPipeline, and validateConfig wired to ParseServerOptionsSchema.
CLI Runner & CLI changes
src/cli/utils/runner-zod.ts, removed src/cli/utils/runner.js, removed src/cli/utils/commander.js, src/cli/parse-server.js, src/cli/parse-live-query-server.js, src/cli/definitions/*
Added a Zod-based CLI runner that registers schema options, merges file/env/CLI; removed old definitions-based runner and commander augmentation; CLI entrypoints switched to schema-based flows.
Core config & runtime integration
src/Config.js, src/ParseServer.ts, src/middlewares.js, src/defaults.js
Rewired validation to use schema-driven validators, compute dynamic async keys from schemas, apply a special-defaults pass, and translate some legacy definitions at runtime.
New Tests & Test updates
spec/** (many new files), removed legacy spec files
Added comprehensive tests for schema utils, loaders, merge, runner-zod, validators; updated tests to derive defaults from schemas and removed tests tied to deleted parsers/generator.
Package
package.json
Added dependency zod; removed scripts ci:definitionsCheck and definitions; updated docs script to run resources/generateDocs.js before JSDoc.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(220,230,240,0.5)
    participant CLI as CLI Process
    participant File as File Loader
    participant Env as Env Loader
    participant CLIReg as CLI Registrar
    participant Merge as Merge Config
    participant SchemaVal as Schema Validator
    participant Controller as Controller Validator
    participant Start as Start Callback
    end

    CLI->>File: loadFromFile(configPath)
    File-->>CLI: fileConfig
    CLI->>Env: loadFromEnv(schema, env)
    Env-->>CLI: envConfig
    CLI->>CLIReg: registerSchemaOptions(program, schema)
    CLIReg-->>CLI: cliOptions (explicit)
    CLI->>Merge: mergeConfigs(fileConfig, envConfig, cliOptions)
    Merge-->>CLI: mergedConfig
    CLI->>SchemaVal: validateConfig(mergedConfig)
    SchemaVal-->>CLI: validatedConfig (defaults applied)
    CLI->>Controller: controllerValidator.validate(validatedConfig)
    Controller-->>CLI: ✓ or throw
    CLI->>Start: start(program, validatedConfig, log)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • mtrezza
  • Moumouls
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Replace custom config system with Zod schemas' is clear, concise, and accurately summarizes the primary change in the PR: replacing the legacy custom config system with Zod schemas.
Description check ✅ Passed The PR description is comprehensive and follows the template structure. It includes a detailed summary of changes, the approach, and a test plan with relevant checkboxes covering unit tests, smoke tests, env var loading, config file loading, cross-field validation, and backwards compatibility.
Linked Issues check ✅ Passed The PR directly addresses both linked issues: #7151 (nested env var support via schema-driven approach) and #9554 (replace custom config logic with Zod schemas as a library-based solution meeting all requirements: validation, env vars, config files, dynamic values, TS support).
Out of Scope Changes check ✅ Passed All changes are in-scope with the linked issues and PR objectives. The refactoring removes legacy custom config infrastructure, introduces Zod schemas, updates validators, and migrates CLI/API flows to use the new system, all directly supporting the migration away from custom config logic.
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

- Fix spec files to import from lib/ instead of src/ (Jasmine can't require .ts)
- Fix z.custom<any>() to z.custom<any>(() => true) (Zod v4 requires check fn)
- Fix z.record(z.any()) to z.record(z.string(), z.any()) (Zod v4 requires key type)
- Fix unwrapToObject/unwrapType to cast Zod v4 internal types
- Fix deprecated z.ZodIssueCode.custom to string literal 'custom'
- Fix databaseOptions = {} TS error with explicit any cast
- Change nested schema .default({}) to .optional() for Zod v4 compat
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Config.js (1)

66-73: ⚠️ Potential issue | 🟠 Major

Use this.applicationId instead of this.appId in the cache operations on lines 66 and 72.

The loadKeys() method uses this.appId to retrieve and update the cache, but appId is not explicitly initialized in the Config instance like applicationId is (set on line 36). While appId may be present in the cached data retrieved from AppCache.get(applicationId), relying on it creates an implicit dependency. Since applicationId is guaranteed to be set and equals appId throughout the codebase (verified in ParseServer.ts where newVal.appId = Parse.applicationId), using this.applicationId would be more robust and consistent with the explicit initialization pattern on line 36.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Config.js` around lines 66 - 73, The cache operations in loadKeys() use
this.appId but the Config instance explicitly initializes applicationId; replace
this.appId with this.applicationId in the AppCache.get(...) and
AppCache.put(...) calls so the cache lookup and update use the guaranteed
property. Keep the surrounding logic (creating updatedConfig, copying asyncKeys
into updatedConfig) unchanged and ensure you update both occurrences of
this.appId referenced in the block where cachedConfig is read and written.
🧹 Nitpick comments (8)
spec/Options/loaders/fileLoader.spec.js (1)

18-35: Add negative tests for invalid apps shapes.

Current coverage skips apps: [] and apps: {} cases, which are important for the loader’s format validation guarantees.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/Options/loaders/fileLoader.spec.js` around lines 18 - 35, Add two
negative tests for loadFromFile to cover invalid apps shapes: create temp
fixtures like in the existing multiple-apps test for apps: [] and apps: {}
(e.g., write JSON with "apps": [] and another with "apps": {}), call
loadFromFile against each and assert it throws the same validation error (e.g.,
expect(() => loadFromFile(...)).toThrow('Multiple apps are not supported') or
the appropriate message used by loadFromFile), and ensure you remove the temp
files in a finally block; reference the existing test structure around
loadFromFile and the temporary file creation/unlink pattern to implement these
cases.
src/Options/validators/ConfigValidator.ts (1)

12-12: Prefer unknown over any in the validator contract.

Using Record<string, unknown> preserves flexibility while preventing accidental unsafe access patterns in validator implementations.

♻️ Suggested refactor
-  validate(config: Record<string, any>): void;
+  validate(config: Record<string, unknown>): void;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/validators/ConfigValidator.ts` at line 12, Update the
ConfigValidator.validate signature to accept Record<string, unknown> instead of
Record<string, any> to avoid unsafe access; change the interface/method
declaration in ConfigValidator (validate(config: Record<string, unknown>): void)
and then update any implementing classes or callers of ConfigValidator.validate
to perform proper type checks/type guards or narrow/cast values before accessing
properties (use typeof checks, Array.isArray, or custom type predicates) so
implementations such as classes implementing ConfigValidator still compile and
handle the unknown config safely.
src/Options/schemas/RateLimitOptions.ts (1)

38-41: Constrain zone to known values.

Using an enum here will catch typos early and prevent silent fallback behavior.

♻️ Suggested refactor
-    zone: option(z.string().default('ip'), {
+    zone: option(z.enum(['ip', 'user', 'session', 'global']).default('ip'), {
       env: 'PARSE_SERVER_RATE_LIMIT_ZONE',
       help: "Rate limit zone identifier for grouping requests (e.g. 'ip', 'user', 'session', or 'global').",
     }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/RateLimitOptions.ts` around lines 38 - 41, The 'zone'
option currently uses z.string().default('ip') which allows typos and silent
fallbacks; change the schema to use z.enum with the allowed values
['ip','user','session','global'] (e.g.
z.enum(['ip','user','session','global']).default('ip')) inside the option(...)
call for the zone property so validation enforces known values and keeps the
same default and env/help metadata.
src/Options/schemas/ParseServerOptions.ts (2)

459-474: Consider adding validation that maintenanceKey differs from readOnlyMasterKey.

The cross-field validation ensures masterKey differs from both readOnlyMasterKey and maintenanceKey, but there's no check that maintenanceKey and readOnlyMasterKey are also different. While less critical, using the same key for both could lead to confusion about which operations are maintenance vs. read-only.

Suggested additional validation
   if (data.masterKey && data.maintenanceKey && data.masterKey === data.maintenanceKey) {
     ctx.addIssue({
       code: z.ZodIssueCode.custom,
       message: 'masterKey and maintenanceKey should be different',
       path: ['maintenanceKey'],
     });
   }
+  if (data.maintenanceKey && data.readOnlyMasterKey && data.maintenanceKey === data.readOnlyMasterKey) {
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      message: 'maintenanceKey and readOnlyMasterKey should be different',
+      path: ['maintenanceKey'],
+    });
+  }
 }).loose();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 459 - 474, Add a
third cross-field check inside the existing .superRefine callback (the one that
receives (data, ctx)) to ensure maintenanceKey and readOnlyMasterKey are not
equal; if they are, call ctx.addIssue with code: z.ZodIssueCode.custom, a clear
message like 'maintenanceKey and readOnlyMasterKey should be different', and set
the path to identify the offending field (e.g., ['maintenanceKey']) so the
schema ParseServerOptions validation reports the conflict between maintenanceKey
and readOnlyMasterKey.

20-32: Move require('net') to module level for better performance.

The net module is imported inside the refine callback, causing it to be required on every validation. Move it to the top of the file to align with how it's imported elsewhere in the codebase (e.g., src/middlewares.js uses import { BlockList, isIPv4 } from 'net') and avoid repeated module initialization overhead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 20 - 32, The
ipArraySchema currently calls require('net') inside the z.refine callback which
causes repeated module loads; move the require/import to module scope (e.g.,
const net = require('net') or import { isIP } from 'net' at top of the file) and
update the refine callback in ipArraySchema to use that top-level net (or isIP)
reference instead of requiring inside the function so validation reuses the
single module instance.
src/cli/utils/runner-zod.ts (2)

90-100: Inline require('path') is inconsistent with module style.

Line 95 uses an inline require('path') while the file uses ES module imports elsewhere. Consider adding import path from 'path' at the top for consistency.

Suggested change

Add at top of file:

import path from 'path';

Then update line 95:

-      console.log(`Configuration loaded from ${require('path').resolve(program.args[0])}`);
+      console.log(`Configuration loaded from ${path.resolve(program.args[0])}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/utils/runner-zod.ts` around lines 90 - 100, The code uses an inline
CommonJS require('path') when logging the resolved config path; replace that
with the ES module import by adding "import path from 'path'" at the top of the
file and then use path.resolve(program.args[0]) in the config-load block where
fileOptions is set (the block that calls loadFromFile and logs configuration
loaded). Keep the rest of the logic (try/catch, process.exit) unchanged and
ensure imports follow the ES module style used elsewhere in the file.

11-32: Consider deriving sensitive keys from schema metadata.

The keysToRedact list is hardcoded, which may fall out of sync if new sensitive options are added to the schema. Consider deriving this list from schema metadata (e.g., a sensitive: true flag in OptionMeta) to ensure consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/utils/runner-zod.ts` around lines 11 - 32, The hardcoded keysToRedact
in logStartupOptions should be derived from the CLI options schema instead;
update logStartupOptions (or its caller) to obtain the schema's OptionMeta
entries, collect option names where OptionMeta.sensitive === true (falling back
to the existing keysToRedact array if schema metadata is unavailable), and use
that derived list to mask values before logging; reference the logStartupOptions
function and the OptionMeta sensitive flag when locating where to pull schema
metadata and build the redaction set.
src/Options/schemaUtils.ts (1)

172-187: Use Zod v4's recommended _zod.def API instead of deprecated _def.

The code accesses _def.options on ZodUnion (line 174), which relies on Zod's deprecated internal structure. Zod v4 recommends using _zod.def instead of _def. Since the file already handles Zod v3/v4 differences elsewhere (see line 210), apply the same pattern here for consistency and forward compatibility. Replace (innerType as z.ZodUnion<...>)._def.options with (innerType as z.ZodUnion<...>)._zod.def.options.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemaUtils.ts` around lines 172 - 187, The Zod union branch is
using the deprecated internal `_def.options` on innerType (a z.ZodUnion) —
update the access to use Zod v4's `_zod.def.options` while preserving
compatibility with older Zod by falling back to `_def.options`; specifically
modify the code that retrieves options from innerType (the line casting to
z.ZodUnion and reading ._def.options) to check for `_zod?.def?.options` first
and otherwise use `._def.options`, keeping the surrounding logic that calls
unwrapType, skips z.ZodFunction, and calls coerceValue unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 64: Replace the semver range for the zod dependency in package.json so
installs are reproducible: find the "zod" entry currently set to "^4.3.6" and
change it to an exact version string "4.3.6" (remove the leading caret) so other
dependencies remain exact-pinned like the rest of the file.

In `@spec/Options/loaders/envLoader.spec.js`:
- Line 109: The test is using the Zod v4 signature incorrectly: update the
`push` option schema so `z.record` receives both a key schema and a value
schema; specifically in the `push: option(z.record(z.unknown()).optional(), {`
expression replace the single-argument `z.record` with a two-argument call using
a key schema like `z.string()` and the existing value schema (e.g.,
`z.record(z.string(), z.unknown()).optional()`), keeping the surrounding
`option(...)` wrapper and test intent intact.

In `@src/Options/loaders/cliLoader.ts`:
- Around line 55-57: The isOptionalOrDefaulted function incorrectly treats
z.ZodNullable as optional; update the logic in isOptionalOrDefaulted to remove
the z.ZodNullable check so only truly-optional/defaulted types are treated as
optional (e.g., keep checks for z.ZodOptional and z.ZodDefault or other
default-handling Zod types used in the project), ensuring nullable fields are
not considered "omittable" when generating CLI flags.

In `@src/Options/loaders/envLoader.ts`:
- Around line 45-49: The traversal currently assumes intermediate values are
non-null plain objects and will overwrite or fail silently on nulls or
scalar/object collisions; update the loop that checks current[key] so that if
current[key] === undefined you set it to {}, but if current[key] === null ||
typeof current[key] !== 'object' || Array.isArray(current[key]) you raise/return
a clear error about a path collision (or otherwise abort), and only then assign
current = current[key] as Record<string, unknown>; this ensures you guard the
nested assignment for null and non-plain-object conflicts instead of silently
overwriting.

In `@src/Options/loaders/fileLoader.ts`:
- Around line 20-25: The code in fileLoader.ts assumes jsonConfig.apps is an
array and indexes into it, which can yield undefined if apps is missing, not an
array, or empty; update the branch that handles jsonConfig.apps to first
validate Array.isArray(jsonConfig.apps) and that jsonConfig.apps.length === 1
(or at least >0 depending on intended behavior), otherwise throw a clear error
(e.g., "apps must be a non-empty array" or "Multiple apps are not supported")
before assigning options = jsonConfig.apps[0]; this ensures options is defined
for downstream merges and avoids indexing into non-array values.

In `@src/Options/loaders/mergeConfig.ts`:
- Around line 34-35: The loop that iterates Object.keys(source) and accesses
source[key] (variables: key, source, sourceVal) must guard against
prototype-poisoning keys; add a check at the top of the loop to skip any keys
equal to "__proto__", "constructor", or "prototype" (or any other forbidden keys
your policy requires) and also ensure the property is an own property via
Object.prototype.hasOwnProperty.call(source, key) before reading sourceVal;
update the merging logic in mergeConfig (the function containing this loop) to
continue on forbidden or non-own keys so prototype manipulation cannot occur.
- Around line 42-49: The bug is that assigning a plain object by reference
(target[key] = sourceVal) allows later recursive calls to deepMerge to mutate
the original source object; update the else branch so that when
isPlainObject(sourceVal) and targetVal is not a plain object you assign a
deep/shallow clone instead of the original reference (e.g., use
structuredClone(sourceVal) or your project's deepClone utility) so subsequent
deepMerge(targetVal, sourceVal) paths won't mutate the original; ensure this
change touches the logic around deepMerge, isPlainObject, targetVal, sourceVal
and target[key].

In `@src/Options/schemas/AccountLockoutOptions.ts`:
- Around line 7-10: The duration numeric validation in AccountLockoutOptions.ts
is too strict (uses .min(1)), rejecting fractional minute values like 0.05;
update the validator on the duration field (the z.number() chain for duration)
to require values greater than 0 instead of at least 1 (use .gt(0, 'Account
lockout duration should be greater than 0') or equivalent) while keeping the
existing .max(...) and .optional() behavior so fractional durations are accepted
again.

In `@src/Options/schemas/LiveQueryOptions.ts`:
- Around line 8-9: Update the help text for the LiveQuery option named
classNames in LiveQueryOptions.ts to accurately describe behavior: state that
this setting controls which classes emit LiveQuery events server-side (i.e.,
which classes the LiveQuery server will publish events for), rather than
implying it limits which classes can accept subscriptions; locate the option
object containing the key/classNames and replace the misleading sentence with a
concise description that it governs server-side event emission for listed
classes.

In `@src/Options/schemas/PasswordPolicyOptions.ts`:
- Line 11: The error message for maxPasswordAge in PasswordPolicyOptions is
misleading because z.number().min(0) allows zero; update the validation message
on the z.number().min(0(...)) call (the maxPasswordAge schema) to say “must be a
non-negative number” (or similar) so it aligns with the actual non-negative
constraint without changing behavior.

In `@src/Options/schemas/RateLimitOptions.ts`:
- Around line 22-25: The numeric rate-limit fields requestCount and
requestTimeWindow accept negative and fractional values; update their Zod
schemas to enforce positive integers by replacing z.number().optional() with
z.number().int().min(1).optional() (apply to the requestCount and
requestTimeWindow option entries in RateLimitOptions.ts) so fractional and
non-positive values are rejected at validation time.
- Around line 26-29: The schema for requestMethods currently only accepts arrays
which breaks backward compatibility with configs that use regex strings; update
the RateLimitOptions schema so requestMethods uses a union type (e.g.,
z.union([z.array(z.string()), z.string()]).optional()) so both array-of-strings
and string/regex forms are accepted, keeping the existing behavior expected by
src/middlewares.js that handles either form; ensure the option key remains
requestMethods and the env/help metadata are unchanged.

In `@src/Options/schemaUtils.ts`:
- Around line 147-159: The CSV fallback in the Zod array coercion (the block
checking innerType instanceof z.ZodArray) can corrupt values containing commas;
change the behavior to require JSON array strings instead of falling back to
value.split(','). Specifically, in the branch where typeof value === 'string'
inside the innerType instanceof z.ZodArray check, remove the CSV split fallback
and instead only attempt JSON.parse and return the parsed array when
Array.isArray(parsed) is true; if parsing fails or parsed is not an array,
return the original value (or throw a descriptive error) so validation fails
upstream—this prevents silently mangling inputs like '["a,b","c"]'.

In `@src/Options/validateConfig.ts`:
- Around line 18-20: The validateConfig function currently does a shallow copy
via "{ ...options }" which will throw if options is undefined or null; update
validateConfig to guard options before spreading (e.g., default to an empty
object or explicitly throw a controlled error) so schemaValidator.validate
always receives a plain object; locate the validateConfig function and change
the initialization of config to use a safe default (or add an early
null/undefined check) and then call schemaValidator.validate(config).

In `@src/Options/validators/SchemaValidator.ts`:
- Around line 26-30: The current copy-back loop leaves pre-existing unknown keys
on config; instead clear config then populate it with the validated object to
preserve Zod's sanitization. Locate the block using result.data / validated and
config in SchemaValidator.ts (the const validated = result.data; for (const key
of Object.keys(validated)) ... loop) and replace that logic with: remove all
existing own properties of config, then assign all properties from validated
(e.g., delete existing keys or reset config's own properties and then
Object.assign(config, validated)) so only schema-approved keys remain.

---

Outside diff comments:
In `@src/Config.js`:
- Around line 66-73: The cache operations in loadKeys() use this.appId but the
Config instance explicitly initializes applicationId; replace this.appId with
this.applicationId in the AppCache.get(...) and AppCache.put(...) calls so the
cache lookup and update use the guaranteed property. Keep the surrounding logic
(creating updatedConfig, copying asyncKeys into updatedConfig) unchanged and
ensure you update both occurrences of this.appId referenced in the block where
cachedConfig is read and written.

---

Nitpick comments:
In `@spec/Options/loaders/fileLoader.spec.js`:
- Around line 18-35: Add two negative tests for loadFromFile to cover invalid
apps shapes: create temp fixtures like in the existing multiple-apps test for
apps: [] and apps: {} (e.g., write JSON with "apps": [] and another with "apps":
{}), call loadFromFile against each and assert it throws the same validation
error (e.g., expect(() => loadFromFile(...)).toThrow('Multiple apps are not
supported') or the appropriate message used by loadFromFile), and ensure you
remove the temp files in a finally block; reference the existing test structure
around loadFromFile and the temporary file creation/unlink pattern to implement
these cases.

In `@src/cli/utils/runner-zod.ts`:
- Around line 90-100: The code uses an inline CommonJS require('path') when
logging the resolved config path; replace that with the ES module import by
adding "import path from 'path'" at the top of the file and then use
path.resolve(program.args[0]) in the config-load block where fileOptions is set
(the block that calls loadFromFile and logs configuration loaded). Keep the rest
of the logic (try/catch, process.exit) unchanged and ensure imports follow the
ES module style used elsewhere in the file.
- Around line 11-32: The hardcoded keysToRedact in logStartupOptions should be
derived from the CLI options schema instead; update logStartupOptions (or its
caller) to obtain the schema's OptionMeta entries, collect option names where
OptionMeta.sensitive === true (falling back to the existing keysToRedact array
if schema metadata is unavailable), and use that derived list to mask values
before logging; reference the logStartupOptions function and the OptionMeta
sensitive flag when locating where to pull schema metadata and build the
redaction set.

In `@src/Options/schemas/ParseServerOptions.ts`:
- Around line 459-474: Add a third cross-field check inside the existing
.superRefine callback (the one that receives (data, ctx)) to ensure
maintenanceKey and readOnlyMasterKey are not equal; if they are, call
ctx.addIssue with code: z.ZodIssueCode.custom, a clear message like
'maintenanceKey and readOnlyMasterKey should be different', and set the path to
identify the offending field (e.g., ['maintenanceKey']) so the schema
ParseServerOptions validation reports the conflict between maintenanceKey and
readOnlyMasterKey.
- Around line 20-32: The ipArraySchema currently calls require('net') inside the
z.refine callback which causes repeated module loads; move the require/import to
module scope (e.g., const net = require('net') or import { isIP } from 'net' at
top of the file) and update the refine callback in ipArraySchema to use that
top-level net (or isIP) reference instead of requiring inside the function so
validation reuses the single module instance.

In `@src/Options/schemas/RateLimitOptions.ts`:
- Around line 38-41: The 'zone' option currently uses z.string().default('ip')
which allows typos and silent fallbacks; change the schema to use z.enum with
the allowed values ['ip','user','session','global'] (e.g.
z.enum(['ip','user','session','global']).default('ip')) inside the option(...)
call for the zone property so validation enforces known values and keeps the
same default and env/help metadata.

In `@src/Options/schemaUtils.ts`:
- Around line 172-187: The Zod union branch is using the deprecated internal
`_def.options` on innerType (a z.ZodUnion) — update the access to use Zod v4's
`_zod.def.options` while preserving compatibility with older Zod by falling back
to `_def.options`; specifically modify the code that retrieves options from
innerType (the line casting to z.ZodUnion and reading ._def.options) to check
for `_zod?.def?.options` first and otherwise use `._def.options`, keeping the
surrounding logic that calls unwrapType, skips z.ZodFunction, and calls
coerceValue unchanged.

In `@src/Options/validators/ConfigValidator.ts`:
- Line 12: Update the ConfigValidator.validate signature to accept
Record<string, unknown> instead of Record<string, any> to avoid unsafe access;
change the interface/method declaration in ConfigValidator (validate(config:
Record<string, unknown>): void) and then update any implementing classes or
callers of ConfigValidator.validate to perform proper type checks/type guards or
narrow/cast values before accessing properties (use typeof checks,
Array.isArray, or custom type predicates) so implementations such as classes
implementing ConfigValidator still compile and handle the unknown config safely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b801dc1-9d69-421c-9586-a42cd5831417

📥 Commits

Reviewing files that changed from the base of the PR and between 206e8b6 and e960091.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (62)
  • .github/workflows/ci.yml
  • ci/definitionsCheck.js
  • package.json
  • resources/buildConfigDefinitions.js
  • spec/AccountLockoutPolicy.spec.js
  • spec/CLI.spec.js
  • spec/Idempotency.spec.js
  • spec/Options/loaders/cliLoader.spec.js
  • spec/Options/loaders/envLoader.spec.js
  • spec/Options/loaders/fileLoader.spec.js
  • spec/Options/loaders/mergeConfig.spec.js
  • spec/Options/loaders/runner-zod.spec.js
  • spec/Options/loaders/schemaUtils.spec.js
  • spec/Options/schemaFeatures.spec.js
  • spec/Options/schemas/schemas.spec.js
  • spec/Options/validateConfig.spec.js
  • spec/PagesRouter.spec.js
  • spec/SecurityCheck.spec.js
  • spec/buildConfigDefinitions.spec.js
  • spec/index.spec.js
  • spec/parsers.spec.js
  • src/Config.js
  • src/Options/Definitions.js
  • src/Options/docs.js
  • src/Options/index.js
  • src/Options/index.ts
  • src/Options/loaders/cliLoader.ts
  • src/Options/loaders/envLoader.ts
  • src/Options/loaders/fileLoader.ts
  • src/Options/loaders/mergeConfig.ts
  • src/Options/parsers.js
  • src/Options/schemaUtils.ts
  • src/Options/schemas/AccountLockoutOptions.ts
  • src/Options/schemas/DatabaseOptions.ts
  • src/Options/schemas/FileUploadOptions.ts
  • src/Options/schemas/IdempotencyOptions.ts
  • src/Options/schemas/LiveQueryOptions.ts
  • src/Options/schemas/LogLevels.ts
  • src/Options/schemas/PagesOptions.ts
  • src/Options/schemas/ParseServerOptions.ts
  • src/Options/schemas/PasswordPolicyOptions.ts
  • src/Options/schemas/RateLimitOptions.ts
  • src/Options/schemas/RequestComplexityOptions.ts
  • src/Options/schemas/SchemaOptions.ts
  • src/Options/schemas/SecurityOptions.ts
  • src/Options/schemas/index.ts
  • src/Options/validateConfig.ts
  • src/Options/validators/ConfigValidationPipeline.ts
  • src/Options/validators/ConfigValidator.ts
  • src/Options/validators/ControllerValidator.ts
  • src/Options/validators/SchemaValidator.ts
  • src/Options/validators/index.ts
  • src/ParseServer.ts
  • src/cli/definitions/parse-live-query-server.js
  • src/cli/definitions/parse-server.js
  • src/cli/parse-live-query-server.js
  • src/cli/parse-server.js
  • src/cli/utils/commander.js
  • src/cli/utils/runner-zod.ts
  • src/cli/utils/runner.js
  • src/defaults.js
  • src/middlewares.js
💤 Files with no reviewable changes (14)
  • .github/workflows/ci.yml
  • src/cli/definitions/parse-server.js
  • ci/definitionsCheck.js
  • resources/buildConfigDefinitions.js
  • src/Options/parsers.js
  • spec/buildConfigDefinitions.spec.js
  • spec/parsers.spec.js
  • src/Options/index.js
  • src/Options/Definitions.js
  • src/cli/utils/runner.js
  • src/cli/definitions/parse-live-query-server.js
  • spec/CLI.spec.js
  • src/Options/docs.js
  • src/cli/utils/commander.js

Add resources/generateDocs.js that generates lib/Options/docs.js
(JSDoc comment blocks) from Zod schema metadata, replacing the old
auto-generated docs.js. Update npm run docs to call the generator
before jsdoc.
@dblythy dblythy force-pushed the feature/simplifiy-config branch from f78aa22 to 22d67c9 Compare March 17, 2026 11:40
dblythy added 2 commits March 17, 2026 22:42
- Fix option() WeakMap collision when shared schema instances are reused
  (e.g. adapterSchema) — use .describe() wrapper + unique ID per field
- Add docType metadata for precise JSDoc types (Adapter<AnalyticsAdapter>,
  String|String[], Any, etc.)
- Fix CSV array coercion regression in coerceValue()
- Doc generator now uses docType override when available
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/Options/schemaUtils.ts (1)

147-157: ⚠️ Potential issue | 🟡 Minor

CSV fallback for array coercion can corrupt values.

At Line 149-Line 157, splitting on commas after JSON parse failure can mangle data (especially values containing commas). Prefer strict JSON array parsing and fail fast otherwise.

Proposed fix
   if (innerType instanceof z.ZodArray) {
     if (typeof value === 'string') {
-      // Try JSON array first, fall back to CSV
+      // Require JSON array syntax to avoid silent value corruption
       try {
         const parsed = JSON.parse(value);
         if (Array.isArray(parsed)) return parsed;
-      } catch {
-        // Not JSON, treat as CSV
-      }
-      return value.split(',');
+      } catch {}
+      throw new Error(`Expected a JSON array, got "${value}"`);
     }
     return value;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemaUtils.ts` around lines 147 - 157, The CSV fallback in the
innerType instanceof z.ZodArray branch splits string values on commas
(value.split(',')) which can corrupt entries; remove the CSV fallback and
require strict JSON arrays instead: attempt JSON.parse(value) and if the result
is an array return it, otherwise throw a clear error (or return an explicit
failure) so coercion fails fast; update the branch handling innerType/ZodArray
to stop using value.split and propagate a validation error when parsing doesn't
yield an array.
🧹 Nitpick comments (6)
src/Options/schemas/ParseServerOptions.ts (2)

20-32: Inline require('net') inside schema refinement.

The net module is required inside the refine callback on every validation call. While Node.js caches require() results, this is an unusual pattern. Consider hoisting it to module scope.

♻️ Optional refactor to hoist require
+import * as net from 'net';
+
 /** Validates an array of IP addresses, supporting CIDR notation. */
 const ipArraySchema = (fieldName: string) =>
   z.array(z.string()).refine(
     ips => {
-      const net = require('net');
       return ips.every(ip => {
         const bare = ip.includes('/') ? ip.split('/')[0] : ip;
         return net.isIP(bare);
       });
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 20 - 32, The
ipArraySchema's refine callback currently calls require('net') on every
validation; hoist the net import to module scope (e.g., const net =
require('net') or import net from 'net') and remove the inline require so the
refine closure uses that top-level net reference; update the
ipArraySchema/refine code to reference the hoisted net variable (keep the same
validation logic and error message).

17-17: Adapter schema accepts anything via z.custom<any>(() => true).

This validation always returns true, effectively disabling type checking for adapter instances. While this is intentional to accommodate class instances and arbitrary configurations, consider adding a comment clarifying this design choice for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` at line 17, The adapterSchema
currently uses z.custom<any>(() => true) which intentionally accepts any value
(disabling validation) to allow class instances or arbitrary adapter configs;
update the declaration for adapterSchema to include an inline comment explaining
that z.custom is deliberately permissive to accommodate runtime class instances
and non-serializable configs (mentioning that stricter validation would reject
such adapters), and reference adapterSchema and the z.custom usage in the
comment so future maintainers understand this design choice.
spec/Options/schemaFeatures.spec.js (1)

168-176: Config.js integration test only verifies key presence.

This test verifies that getDynamicKeys returns the expected keys but doesn't actually test Config.transformConfiguration. Consider whether an integration test that exercises the actual Config.js behavior would provide more confidence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/Options/schemaFeatures.spec.js` around lines 168 - 176, Test currently
only asserts getDynamicKeys(ParseServerOptionsSchema) contains certain keys;
update the spec to exercise Config.transformConfiguration to validate end-to-end
behavior: import or require the Config module (Config.js) and call
Config.transformConfiguration with a sample configuration object containing
async/dynamic keys (e.g., publicServerURL and masterKey) plus any
metadata-driven values, then assert the output contains the expected transformed
results (or side-effects) rather than just key presence; keep assertions focused
on the actual transformation behavior and use
getDynamicKeys(ParseServerOptionsSchema) to drive which keys to include in the
sample input.
spec/Options/loaders/cliLoader.spec.js (1)

84-90: Boolean coercion test passes string 'true'.

The test passes --verbose 'true' as a string, expecting it to be coerced to boolean true. Verify the coercion handles this case and also handles flag-only usage (e.g., --verbose without a value).

Consider adding a test case for flag-only boolean usage:

it('handles boolean flags without values', () => {
  // Commander may pass true for flag-only --verbose
  registerSchemaOptions(program, schema);
  program.parse(['node', 'test', '--appId', 'x', '--verbose'], { from: 'user' });
  const result = extractCliOptions(program, schema);
  expect(result.verbose).toBe(true);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/Options/loaders/cliLoader.spec.js` around lines 84 - 90, Add a unit test
that verifies boolean flags without explicit values are handled as true:
duplicate the existing coercion test pattern but call
program.parse(['node','test','--appId','x','--verbose'], { from: 'user' }) after
registerSchemaOptions(program, schema) and assert extractCliOptions(program,
schema).verbose === true; this ensures registerSchemaOptions / extractCliOptions
correctly handle flag-only usage in addition to the existing string 'true'
coercion case.
src/ParseServer.ts (2)

74-80: Validated config values copied back to options object.

The pattern of copying validated values back preserves the original options reference, which is important since consumers may hold references to it. However, this approach mutates the input object.

Consider documenting this mutation behavior, as callers passing a config object may not expect it to be modified in place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ParseServer.ts` around lines 74 - 80, The code mutates the incoming
options object by copying validated values back into it (see validateConfig and
the Object.keys(validated).forEach block), which callers may not expect; add a
clear JSDoc or inline comment on the ParseServer constructor/function (or
wherever options is accepted) stating that the options object is modified
in-place, that validated values are written back to preserve the original
reference for consumers, and advise callers to pass a clone if they need to
retain the original unmodified object. Ensure the comment references
validateConfig and the in-place assignment behavior so future readers and
callers are aware.

618-625: Potential issue: accessing protectedFields['_User'] before ensuring it exists.

On line 622, the code accesses options.protectedFields['_User']['*'] but only creates options.protectedFields['_User'] as an empty array [] on line 619 if '_User' in options.protectedFields is false. When _User exists but '*' doesn't, accessing ['*'] returns undefined which is handled by the spread, but the assignment pattern could be clearer.

♻️ Clearer protected fields handling
     if (!('_User' in options.protectedFields)) {
-      options.protectedFields = Object.assign({ _User: [] }, options.protectedFields);
+      options.protectedFields = Object.assign({ _User: {} }, options.protectedFields);
     }

+    if (!options.protectedFields['_User']['*']) {
+      options.protectedFields['_User']['*'] = [];
+    }
+
     options.protectedFields['_User']['*'] = Array.from(
       new Set([...(options.protectedFields['_User']['*'] || []), ...userSensitiveFields])
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ParseServer.ts` around lines 618 - 625, The protectedFields handling
accesses options.protectedFields['_User']['*'] before guaranteeing _User is an
object with a '*' entry; change the default for options.protectedFields['_User']
from an empty array to an object and ensure '*' is initialized to an array
before merging with userSensitiveFields. Specifically, when setting
options.protectedFields (the existing block that checks '_User' in
options.protectedFields), assign options.protectedFields['_User'] =
options.protectedFields['_User'] || {} and ensure
options.protectedFields['_User']['*'] = Array.from(new
Set([...(options.protectedFields['_User']['*'] || []), ...userSensitiveFields]))
so the '*' property always exists as an array before merging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@resources/generateDocs.js`:
- Line 8: Remove the unused import getSchemaDefault from the top require (keep
getAllOptionMeta), remove the unused key parameter from any callbacks/handlers
(rename or delete the parameter named key where it is not used), and replace all
single-line if statements without braces with block-braced forms so they satisfy
eslint (e.g., convert if (cond) doSomething; to if (cond) { doSomething; }) —
apply these changes for the occurrences around the getAllOptionMeta usage and
the functions that accept key and the one-line ifs mentioned.
- Around line 5-6: The docs generator imports transpiled code from lib so
running it without rebuilding can produce stale docs; update the npm "docs"
script to run the build first (change to "npm run build && node
resources/generateDocs.js && jsdoc -c ./jsdoc-conf.json") or, if you prefer not
to change package.json, update the usage comment at the top of
resources/generateDocs.js to explicitly state that "npm run build" (or
equivalent) must be run before invoking the script so lib/ is up-to-date.

In `@src/Options/schemas/PagesOptions.ts`:
- Around line 6-8: The schema for customRoutes.handler is too permissive (uses
option(z.any(), ...)) allowing non-callables; update the PagesOptions schema so
the handler option uses a Zod function type (e.g., z.function(...).returns(...))
instead of z.any(), and remove the env mapping/ env:
'PARSE_SERVER_PAGES_ROUTE_HANDLER' because function values can’t be serialized
via env. Locate the handler declaration (customRoutes.handler / handler:
option(...)) and replace z.any() with an appropriate z.function() signature that
matches the expected Express handler shape, and drop the env entry from that
option’s config.

---

Duplicate comments:
In `@src/Options/schemaUtils.ts`:
- Around line 147-157: The CSV fallback in the innerType instanceof z.ZodArray
branch splits string values on commas (value.split(',')) which can corrupt
entries; remove the CSV fallback and require strict JSON arrays instead: attempt
JSON.parse(value) and if the result is an array return it, otherwise throw a
clear error (or return an explicit failure) so coercion fails fast; update the
branch handling innerType/ZodArray to stop using value.split and propagate a
validation error when parsing doesn't yield an array.

---

Nitpick comments:
In `@spec/Options/loaders/cliLoader.spec.js`:
- Around line 84-90: Add a unit test that verifies boolean flags without
explicit values are handled as true: duplicate the existing coercion test
pattern but call program.parse(['node','test','--appId','x','--verbose'], {
from: 'user' }) after registerSchemaOptions(program, schema) and assert
extractCliOptions(program, schema).verbose === true; this ensures
registerSchemaOptions / extractCliOptions correctly handle flag-only usage in
addition to the existing string 'true' coercion case.

In `@spec/Options/schemaFeatures.spec.js`:
- Around line 168-176: Test currently only asserts
getDynamicKeys(ParseServerOptionsSchema) contains certain keys; update the spec
to exercise Config.transformConfiguration to validate end-to-end behavior:
import or require the Config module (Config.js) and call
Config.transformConfiguration with a sample configuration object containing
async/dynamic keys (e.g., publicServerURL and masterKey) plus any
metadata-driven values, then assert the output contains the expected transformed
results (or side-effects) rather than just key presence; keep assertions focused
on the actual transformation behavior and use
getDynamicKeys(ParseServerOptionsSchema) to drive which keys to include in the
sample input.

In `@src/Options/schemas/ParseServerOptions.ts`:
- Around line 20-32: The ipArraySchema's refine callback currently calls
require('net') on every validation; hoist the net import to module scope (e.g.,
const net = require('net') or import net from 'net') and remove the inline
require so the refine closure uses that top-level net reference; update the
ipArraySchema/refine code to reference the hoisted net variable (keep the same
validation logic and error message).
- Line 17: The adapterSchema currently uses z.custom<any>(() => true) which
intentionally accepts any value (disabling validation) to allow class instances
or arbitrary adapter configs; update the declaration for adapterSchema to
include an inline comment explaining that z.custom is deliberately permissive to
accommodate runtime class instances and non-serializable configs (mentioning
that stricter validation would reject such adapters), and reference
adapterSchema and the z.custom usage in the comment so future maintainers
understand this design choice.

In `@src/ParseServer.ts`:
- Around line 74-80: The code mutates the incoming options object by copying
validated values back into it (see validateConfig and the
Object.keys(validated).forEach block), which callers may not expect; add a clear
JSDoc or inline comment on the ParseServer constructor/function (or wherever
options is accepted) stating that the options object is modified in-place, that
validated values are written back to preserve the original reference for
consumers, and advise callers to pass a clone if they need to retain the
original unmodified object. Ensure the comment references validateConfig and the
in-place assignment behavior so future readers and callers are aware.
- Around line 618-625: The protectedFields handling accesses
options.protectedFields['_User']['*'] before guaranteeing _User is an object
with a '*' entry; change the default for options.protectedFields['_User'] from
an empty array to an object and ensure '*' is initialized to an array before
merging with userSensitiveFields. Specifically, when setting
options.protectedFields (the existing block that checks '_User' in
options.protectedFields), assign options.protectedFields['_User'] =
options.protectedFields['_User'] || {} and ensure
options.protectedFields['_User']['*'] = Array.from(new
Set([...(options.protectedFields['_User']['*'] || []), ...userSensitiveFields]))
so the '*' property always exists as an array before merging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b798c309-70d4-4487-abce-c7acdf5de315

📥 Commits

Reviewing files that changed from the base of the PR and between e960091 and 22d67c9.

📒 Files selected for processing (16)
  • package.json
  • resources/generateDocs.js
  • spec/Options/loaders/cliLoader.spec.js
  • spec/Options/loaders/envLoader.spec.js
  • spec/Options/loaders/fileLoader.spec.js
  • spec/Options/loaders/mergeConfig.spec.js
  • spec/Options/loaders/runner-zod.spec.js
  • spec/Options/loaders/schemaUtils.spec.js
  • spec/Options/schemaFeatures.spec.js
  • spec/Options/schemas/schemas.spec.js
  • spec/Options/validateConfig.spec.js
  • src/Options/schemaUtils.ts
  • src/Options/schemas/DatabaseOptions.ts
  • src/Options/schemas/PagesOptions.ts
  • src/Options/schemas/ParseServerOptions.ts
  • src/ParseServer.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • spec/Options/schemas/schemas.spec.js
  • spec/Options/loaders/fileLoader.spec.js
  • spec/Options/loaders/mergeConfig.spec.js
  • spec/Options/loaders/envLoader.spec.js
  • package.json

dblythy added 2 commits March 17, 2026 23:00
- Replace z.any() with z.function() for PagesRoute handler schema
- Remove CSV split fallback in schemaUtils array coercion
- Add inline comment explaining permissive adapterSchema design
- Fix protectedFields _User initialization (object instead of array)
- Add JSDoc documenting in-place options mutation in ParseServer
- Add boolean flag and Config.transformConfiguration tests
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
resources/generateDocs.js (1)

8-8: ⚠️ Potential issue | 🟡 Minor

Fix ESLint failures: unused import and missing braces.

The getSchemaDefault import is unused and the key parameter on line 104 is never used. Additionally, multiple if statements lack braces (lines 44, 55-57, 74, 107-108, 110).

Proposed fix
-const { getAllOptionMeta, getSchemaDefault } = require('../lib/Options/schemaUtils');
+const { getAllOptionMeta } = require('../lib/Options/schemaUtils');
-function getTypeName(schema, key) {
+function getTypeName(schema) {

And wrap single-line if bodies in braces throughout the file (lines 44, 55-57, 74, 107-108, 110).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/generateDocs.js` at line 8, Remove the unused import
getSchemaDefault from the require statement and update the require to only
import getAllOptionMeta; remove the unused parameter key from the function that
declares it (keep function name or surrounding context the same) so there are no
unused parameters; and add braces around all single-line if bodies throughout
resources/generateDocs.js (wrap the bodies of the if statements that currently
omit braces) to satisfy ESLint rules and avoid dangling single-line
conditionals.
src/Options/schemaUtils.ts (1)

158-170: ⚠️ Potential issue | 🟡 Minor

CSV fallback for array coercion may corrupt values with embedded commas.

When JSON parsing fails, falling back to value.split(',') can incorrectly split values that legitimately contain commas (e.g., a malformed JSON string or an intentional comma in a single value). Consider requiring strict JSON format for arrays or documenting this limitation clearly.

Alternative: Require JSON format
   if (innerType instanceof z.ZodArray) {
     if (typeof value === 'string') {
       try {
         const parsed = JSON.parse(value);
         if (Array.isArray(parsed)) return parsed;
+        throw new Error('Parsed value is not an array');
       } catch {
-        // Not valid JSON
+        // For arrays, require valid JSON format to avoid comma-splitting issues
+        throw new Error(`Expected valid JSON array, got "${value}"`);
       }
-      // Fall back to CSV splitting
-      return value.split(',');
     }
     return value;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemaUtils.ts` around lines 158 - 170, The CSV fallback when
coercing arrays (inside the block checking innerType instanceof z.ZodArray) can
corrupt values with embedded commas; remove the fallback that returns
value.split(',') and require strict JSON arrays instead: when value is a string
attempt JSON.parse(value) and if parsing fails or the result is not an array
either throw or return the original value so Zod surfaces a validation error (do
not perform CSV splitting), updating the logic around JSON.parse and the branch
that currently returns value.split(',') to enforce strict JSON-only input.
🧹 Nitpick comments (7)
spec/Options/loaders/fileLoader.spec.js (2)

18-55: Use unique temp files/dirs per test to avoid fixture collisions.

These tests write fixed filenames into a shared directory. That can cause intermittent failures under retries/parallelism or leave stale fixtures after interrupted runs. Prefer per-test temp dirs/files (fs.mkdtempSync + os.tmpdir) and clean up with fs.rmSync(..., { recursive: true, force: true }).

♻️ Suggested refactor
+const fs = require('fs');
+const os = require('os');
 const path = require('path');
 const { loadFromFile } = require('../../../lib/Options/loaders/fileLoader');
@@
   it('throws for multiple apps', () => {
-    const multiAppPath = path.join(configDir, 'CLIConfigMultipleApps.json');
-    const fs = require('fs');
+    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fileLoader-'));
+    const multiAppPath = path.join(tmpDir, 'CLIConfigMultipleApps.json');
     const multiApp = { apps: [{ arg1: 'a' }, { arg1: 'b' }] };
     fs.writeFileSync(multiAppPath, JSON.stringify(multiApp));
     try {
       expect(() => loadFromFile(multiAppPath)).toThrow('Multiple apps are not supported');
     } finally {
-      fs.unlinkSync(multiAppPath);
+      fs.rmSync(tmpDir, { recursive: true, force: true });
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/Options/loaders/fileLoader.spec.js` around lines 18 - 55, Tests create
temp fixtures using fixed filenames in configDir which can collide; update each
test (e.g., the "throws for multiple apps", "throws for empty apps array", and
"throws for apps that is not an array" cases) to create a unique temp directory
with fs.mkdtempSync(path.join(os.tmpdir(), '...')) and write their JSON files
there, call loadFromFile with the temp file path, and always clean up the
directory with fs.rmSync(tempDir, { recursive: true, force: true }) in the
finally block; reference loadFromFile and the test names to locate the places to
change.

58-60: Make the relative-path test independent of repository CWD.

Line 59 hard-codes a root-relative string, which is brittle if tests are launched from a different working directory. Compute the relative path dynamically from process.cwd() to keep the same intent without CWD coupling.

♻️ Suggested refactor
   it('resolves relative paths', () => {
-    const result = loadFromFile('./spec/configs/CLIConfig.json');
+    const relativeConfigPath = path.relative(
+      process.cwd(),
+      path.join(configDir, 'CLIConfig.json')
+    );
+    const result = loadFromFile(relativeConfigPath);
     expect(result.arg1).toBe('my_app');
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/Options/loaders/fileLoader.spec.js` around lines 58 - 60, The test
'resolves relative paths' currently hard-codes './spec/configs/CLIConfig.json';
update it to compute the relative path from process.cwd() so it doesn't depend
on the test runner CWD: require('path') in the spec, compute const rel =
path.relative(process.cwd(), path.resolve(__dirname, '..', '..', 'configs',
'CLIConfig.json')) (ensure it begins with './' if needed), and call
loadFromFile(rel) in the it block; reference symbols: the test case "resolves
relative paths", the loadFromFile function, process.cwd(), and __dirname.
src/Options/schemas/ParseServerOptions.ts (1)

466-488: LGTM on cross-field validation.

The superRefine correctly enforces that masterKey, readOnlyMasterKey, and maintenanceKey are all distinct when provided. The .loose() at the end allows forward compatibility with additional options.

Minor note: Lines 469 and 476 use the string 'custom' while line 483 uses z.ZodIssueCode.custom — both are equivalent, but using the constant consistently would be cleaner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 466 - 488, Replace
the string literal 'custom' with the Zod constant z.ZodIssueCode.custom in the
superRefine calls to make issue codes consistent: update the two ctx.addIssue
invocations that currently pass code: 'custom' (the ones comparing masterKey vs
readOnlyMasterKey and masterKey vs maintenanceKey) to use z.ZodIssueCode.custom
so all three cross-field checks use the same constant; keep the same message and
path values and leave the other logic in superRefine unchanged.
src/cli/utils/runner-zod.ts (2)

16-21: Hardcoded redaction list may miss sensitive fields.

The keysToRedact array is hardcoded. Consider deriving this from schema metadata (e.g., a sensitive: true flag in OptionMeta) to ensure new sensitive options are automatically redacted.

Proposed approach
-  const keysToRedact = ['databaseAdapter', 'databaseURI', 'masterKey', 'maintenanceKey', 'push'];
+  // Derive from schema metadata or a centralized sensitive keys list
+  const keysToRedact = getSensitiveKeys(schema); // or import from schemaUtils

This would require adding a sensitive?: boolean field to OptionMeta and a helper function to extract sensitive keys.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/utils/runner-zod.ts` around lines 16 - 21, Replace the hardcoded
keysToRedact array with schema-driven extraction: add a sensitive?: boolean flag
to OptionMeta, implement a helper (e.g., getSensitiveOptionKeys(optionMetaMap))
that scans the option metadata and returns option names with sensitive=true, and
then use that helper inside the options redaction loop in runner-zod.ts instead
of the static keysToRedact; update any code that constructs OptionMeta to
include sensitive where appropriate and ensure the redaction logic still falls
back safely if metadata is missing.

93-101: Config file loading silently succeeds if file doesn't exist.

If loadFromFile throws because the file doesn't exist versus a parse error, the error message may not distinguish these cases. Consider checking file existence first for a clearer error message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/utils/runner-zod.ts` around lines 93 - 101, The current config
loading blindly calls loadFromFile and reports its error, which can mask a
missing file; before calling loadFromFile (in runner-zod.ts around the
program.args handling), check the file existence (e.g., fs.existsSync or
fs.promises.access on program.args[0]) and if missing, log a clear "config file
not found: <path>" error and exit; otherwise call loadFromFile and on exceptions
differentiate parse/validation errors vs other runtime errors in the catch
(include the file path and the thrown error message) so the user sees whether
the failure was "file not found" or a parse/validation problem with
loadFromFile.
src/Options/loaders/mergeConfig.ts (1)

62-64: Consider handling null prototype objects.

isPlainObject uses Object.prototype.toString.call(value) which returns '[object Object]' for both regular objects and Object.create(null) objects. This is likely fine for config merging, but note that null-prototype objects will be treated as plain objects.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/loaders/mergeConfig.ts` around lines 62 - 64, isPlainObject
currently uses Object.prototype.toString which treats objects created with
Object.create(null) the same as normal objects; update isPlainObject to first
ensure value is non-null and typeof 'object', then check
Object.getPrototypeOf(value) and return true only if the prototype is exactly
Object.prototype or null (so both normal and null-prototype objects are
explicitly handled). Change the isPlainObject function to use these checks (use
Object.getPrototypeOf) to make the behavior explicit.
src/Options/schemaUtils.ts (1)

310-435: Option groups cover all schema keys but rely on manual maintenance.

The getOptionGroups() function currently includes all 102 schema keys with no drift detected. However, this approach requires manual synchronization whenever schema options are added, removed, or renamed. Consider adding a test to validate coverage or deriving groups from schema metadata to make this maintainable long-term.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemaUtils.ts` around lines 310 - 435, getOptionGroups()
currently lists schema keys manually, which can drift from the authoritative
schema; add a coverage test and/or derive groups from the schema metadata to
prevent manual desync. Implement a unit test (e.g., in the existing test suite)
that compares the union of all keys returned by getOptionGroups() to the
canonical schema key set (the schema definition object used elsewhere in this
module) and fails the build if any key is missing or extra; alternatively
refactor getOptionGroups() to generate groups programmatically from the schema
metadata (the schema definition object or exported keys list) so groups are
computed rather than hand-maintained, ensuring getOptionGroups(), the schema
definition, and the test reference the same source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@resources/generateDocs.js`:
- Line 133: The call to getTypeName in the assignment to type still passes two
arguments even though getTypeName's signature no longer accepts key; update the
call at the type assignment (const type = fieldMeta?.docType ||
getTypeName(shape[key], key)) to call getTypeName with only the shape element
(e.g., getTypeName(shape[key])) so it matches the new function signature while
keeping the fallback to fieldMeta?.docType; verify getTypeName usage elsewhere
(references to getTypeName) are similarly updated.

In `@src/Options/schemas/ParseServerOptions.ts`:
- Around line 267-269: The ParseServerOptions schema declares maintenanceKey as
required but runtime treats it as optional; update the schema in
ParseServerOptions.ts by making maintenanceKey optional (e.g., change the type
passed to option from z.string() to z.string().optional()) or provide a default
value so the option(...) call matches runtime behavior; ensure the unique symbol
maintenanceKey within the ParseServerOptions schema is adjusted so downstream
code that checks for its presence continues to work without validation failures.

In `@src/Options/schemaUtils.ts`:
- Around line 225-237: The getSchemaDefault function currently uses deprecated
_def and includes v3 typeName checks; update getSchemaDefault to read def from
schema.def (not schema._def), remove the legacy checks for 'ZodDefault',
'ZodOptional', and 'ZodNullable', and rely only on the v4 lowercase
discriminators ('default', 'optional', 'nullable') when inspecting def.type;
ensure you still handle defaultValue (calling if it's a function) and recurse
into def.innerType for optional/nullable cases using the same getSchemaDefault
helper.

---

Duplicate comments:
In `@resources/generateDocs.js`:
- Line 8: Remove the unused import getSchemaDefault from the require statement
and update the require to only import getAllOptionMeta; remove the unused
parameter key from the function that declares it (keep function name or
surrounding context the same) so there are no unused parameters; and add braces
around all single-line if bodies throughout resources/generateDocs.js (wrap the
bodies of the if statements that currently omit braces) to satisfy ESLint rules
and avoid dangling single-line conditionals.

In `@src/Options/schemaUtils.ts`:
- Around line 158-170: The CSV fallback when coercing arrays (inside the block
checking innerType instanceof z.ZodArray) can corrupt values with embedded
commas; remove the fallback that returns value.split(',') and require strict
JSON arrays instead: when value is a string attempt JSON.parse(value) and if
parsing fails or the result is not an array either throw or return the original
value so Zod surfaces a validation error (do not perform CSV splitting),
updating the logic around JSON.parse and the branch that currently returns
value.split(',') to enforce strict JSON-only input.

---

Nitpick comments:
In `@spec/Options/loaders/fileLoader.spec.js`:
- Around line 18-55: Tests create temp fixtures using fixed filenames in
configDir which can collide; update each test (e.g., the "throws for multiple
apps", "throws for empty apps array", and "throws for apps that is not an array"
cases) to create a unique temp directory with
fs.mkdtempSync(path.join(os.tmpdir(), '...')) and write their JSON files there,
call loadFromFile with the temp file path, and always clean up the directory
with fs.rmSync(tempDir, { recursive: true, force: true }) in the finally block;
reference loadFromFile and the test names to locate the places to change.
- Around line 58-60: The test 'resolves relative paths' currently hard-codes
'./spec/configs/CLIConfig.json'; update it to compute the relative path from
process.cwd() so it doesn't depend on the test runner CWD: require('path') in
the spec, compute const rel = path.relative(process.cwd(),
path.resolve(__dirname, '..', '..', 'configs', 'CLIConfig.json')) (ensure it
begins with './' if needed), and call loadFromFile(rel) in the it block;
reference symbols: the test case "resolves relative paths", the loadFromFile
function, process.cwd(), and __dirname.

In `@src/cli/utils/runner-zod.ts`:
- Around line 16-21: Replace the hardcoded keysToRedact array with schema-driven
extraction: add a sensitive?: boolean flag to OptionMeta, implement a helper
(e.g., getSensitiveOptionKeys(optionMetaMap)) that scans the option metadata and
returns option names with sensitive=true, and then use that helper inside the
options redaction loop in runner-zod.ts instead of the static keysToRedact;
update any code that constructs OptionMeta to include sensitive where
appropriate and ensure the redaction logic still falls back safely if metadata
is missing.
- Around line 93-101: The current config loading blindly calls loadFromFile and
reports its error, which can mask a missing file; before calling loadFromFile
(in runner-zod.ts around the program.args handling), check the file existence
(e.g., fs.existsSync or fs.promises.access on program.args[0]) and if missing,
log a clear "config file not found: <path>" error and exit; otherwise call
loadFromFile and on exceptions differentiate parse/validation errors vs other
runtime errors in the catch (include the file path and the thrown error message)
so the user sees whether the failure was "file not found" or a parse/validation
problem with loadFromFile.

In `@src/Options/loaders/mergeConfig.ts`:
- Around line 62-64: isPlainObject currently uses Object.prototype.toString
which treats objects created with Object.create(null) the same as normal
objects; update isPlainObject to first ensure value is non-null and typeof
'object', then check Object.getPrototypeOf(value) and return true only if the
prototype is exactly Object.prototype or null (so both normal and null-prototype
objects are explicitly handled). Change the isPlainObject function to use these
checks (use Object.getPrototypeOf) to make the behavior explicit.

In `@src/Options/schemas/ParseServerOptions.ts`:
- Around line 466-488: Replace the string literal 'custom' with the Zod constant
z.ZodIssueCode.custom in the superRefine calls to make issue codes consistent:
update the two ctx.addIssue invocations that currently pass code: 'custom' (the
ones comparing masterKey vs readOnlyMasterKey and masterKey vs maintenanceKey)
to use z.ZodIssueCode.custom so all three cross-field checks use the same
constant; keep the same message and path values and leave the other logic in
superRefine unchanged.

In `@src/Options/schemaUtils.ts`:
- Around line 310-435: getOptionGroups() currently lists schema keys manually,
which can drift from the authoritative schema; add a coverage test and/or derive
groups from the schema metadata to prevent manual desync. Implement a unit test
(e.g., in the existing test suite) that compares the union of all keys returned
by getOptionGroups() to the canonical schema key set (the schema definition
object used elsewhere in this module) and fails the build if any key is missing
or extra; alternatively refactor getOptionGroups() to generate groups
programmatically from the schema metadata (the schema definition object or
exported keys list) so groups are computed rather than hand-maintained, ensuring
getOptionGroups(), the schema definition, and the test reference the same source
of truth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6771aaa6-4ddd-48fb-ad56-0ae8174581e3

📥 Commits

Reviewing files that changed from the base of the PR and between 22d67c9 and 8bc7162.

📒 Files selected for processing (23)
  • package.json
  • resources/generateDocs.js
  • spec/Options/loaders/envLoader.spec.js
  • spec/Options/loaders/fileLoader.spec.js
  • spec/Options/validateConfig.spec.js
  • src/Config.js
  • src/Options/loaders/cliLoader.ts
  • src/Options/loaders/envLoader.ts
  • src/Options/loaders/fileLoader.ts
  • src/Options/loaders/mergeConfig.ts
  • src/Options/schemaUtils.ts
  • src/Options/schemas/AccountLockoutOptions.ts
  • src/Options/schemas/LiveQueryOptions.ts
  • src/Options/schemas/ParseServerOptions.ts
  • src/Options/schemas/PasswordPolicyOptions.ts
  • src/Options/schemas/RateLimitOptions.ts
  • src/Options/schemas/SchemaOptions.ts
  • src/Options/validateConfig.ts
  • src/Options/validators/ConfigValidationPipeline.ts
  • src/Options/validators/ConfigValidator.ts
  • src/Options/validators/ControllerValidator.ts
  • src/Options/validators/SchemaValidator.ts
  • src/cli/utils/runner-zod.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/Options/validators/ControllerValidator.ts
  • src/Options/schemas/AccountLockoutOptions.ts
  • src/Options/loaders/fileLoader.ts
  • src/Options/validators/ConfigValidationPipeline.ts
  • src/Options/loaders/envLoader.ts
  • src/Options/validateConfig.ts
  • src/Options/schemas/PasswordPolicyOptions.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
resources/generateDocs.js (1)

154-154: ⚠️ Potential issue | 🟡 Minor

Align getTypeName call with its current signature.

getTypeName now accepts one argument, but this call still passes key as a second argument.

🧹 Minimal cleanup
-    const type = fieldMeta?.docType || getTypeName(shape[key], key);
+    const type = fieldMeta?.docType || getTypeName(shape[key]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/generateDocs.js` at line 154, The call to getTypeName is passing a
now-removed second parameter; update the assignment (const type =
fieldMeta?.docType || getTypeName(shape[key], key)) to call getTypeName with its
single expected argument (use getTypeName(shape[key]) so it aligns with the
current function signature), keeping the fieldMeta?.docType fallback logic
intact and leaving references to shape and key unchanged elsewhere.
🧹 Nitpick comments (1)
src/ParseServer.ts (1)

47-50: Suggested PR title (Angular style): feat(options): replace legacy config system with Zod schema validation pipeline

This is clearer for changelog readers than a broad “simplify config” phrasing.

Based on learnings: For Parse Server PRs, always suggest an Angular-style PR title that creates a meaningful developer-facing changelog entry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ParseServer.ts` around lines 47 - 50, Update the PR title to the
suggested Angular-style heading: set the PR title to "feat(options): replace
legacy config system with Zod schema validation pipeline" so changelogs are
developer-facing and consistent for Parse Server PRs; no code changes
required—just rename the PR/title in your GitHub/Git client where the PR for the
changes touching validateConfig, ParseServerOptionsSchema,
warnInapplicableOptions, and related config code is created.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Options/schemaUtils.ts`:
- Around line 104-114: When building the environment mapping in buildEnvMap,
don't silently overwrite existing entries in envMap: check envMap.has(meta.env)
before envMap.set(meta.env, ...) and raise/throw a clear error (or aggregate and
throw after scan) naming the duplicate key and both conflicting field paths (use
currentPath and the existing value.path) so duplicates in meta.env are detected;
also apply the same duplicate-detection when merging nestedMap results from
unwrapToObject/zodField so nested schema env keys cannot collide with outer
ones.

In `@src/ParseServer.ts`:
- Around line 94-99: warnInapplicableOptions is currently called with a callback
that drops warnings if (logging as any).logger is undefined; update the callback
passed to warnInapplicableOptions (the one referencing logging and logger) so it
falls back to console.warn (or another always-available logger) when (logging as
any).logger is not initialized, ensuring messages from
warnInapplicableOptions(options, 'api', ParseServerOptionsSchema, ...) are never
lost.
- Around line 85-88: The current copy-back logic uses
Object.keys(validated).forEach(key => { options[key] = validated[key]; }) which
preserves the options object reference but leaves any stale keys on options that
are not present in validated; to fix, first remove all existing own properties
from the options object (e.g., iterate Object.keys(options) and delete each key)
and then copy validated entries back using the existing Object.keys(validated)
loop so the reference is preserved and no stale keys remain (refer to the
options and validated objects and the copy-back block in ParseServer.ts).

---

Duplicate comments:
In `@resources/generateDocs.js`:
- Line 154: The call to getTypeName is passing a now-removed second parameter;
update the assignment (const type = fieldMeta?.docType ||
getTypeName(shape[key], key)) to call getTypeName with its single expected
argument (use getTypeName(shape[key]) so it aligns with the current function
signature), keeping the fieldMeta?.docType fallback logic intact and leaving
references to shape and key unchanged elsewhere.

---

Nitpick comments:
In `@src/ParseServer.ts`:
- Around line 47-50: Update the PR title to the suggested Angular-style heading:
set the PR title to "feat(options): replace legacy config system with Zod schema
validation pipeline" so changelogs are developer-facing and consistent for Parse
Server PRs; no code changes required—just rename the PR/title in your GitHub/Git
client where the PR for the changes touching validateConfig,
ParseServerOptionsSchema, warnInapplicableOptions, and related config code is
created.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05b2348b-d827-43d1-a5ad-90613af63bac

📥 Commits

Reviewing files that changed from the base of the PR and between 8bc7162 and 2edf1ac.

📒 Files selected for processing (7)
  • resources/generateDocs.js
  • spec/Options/loaders/cliLoader.spec.js
  • spec/Options/schemaFeatures.spec.js
  • src/Options/schemaUtils.ts
  • src/Options/schemas/PagesOptions.ts
  • src/Options/schemas/ParseServerOptions.ts
  • src/ParseServer.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Options/schemas/PagesOptions.ts
  • spec/Options/loaders/cliLoader.spec.js
  • src/Options/schemas/ParseServerOptions.ts
  • spec/Options/schemaFeatures.spec.js

dblythy added 2 commits March 17, 2026 23:27
- Use schema.def (public API) instead of _zod?.def ?? _def in getSchemaDefault
- Fix CSV array coercion being dropped by restructuring control flow
- Replace structuredClone with recursive deepMerge clone for Jest compat
- Update test for maintenanceKey now being optional per schema change
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli/utils/runner-zod.ts`:
- Around line 13-20: logStartupOptions currently replaces the fallback redaction
set when schema metadata returns any sensitive keys; instead merge the
schema-derived keys with FALLBACK_KEYS_TO_REDACT so fallback secrets (e.g.,
databaseURI, maintenanceKey, push) are always redacted. In the logStartupOptions
function, call getSensitiveOptionKeys(schema) and combine its result with
FALLBACK_KEYS_TO_REDACT (deduplicating as needed) to produce keysToRedact, and
continue using that merged set when options.verbose is true.

In `@src/Options/schemas/ParseServerOptions.ts`:
- Around line 27-38: The ipArraySchema currently only validates the host portion
via isIP and misses malformed or out-of-range CIDR masks; update the refine
logic in ipArraySchema to, for each entry, if it contains a '/', ensure there is
exactly one '/' (reject entries like "10.0.0.1/24/extra"), split into host and
prefix, validate host with isIP, parse the prefix as an integer and check it is
within the correct range (0–32 for IPv4 hosts, 0–128 for IPv6 hosts), and reject
non-numeric or out-of-range prefixes so
masterKeyIps/maintenanceKeyIps/readOnlyMasterKeyIps cannot accept malformed
CIDRs.
- Around line 437-440: The schema's serverURL field must be optional so
validateConfig() won't reject a missing value before injectSpecialDefaults() can
set the fallback; update the serverURL option declaration to accept an optional
string (e.g., use z.string().optional() inside the option call for the serverURL
field) while keeping the same env/help metadata so injectSpecialDefaults() in
ParseServer.ts can later populate the default URL.
- Around line 17-24: The adapterSchema's current catch-all z.custom<any>(() =>
true) accepts primitives and arrays that loadAdapter can't handle; replace that
with a predicate that only allows non-null plain objects (excluding arrays) and
functions/classes: use z.custom<any>(val => (typeof val === 'object' && val !==
null && !Array.isArray(val)) || typeof val === 'function') so adapterSchema
continues to accept strings, config records, and runtime class/function
instances but rejects invalid primitive and array values.
- Around line 231-234: The Zod definition for graphQLSchema is too restrictive
(currently option(z.string().optional())) and must accept the same shapes
ParseServer supports (string path, a parsed schema object, or a factory function
that returns a schema) so validation and the generated types match runtime;
update the graphQLSchema option to a union that allows string | object |
function (e.g., option(z.union([z.string(), z.any(), z.function()]).optional(),
...)) so ParseServer.startApp and the ParseServer constructor can accept a
parsed schema or factory function and the emitted types reflect those shapes.
- Around line 101-104: The cloud option schema currently only allows strings but
ParseServer.start() accepts both string paths and functions; update the cloud
option in ParseServerOptions.ts (the cloud entry) to a union of
z.string().optional() and z.custom<Function>(v => typeof v === 'function')
(following the same pattern used by
masterKey/preventLoginWithUnverifiedEmail/sendUserEmailVerification) so the
schema accepts either a file path string or a function at runtime.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 087976d8-6543-4d12-999e-75125a357a24

📥 Commits

Reviewing files that changed from the base of the PR and between 2edf1ac and b4240e2.

📒 Files selected for processing (8)
  • resources/generateDocs.js
  • spec/Options/loaders/fileLoader.spec.js
  • spec/Options/schemaFeatures.spec.js
  • src/Options/loaders/mergeConfig.ts
  • src/Options/schemaUtils.ts
  • src/Options/schemas/ParseServerOptions.ts
  • src/ParseServer.ts
  • src/cli/utils/runner-zod.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • spec/Options/loaders/fileLoader.spec.js
  • resources/generateDocs.js
  • src/Options/loaders/mergeConfig.ts
  • src/Options/schemaUtils.ts

Comment on lines +13 to +20
const FALLBACK_KEYS_TO_REDACT = ['databaseAdapter', 'databaseURI', 'masterKey', 'maintenanceKey', 'push'];

function logStartupOptions(options: Record<string, any>, schema?: z.ZodObject<z.ZodRawShape>) {
if (!options.verbose) {
return;
}
const sensitiveKeys = schema ? getSensitiveOptionKeys(schema) : [];
const keysToRedact = sensitiveKeys.length > 0 ? sensitiveKeys : FALLBACK_KEYS_TO_REDACT;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the fallback redaction list even when schema metadata exists.

Line 20 drops the fallback list as soon as the schema reports any sensitive keys. The current schema does not mark databaseURI, maintenanceKey, or the push block as sensitive, so --verbose can print those secrets in cleartext.

🔐 Merge metadata-driven and fallback redaction keys
   const sensitiveKeys = schema ? getSensitiveOptionKeys(schema) : [];
-  const keysToRedact = sensitiveKeys.length > 0 ? sensitiveKeys : FALLBACK_KEYS_TO_REDACT;
+  const keysToRedact = [...new Set([...FALLBACK_KEYS_TO_REDACT, ...sensitiveKeys])];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/utils/runner-zod.ts` around lines 13 - 20, logStartupOptions
currently replaces the fallback redaction set when schema metadata returns any
sensitive keys; instead merge the schema-derived keys with
FALLBACK_KEYS_TO_REDACT so fallback secrets (e.g., databaseURI, maintenanceKey,
push) are always redacted. In the logStartupOptions function, call
getSensitiveOptionKeys(schema) and combine its result with
FALLBACK_KEYS_TO_REDACT (deduplicating as needed) to produce keysToRedact, and
continue using that merged set when options.verbose is true.

Comment on lines +17 to +24
/** Schema for adapter fields that accept a module path string, config object, or class instance. */
const adapterSchema = z.union([
z.string(),
z.record(z.string(), z.any()),
// Deliberately permissive: adapters may be runtime class instances or other
// non-serializable values that stricter Zod validators would reject.
z.custom<any>(() => true),
]).optional();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check the current schema file
head -50 src/Options/schemas/ParseServerOptions.ts

Repository: parse-community/parse-server

Length of output: 2430


🏁 Script executed:

# Find all adapter definitions in ParseServer.ts to understand runtime behavior
fd "ParseServer\.ts" --type f

Repository: parse-community/parse-server

Length of output: 90


🏁 Script executed:

# Check how adapters are instantiated at runtime
rg "adapterSchema|analyticsAdapter|databaseAdapter|filesAdapter" src/ -A 3 -B 3 --max-count 20

Repository: parse-community/parse-server

Length of output: 13536


🏁 Script executed:

# List available adapters to understand valid runtime shapes
fd -e js -e ts src/Adapters/ -type f | head -20

Repository: parse-community/parse-server

Length of output: 242


🏁 Script executed:

# Check a concrete adapter implementation
find src/Adapters -name "*.js" -o -name "*.ts" | head -3 | xargs -I {} sh -c 'echo "=== {} ===" && head -40 {}'

Repository: parse-community/parse-server

Length of output: 3695


🏁 Script executed:

# Check how loadAdapter is called in the codebase to understand the full flow
rg "loadAdapter\(" src/ -B 2 -A 2 --max-count 15

Repository: parse-community/parse-server

Length of output: 6167


🏁 Script executed:

# Verify if z.custom<any>(() => true) actually allows invalid values
cat > /tmp/test_zod.ts << 'EOF'
import { z } from 'zod';

const schema = z.custom<any>(() => true);

console.log('Testing schema validation:');
console.log('42:', schema.safeParse(42).success);
console.log('true:', schema.safeParse(true).success);
console.log('[]:', schema.safeParse([]).success);
console.log('"string":', schema.safeParse('string').success);
console.log('{}:', schema.safeParse({}).success);
console.log('function:', schema.safeParse(() => {}).success);
EOF
cd src && node -e "
const zod = require('zod');
const { z } = zod;

const schema = z.custom(() => true);

console.log('Testing schema validation:');
console.log('42:', schema.safeParse(42).success);
console.log('true:', schema.safeParse(true).success);
console.log('[]:', schema.safeParse([]).success);
console.log('\"string\":', schema.safeParse('string').success);
console.log('{}:', schema.safeParse({}).success);
console.log('function:', schema.safeParse(() => {}).success);
" 2>&1 || echo "Node test failed, checking npm modules"

Repository: parse-community/parse-server

Length of output: 949


🏁 Script executed:

# Check if there are any guards or validation in the controller that uses adapters
grep -A 20 "export function getAnalyticsController" src/Controllers/index.js

Repository: parse-community/parse-server

Length of output: 1013


🏁 Script executed:

# Check what happens when loadAdapter receives invalid values
sed -n '20,60p' src/Adapters/AdapterLoader.js

Repository: parse-community/parse-server

Length of output: 1041


🏁 Script executed:

# Verify the complete loadAdapter implementation
cat src/Adapters/AdapterLoader.js

Repository: parse-community/parse-server

Length of output: 1662


Narrow the adapter schema to reject invalid primitive and array values.

The z.custom<any>(() => true) catch-all currently accepts any value, including primitives and arrays that loadAdapter cannot handle. Replace with explicit type checks for the valid shapes adapters accept at runtime:

Proposed fix
-const adapterSchema = z.union([
-  z.string(),
-  z.record(z.string(), z.any()),
-  // Deliberately permissive: adapters may be runtime class instances or other
-  // non-serializable values that stricter Zod validators would reject.
-  z.custom<any>(() => true),
-]).optional();
+const adapterSchema = z
+  .custom<any>(
+    value =>
+      typeof value === 'string' ||
+      typeof value === 'function' ||
+      (value !== null && typeof value === 'object' && !Array.isArray(value)),
+    { message: 'Adapter must be a module path string, function, or object/class instance.' }
+  )
+  .optional();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 17 - 24, The
adapterSchema's current catch-all z.custom<any>(() => true) accepts primitives
and arrays that loadAdapter can't handle; replace that with a predicate that
only allows non-null plain objects (excluding arrays) and functions/classes: use
z.custom<any>(val => (typeof val === 'object' && val !== null &&
!Array.isArray(val)) || typeof val === 'function') so adapterSchema continues to
accept strings, config records, and runtime class/function instances but rejects
invalid primitive and array values.

Comment on lines +27 to +38
const ipArraySchema = (fieldName: string) =>
z.array(z.string()).refine(
ips => {
return ips.every(ip => {
const bare = ip.includes('/') ? ip.split('/')[0] : ip;
return isIP(bare);
});
},
{
message: `The option "${fieldName}" contains an invalid IP address. All entries must be valid IPv4 or IPv6 addresses, optionally with CIDR notation.`,
}
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate the CIDR prefix, not just the host portion.

Lines 31-32 accept malformed entries like 10.0.0.1/999 and 10.0.0.1/24/extra because only the substring before / is checked. That weakens the allowlist validation behind masterKeyIps, maintenanceKeyIps, and readOnlyMasterKeyIps.

🛡️ Reject malformed or out-of-range CIDR masks
 const ipArraySchema = (fieldName: string) =>
   z.array(z.string()).refine(
     ips => {
       return ips.every(ip => {
-        const bare = ip.includes('/') ? ip.split('/')[0] : ip;
-        return isIP(bare);
+        const parts = ip.split('/');
+        if (parts.length > 2) {
+          return false;
+        }
+        const [bare, prefix] = parts;
+        const version = isIP(bare);
+        if (!version) {
+          return false;
+        }
+        if (prefix == null) {
+          return true;
+        }
+        if (!/^\d+$/.test(prefix)) {
+          return false;
+        }
+        const maxPrefix = version === 4 ? 32 : 128;
+        const parsedPrefix = Number(prefix);
+        return parsedPrefix >= 0 && parsedPrefix <= maxPrefix;
       });
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ipArraySchema = (fieldName: string) =>
z.array(z.string()).refine(
ips => {
return ips.every(ip => {
const bare = ip.includes('/') ? ip.split('/')[0] : ip;
return isIP(bare);
});
},
{
message: `The option "${fieldName}" contains an invalid IP address. All entries must be valid IPv4 or IPv6 addresses, optionally with CIDR notation.`,
}
);
const ipArraySchema = (fieldName: string) =>
z.array(z.string()).refine(
ips => {
return ips.every(ip => {
const parts = ip.split('/');
if (parts.length > 2) {
return false;
}
const [bare, prefix] = parts;
const version = isIP(bare);
if (!version) {
return false;
}
if (prefix == null) {
return true;
}
if (!/^\d+$/.test(prefix)) {
return false;
}
const maxPrefix = version === 4 ? 32 : 128;
const parsedPrefix = Number(prefix);
return parsedPrefix >= 0 && parsedPrefix <= maxPrefix;
});
},
{
message: `The option "${fieldName}" contains an invalid IP address. All entries must be valid IPv4 or IPv6 addresses, optionally with CIDR notation.`,
}
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 27 - 38, The
ipArraySchema currently only validates the host portion via isIP and misses
malformed or out-of-range CIDR masks; update the refine logic in ipArraySchema
to, for each entry, if it contains a '/', ensure there is exactly one '/'
(reject entries like "10.0.0.1/24/extra"), split into host and prefix, validate
host with isIP, parse the prefix as an integer and check it is within the
correct range (0–32 for IPv4 hosts, 0–128 for IPv6 hosts), and reject
non-numeric or out-of-range prefixes so
masterKeyIps/maintenanceKeyIps/readOnlyMasterKeyIps cannot accept malformed
CIDRs.

Comment on lines +101 to +104
cloud: option(z.string().optional(), {
env: 'PARSE_SERVER_CLOUD',
help: 'Path to the cloud code file (e.g. ./cloud/main.js) containing triggers and functions.',
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check the schema definition
echo "=== ParseServerOptions.ts schema definition ==="
sed -n '95,115p' src/Options/schemas/ParseServerOptions.ts

# Check ParseServer.ts to see how cloud is handled
echo -e "\n=== ParseServer.ts cloud handling ==="
sed -n '165,190p' src/ParseServer.ts

Repository: parse-community/parse-server

Length of output: 2135


🏁 Script executed:

#!/bin/bash
# Search for tests that use cloud option with function values
echo "=== Searching for cloud option usage in tests ==="
rg "cloud:\s*\(" spec/ -A 2 -B 2 | head -50

# Also search for cloud option in general
echo -e "\n=== All cloud option references in tests ==="
rg "cloud:" spec/ | head -30

Repository: parse-community/parse-server

Length of output: 1972


🏁 Script executed:

#!/bin/bash
# Let's also check src/Options/index.js to see the cloud option definition there
echo "=== Cloud option in src/Options/index.js ==="
rg -A 5 "^\s*cloud:" src/Options/index.js | head -30

Repository: parse-community/parse-server

Length of output: 194


🏁 Script executed:

#!/bin/bash
# Search for how options are validated
echo "=== Schema validation mechanism ==="
rg "ParseServerOptions" src/ | grep -i "validat\|schema\|zod" | head -20

# Check how reconfigureServer handles options
echo -e "\n=== reconfigureServer implementation ==="
rg -A 10 "function reconfigureServer\|const reconfigureServer" spec/helper.js | head -40

Repository: parse-community/parse-server

Length of output: 2038


🏁 Script executed:

#!/bin/bash
# Let's check the ParseServerOptions type definition
echo "=== ParseServerOptions type definition ==="
fd ParseServerOptions src/ --exec cat {} \;

# Also search for where validation happens
echo -e "\n=== Validation/parsing logic ==="
rg "parse.*Options\|validate.*Options" src/ -B 2 -A 5 | head -60

Repository: parse-community/parse-server

Length of output: 23896


🏁 Script executed:

#!/bin/bash
# Check the actual ParseServerOptions.ts file more comprehensively
echo "=== Full context around cloud option ==="
cat src/Options/schemas/ParseServerOptions.ts | head -150

Repository: parse-community/parse-server

Length of output: 7231


🏁 Script executed:

#!/bin/bash
# Check reconfigureServer implementation
echo "=== reconfigureServer implementation in spec/helper.js ==="
rg -A 20 "function reconfigureServer|const reconfigureServer.*=" spec/helper.js | head -60

# Search for where schema validation occurs
echo -e "\n=== validateConfig and schema validation ==="
rg -B 3 -A 10 "schemaValidator\|ParseServerOptionsSchema" src/Options/validateConfig.ts | head -80

Repository: parse-community/parse-server

Length of output: 1027


🏁 Script executed:

#!/bin/bash
# Check if validation errors are caught or thrown in the startup flow
echo "=== ParseServer constructor/start validation flow ==="
rg -B 5 -A 15 "new ParseServer\|ParseServer.*constructor" src/ParseServer.ts | head -100

# Check if validateConfig is called during initialization
echo -e "\n=== validateConfig usage ==="
rg "validateConfig\|parse.*options" src/ParseServer.ts | head -20

Repository: parse-community/parse-server

Length of output: 155


🏁 Script executed:

#!/bin/bash
# Let's run a test to see if function values actually pass or fail
echo "=== Check if tests pass with cloud function ==="
cd spec && npm test -- --grep "baseAdapter" 2>&1 | head -100

Repository: parse-community/parse-server

Length of output: 414


🏁 Script executed:

#!/bin/bash
# Search for where validateConfig is imported and used
echo "=== validateConfig imports and usage ==="
rg "validateConfig" src/ --type ts --type js | head -30

# Check the validateConfig.ts file itself
echo -e "\n=== validateConfig.ts content ==="
cat src/Options/validateConfig.ts | head -100

Repository: parse-community/parse-server

Length of output: 1470


🏁 Script executed:

#!/bin/bash
# Check ParseServer.ts for where options are validated
echo "=== ParseServer.ts constructor and validation ==="
head -200 src/ParseServer.ts | tail -100

# Also check if there's any try-catch around validation
echo -e "\n=== Error handling in startup ==="
rg "try|catch|validateConfig" src/ParseServer.ts | head -20

Repository: parse-community/parse-server

Length of output: 4102


🏁 Script executed:

#!/bin/bash
# Let's trace what happens when reconfigureServer is called with cloud function
# First check the exact test that uses cloud with function
echo "=== Test using cloud with function ==="
rg -B 5 -A 15 'cloud: \(\) =>' spec/AuthenticationAdaptersV2.spec.js | head -40

Repository: parse-community/parse-server

Length of output: 1509


🏁 Script executed:

#!/bin/bash
# Check SchemaValidator implementation
echo "=== SchemaValidator.ts ==="
cat src/Options/validators/SchemaValidator.ts

# Also look for how schema validation actually happens
echo -e "\n=== Zod validation process ==="
rg -B 5 -A 15 "class SchemaValidator\|validate\(" src/Options/validators/SchemaValidator.ts | head -80

Repository: parse-community/parse-server

Length of output: 1253


🏁 Script executed:

#!/bin/bash
# Let's see what happens in the full ParseServer constructor flow
echo "=== ParseServer constructor ==="
sed -n '1,150p' src/ParseServer.ts | tail -80

# Check if validateConfig is wrapped in try-catch
echo -e "\n=== Constructor error handling ==="
rg -B 10 -A 5 "validateConfig" src/ParseServer.ts | head -50

Repository: parse-community/parse-server

Length of output: 5079


🏁 Script executed:

#!/antml/bash
# Let's verify the exact behavior by checking if there are any error suppressions
echo "=== Search for validation error handling ==="
rg "\.parse\(|\.safeParse\(|ZodError|validation.*error" src/ -B 2 -A 3 | head -60

Repository: parse-community/parse-server

Length of output: 2676


🏁 Script executed:

#!/antml/bash
# Get the full reconfigureServer implementation
echo "=== Full reconfigureServer implementation ==="
rg -A 100 "const reconfigureServer = async" spec/helper.js | head -120

# Check where parseServer is assigned/created
echo -e "\n=== ParseServer instantiation ==="
rg "parseServer = " spec/helper.js

Repository: parse-community/parse-server

Length of output: 3248


🏁 Script executed:

#!/bin/bash
# Check if there's a try-catch around parseServer construction
echo "=== Error handling around parseServer creation ==="
rg -B 5 -A 5 "new ParseServer\|= await" spec/helper.js | head -60

# Look for where the actual new ParseServer is created
echo -e "\n=== Search for ParseServer constructor call ==="
rg "new ParseServer" spec/ | head -20

Repository: parse-community/parse-server

Length of output: 607


🏁 Script executed:

#!/bin/bash
# Find ParseServer.startApp method
echo "=== ParseServer.startApp method ==="
rg -B 5 -A 30 "static.*startApp|startApp.*=" src/ParseServer.ts | head -80

Repository: parse-community/parse-server

Length of output: 1371


🏁 Script executed:

#!/bin/bash
# Let's also verify what happens during validation of the cloud option specifically
# by checking if the safeParse accepts or rejects function values
echo "=== Testing Zod schema behavior with union types ==="
cat > /tmp/test_zod.js << 'EOF'
const { z } = require('zod');

// Current schema
const currentSchema = z.object({
  cloud: z.string().optional(),
});

// Test with function
const testWithFunction = () => {
  try {
    const result = currentSchema.safeParse({ cloud: () => {} });
    console.log("Current schema (string only):");
    console.log("Success:", result.success);
    if (!result.success) {
      console.log("Error:", result.error.issues[0]);
    }
  } catch (e) {
    console.log("Exception:", e.message);
  }
};

testWithFunction();

// Proposed schema
const proposedSchema = z.object({
  cloud: z.union([z.string(), z.custom(v => typeof v === 'function')]).optional(),
});

const testProposed = () => {
  try {
    const result = proposedSchema.safeParse({ cloud: () => {} });
    console.log("\nProposed schema (string | function):");
    console.log("Success:", result.success);
    if (result.success) {
      console.log("Data:", typeof result.data.cloud);
    }
  } catch (e) {
    console.log("Exception:", e.message);
  }
};

testProposed();
EOF

cd /tmp && node test_zod.js

Repository: parse-community/parse-server

Length of output: 995


🏁 Script executed:

#!/bin/bash
# Check what the option() function does
echo "=== option() function implementation ==="
cat src/Options/schemaUtils.ts | head -200

Repository: parse-community/parse-server

Length of output: 6324


🏁 Script executed:

#!/bin/bash
# Let's search more carefully for where cloud validation might be bypassed
echo "=== Checking if there's special handling for cloud option ==="
rg "cloud" src/Options/ -B 2 -A 2 | head -60

# Also check if there's any ConfigValidationPipeline that might bypass Zod
echo -e "\n=== ConfigValidationPipeline ==="
rg -A 20 "class.*ValidationPipeline\|ConfigValidationPipeline" src/Options/ | head -40

Repository: parse-community/parse-server

Length of output: 2480


🏁 Script executed:

#!/bin/bash
# Let's check if there are any recent commits or PRs related to cloud option schema changes
echo "=== Recent changes to ParseServerOptions.ts ==="
git log --oneline src/Options/schemas/ParseServerOptions.ts | head -10

# Check git diff to see if cloud schema was recently changed
echo -e "\n=== Recent changes to cloud option specifically ==="
git log -p src/Options/schemas/ParseServerOptions.ts | grep -A 5 -B 5 "cloud:" | head -40

Repository: parse-community/parse-server

Length of output: 246


🏁 Script executed:

#!/bin/bash
# Let's verify if the masterKey pattern (which accepts functions) is documented
echo "=== Pattern used for options that accept functions ==="
rg "z.union.*z.custom.*Function" src/Options/schemas/ParseServerOptions.ts | wc -l

echo "Options accepting functions via union pattern:"
rg "z.union.*z.custom.*Function" src/Options/schemas/ParseServerOptions.ts -B 1 | grep -E "^\s+[a-z]+" | head -10

Repository: parse-community/parse-server

Length of output: 647


Match the schema to the runtime contract by accepting functions.

The cloud option schema is defined as z.string().optional() but ParseServer.start() explicitly accepts both strings and functions (lines 171-183 of src/ParseServer.ts). Other options that support function values (like masterKey, preventLoginWithUnverifiedEmail, sendUserEmailVerification) use the z.union([...type..., z.custom<Function>(v => typeof v === 'function')]) pattern, which cloud is missing.

Update the schema to accept both types:

Schema fix
-  cloud: option(z.string().optional(), {
+  cloud: option(z.union([z.string(), z.custom<Function>(v => typeof v === 'function')]).optional(), {
     env: 'PARSE_SERVER_CLOUD',
-    help: 'Path to the cloud code file (e.g. ./cloud/main.js) containing triggers and functions.',
+    help: 'Path to the cloud code file or a function that initializes cloud code.',
   }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cloud: option(z.string().optional(), {
env: 'PARSE_SERVER_CLOUD',
help: 'Path to the cloud code file (e.g. ./cloud/main.js) containing triggers and functions.',
}),
cloud: option(z.union([z.string(), z.custom<Function>(v => typeof v === 'function')]).optional(), {
env: 'PARSE_SERVER_CLOUD',
help: 'Path to the cloud code file or a function that initializes cloud code.',
}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 101 - 104, The cloud
option schema currently only allows strings but ParseServer.start() accepts both
string paths and functions; update the cloud option in ParseServerOptions.ts
(the cloud entry) to a union of z.string().optional() and z.custom<Function>(v
=> typeof v === 'function') (following the same pattern used by
masterKey/preventLoginWithUnverifiedEmail/sendUserEmailVerification) so the
schema accepts either a file path string or a function at runtime.

Comment on lines +231 to +234
graphQLSchema: option(z.string().optional(), {
env: 'PARSE_SERVER_GRAPH_QLSCHEMA',
help: 'Path to a custom GraphQL schema definition file to extend the auto-generated schema.',
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the ParseServerOptions schema definition
cat -n src/Options/schemas/ParseServerOptions.ts | sed -n '225,240p'

Repository: parse-community/parse-server

Length of output: 932


🏁 Script executed:

# Check the ParseServer.ts startApp method around lines 428-434
cat -n src/ParseServer.ts | sed -n '420,445p'

Repository: parse-community/parse-server

Length of output: 1153


🏁 Script executed:

# Search for graphQLSchema usage in the codebase to understand supported types
rg "graphQLSchema" -B 2 -A 2 --max-count 20

Repository: parse-community/parse-server

Length of output: 13821


🏁 Script executed:

# Search for tests or examples using graphQLSchema with objects or functions
rg "graphQLSchema\s*[:=]" -A 3 -B 1 --max-count 30

Repository: parse-community/parse-server

Length of output: 4193


🏁 Script executed:

# Look for test files that might test graphQLSchema with different types
fd ".*spec.js$|.*test.js$" | xargs rg -l "graphQLSchema" 2>/dev/null

Repository: parse-community/parse-server

Length of output: 135


🏁 Script executed:

# Check the generated definitions file to see current state
cat -n src/Options/Definitions.js | grep -A 5 -B 5 "graphQLSchema" | head -20

Repository: parse-community/parse-server

Length of output: 130


🏁 Script executed:

# Find the Definitions file - it might be generated or in a different location
find . -name "*Definitions*" -type f | head -20

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check the test files for graphQLSchema usage with objects or functions
cat -n spec/ParseGraphQLSchema.spec.js | head -100

Repository: parse-community/parse-server

Length of output: 4913


🏁 Script executed:

# Look for ParseGraphQLServer test with graphQLCustomTypeDefs to see if they test function/object variants
rg "graphQLCustomTypeDefs|graphQLSchema" spec/ParseGraphQLServer.spec.js -B 3 -A 3

Repository: parse-community/parse-server

Length of output: 1473


🏁 Script executed:

# Check if Definitions is generated or located elsewhere
fd "Definitions" . -type f 2>/dev/null | head -20

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check the Options directory structure
ls -la src/Options/

Repository: parse-community/parse-server

Length of output: 562


🏁 Script executed:

# Look for the npm run definitions script to understand the workflow
cat -n package.json | grep -A 5 -B 5 "definitions"

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Search for npm scripts related to options/definitions
rg "definitions|Definitions" package.json

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check if there's a types file that might be auto-generated
ls -la types/Options/

Repository: parse-community/parse-server

Length of output: 246


🏁 Script executed:

# Look at the test examples more closely to understand the actual supported types
rg "graphQLCustomTypeDefs" spec/ParseGraphQLServer.spec.js -B 5 -A 5 | head -50

Repository: parse-community/parse-server

Length of output: 1518


🏁 Script executed:

# Check how ParseServer uses the validated options
rg "startApp|ParseServer\(" src/ParseServer.ts -B 2 -A 2 | head -30

Repository: parse-community/parse-server

Length of output: 520


🏁 Script executed:

# Look for where the schema validation is actually invoked
rg "parseServerOptions\.graphQLSchema|options\.graphQLSchema" src/ParseServer.ts -B 3 -A 3

Repository: parse-community/parse-server

Length of output: 597


🏁 Script executed:

# Check if there are integration tests that test options validation
rg "graphQLSchema" spec/ -l | xargs grep -l "options\|config" 2>/dev/null

Repository: parse-community/parse-server

Length of output: 135


🏁 Script executed:

# Verify that the options flow through validation in ParseServer
rg "validateConfig|parseServerOptions" src/ParseServer.ts -B 2 -A 2 | head -40

Repository: parse-community/parse-server

Length of output: 922


🏁 Script executed:

# Check if there's any schema validation happening on constructor
cat -n src/ParseServer.ts | sed -n '1,150p' | grep -A 20 "constructor"

Repository: parse-community/parse-server

Length of output: 1980


🏁 Script executed:

# Verify that validateConfig actually enforces the schema
cat -n src/Options/validateConfig.ts

Repository: parse-community/parse-server

Length of output: 1208


🏁 Script executed:

# Check if there are any integration tests that pass graphQLSchema as options to ParseServer
rg "new ParseServer|ParseServer.startApp" spec/ -A 10 | grep -B 5 -A 5 "graphQLSchema"

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check SchemaValidator to understand how validation works
cat -n src/Options/validators/SchemaValidator.ts | head -50

Repository: parse-community/parse-server

Length of output: 1425


🏁 Script executed:

# Look for any tests in spec directory that instantiate ParseServer with graphQLSchema options
find spec -name "*.spec.js" -type f -exec grep -l "ParseServer\|startApp" {} \; | head -5 | xargs grep -l "graphQL" 2>/dev/null

Repository: parse-community/parse-server

Length of output: 482


🏁 Script executed:

# Check if there are any real-world examples or docs showing graphQLSchema with function/object
rg "graphQLSchema.*function|graphQLSchema.*\{" --type ts --type js -i | head -20

Repository: parse-community/parse-server

Length of output: 2002


🏁 Script executed:

# Search for any existing tests that try to pass graphQLSchema through ParseServer options
rg "new ParseServer\(|startApp\(" spec/ -A 20 | grep -B 10 -A 10 "graphQLSchema"

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check README for documented usage of graphQLSchema
grep -A 10 -B 2 "graphQLSchema" README.md

Repository: parse-community/parse-server

Length of output: 929


🏁 Script executed:

# Verify the type definitions match the current schema
cat -n types/Options/index.d.ts | grep -A 3 -B 3 "graphQLSchema"

Repository: parse-community/parse-server

Length of output: 367


graphQLSchema is narrower than the runtime API and will reject valid programmatic usage.

ParseServer.startApp() and the constructor both accept a parsed schema object or factory function on lines 428-434 of src/ParseServer.ts, but the Zod schema restricts this field to string only. Any attempt to pass graphQLSchema: parse(...) or a function will fail validation before reaching the runtime type checks.

The generated type in types/Options/index.d.ts (line 129) also incorrectly shows graphQLSchema?: string, preventing TypeScript developers from passing valid alternatives.

Expand the schema to match the runtime's actual capabilities:

↩️ Allow the same shapes the runtime already supports
-  graphQLSchema: option(z.string().optional(), {
-    env: 'PARSE_SERVER_GRAPH_QLSCHEMA',
-    help: 'Path to a custom GraphQL schema definition file to extend the auto-generated schema.',
-  }),
+  graphQLSchema: option(
+    z.union([
+      z.string(),
+      z.custom<object>(value => value !== null && typeof value === 'object' && !Array.isArray(value)),
+      z.custom<Function>(value => typeof value === 'function'),
+    ]).optional(),
+    {
+      env: 'PARSE_SERVER_GRAPH_QLSCHEMA',
+      help: 'Path to a custom GraphQL schema file, a parsed schema object, or a factory function.',
+    }
+  ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 231 - 234, The Zod
definition for graphQLSchema is too restrictive (currently
option(z.string().optional())) and must accept the same shapes ParseServer
supports (string path, a parsed schema object, or a factory function that
returns a schema) so validation and the generated types match runtime; update
the graphQLSchema option to a union that allows string | object | function
(e.g., option(z.union([z.string(), z.any(), z.function()]).optional(), ...)) so
ParseServer.startApp and the ParseServer constructor can accept a parsed schema
or factory function and the emitted types reflect those shapes.

Comment on lines +437 to +440
serverURL: option(z.string(), {
env: 'PARSE_SERVER_URL',
help: 'The URL where Parse Server is accessible, used for internal requests.',
}),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's check the current serverURL schema definition
cat -n src/Options/schemas/ParseServerOptions.ts | sed -n '437,440p'

Repository: parse-community/parse-server

Length of output: 251


🏁 Script executed:

# Check the ParseServer.ts file to verify the order of validateConfig and injectSpecialDefaults
sed -n '82,96p' src/ParseServer.ts

Repository: parse-community/parse-server

Length of output: 688


🏁 Script executed:

# Search for injectSpecialDefaults implementation to understand what it does
rg -A 20 "injectSpecialDefaults" src/ParseServer.ts

Repository: parse-community/parse-server

Length of output: 1641


🏁 Script executed:

# Look for the http://localhost fallback logic mentioned in the review
rg -B 5 -A 5 "http://localhost" src/

Repository: parse-community/parse-server

Length of output: 2938


Make serverURL optional in the schema to allow the fallback default to be injected.

validateConfig() runs before injectSpecialDefaults() (src/ParseServer.ts). With z.string(), a missing serverURL fails validation before the http://localhost:${port}${mountPath} fallback in injectSpecialDefaults() can be applied. The fallback logic exists but is unreachable.

↩️ Keep `serverURL` optional until the post-parse default is injected
-  serverURL: option(z.string(), {
+  serverURL: option(z.string().optional(), {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
serverURL: option(z.string(), {
env: 'PARSE_SERVER_URL',
help: 'The URL where Parse Server is accessible, used for internal requests.',
}),
serverURL: option(z.string().optional(), {
env: 'PARSE_SERVER_URL',
help: 'The URL where Parse Server is accessible, used for internal requests.',
}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Options/schemas/ParseServerOptions.ts` around lines 437 - 440, The
schema's serverURL field must be optional so validateConfig() won't reject a
missing value before injectSpecialDefaults() can set the fallback; update the
serverURL option declaration to accept an optional string (e.g., use
z.string().optional() inside the option call for the serverURL field) while
keeping the same env/help metadata so injectSpecialDefaults() in ParseServer.ts
can later populate the default URL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace Parse Server custom config logic with library Parse Server option cannot be set via environment for interfaces

2 participants