Skip to content

feat(cli): build and send json-schema per function#5572

Open
kaposke wants to merge 10 commits intomasterfrom
gui/NAN-4865/feat-zod-to-json-schema
Open

feat(cli): build and send json-schema per function#5572
kaposke wants to merge 10 commits intomasterfrom
gui/NAN-4865/feat-zod-to-json-schema

Conversation

@kaposke
Copy link
Contributor

@kaposke kaposke commented Mar 5, 2026

Function aka "flow", "sync_config"

We currently generate a "global" json schema with all the models in the integration folders and save it to schema.json. When deploying / generating docs / dry-running, we load that big schema and use it as the source of truth. We send the full json-schema for deploying, and let the server filter the schemas for each function (aka. sync_config, flow).
Furthermore, we take a very convoluted path to generate schema.json:

  • Convert zod schemas to NangoModel
  • Use NangoModel to generate schema.ts
  • Use ts-to-json-schema-generator to generate schema.json from schema.ts

In my previous PR, I made the server accept function-level json-schemas. Each function can now be sent with it's own json-schema, as opposed to a single top-level json-schema in POST /sync/deploy.

This PR:

  • Keeps the generation of schema.ts and schema.json AS IS (adds a deprecation notice comment on schema.ts)
  • Updates zod versions in the cli and runner-sdk packages to v4.3.6 (currently latest)
  • Also updates zod version in cli/example/package.json. This is what will make our customers packages be updated as soon as a cli command is run.
  • Leverages z.toJsonSchema (introduced in zod v4) to generate json-schemas directly from the zod schemas.
  • Adds the json-schemas to each function in the payload.
  • Stops sending top-level json-schema with all models
  • Modifies dry-run and generate:docs to use these function-level json-schemas instead of schema.json.
  • These new json-schemas are generated and kept in-memory. They don't generate any artifacts. Every run of deploy, dry-run and generate:docs will re-generate them in-memory for use. The source is the compiled cjs files, and not the typescript files themselves.

This PR shifts schema handling from a single aggregated schema.json to per-function JSON schemas generated directly from Zod v4 via z.toJSONSchema. The CLI now attaches models_json_schema to each sync/action in deploy payloads, and dry-run and docs generation consume the function-level schemas instead of loading a top-level file.

It updates zod to 4.3.6 in packages/cli, packages/runner-sdk, and packages/cli/example, introduces a new buildJsonSchemaDefinitionsFromZodModels helper, adds deprecation notices for legacy schema.ts/schema.json, and adjusts server validation/types/tests to accept schemas without a required `` field.

Possible Issues

• Docs generation now relies on entry.json_schema being present; missing json_schema could yield undefined model definitions
• Per-function schemas omit a top-level ``, which could affect external consumers expecting it


This summary was automatically generated by @propel-code-bot

@kaposke kaposke requested a review from a team March 5, 2026 13:57
@linear
Copy link

linear bot commented Mar 5, 2026

webhookSubscriptions: z.array(z.string().max(255)).optional(),
models_json_schema: z
.object({
$schema: z.literal('http://json-schema.org/draft-07/schema#'),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Felt like this is unnecessary and will increase storage with redundancy.

Copy link
Contributor

@propel-code-bot propel-code-bot bot left a comment

Choose a reason for hiding this comment

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

Schema generation may be invalid due to missing top-level $schema in output.

Status: Changes Suggested | Risk: Medium

Issues Identified & Suggestions
  • Add top-level $schema to keep JSON Schema valid and tests passing: packages/cli/lib/zeroYaml/json-schema.ts
Review Details

📁 20 files reviewed | 💬 1 comments

Instruction Files
├── .claude/
│   ├── agents/
│   │   └── nango-docs-migrator.md
│   └── skills/
│       ├── agent-builder-skill/
│       │   ├── EXAMPLES.md
│       │   └── SKILL.md
│       ├── creating-integration-docs/
│       │   └── SKILL.md
│       └── creating-skills-skill/
│           └── SKILL.md
├── AGENTS.md
└── GEMINI.md

👍 / 👎 individual comments to help improve reviews for you

Comment on lines +50 to +52
return {
definitions
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Important

[Logic] The new buildJsonSchemaDefinitionsFromZodModels return value omits the top-level $schema, but the added snapshot expects it. This will fail the unit test and produces a non-standard JSON Schema document. Add $schema at the top-level return so the output matches the snapshot and stays valid JSON Schema.

Suggested change
return {
definitions
};
return {
$schema: 'http://json-schema.org/draft-07/schema#',
definitions
};
Context for Agents
The new `buildJsonSchemaDefinitionsFromZodModels` return value omits the top-level `$schema`, but the added snapshot expects it. This will fail the unit test and produces a non-standard JSON Schema document. Add `$schema` at the top-level return so the output matches the snapshot and stays valid JSON Schema.

```suggestion
    return {
        $schema: 'http://json-schema.org/draft-07/schema#',
        definitions
    };
```

File: packages/cli/lib/zeroYaml/json-schema.ts
Line: 52

propel-code-bot[bot]

This comment was marked as outdated.

Copy link
Contributor

@propel-code-bot propel-code-bot bot left a comment

Choose a reason for hiding this comment

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

Changes suggested due to a potential runtime error when JSON schema definitions are missing.

Status: Changes Suggested | Risk: Medium

Issues Identified & Suggestions
  • Guard missing JSON schema definitions before casting to avoid runtime errors: packages/cli/lib/services/docs.service.ts
Review Details

📁 22 files reviewed | 💬 1 comments

Instruction Files
├── .claude/
│   ├── agents/
│   │   └── nango-docs-migrator.md
│   └── skills/
│       ├── agent-builder-skill/
│       │   ├── EXAMPLES.md
│       │   └── SKILL.md
│       ├── creating-integration-docs/
│       │   └── SKILL.md
│       └── creating-skills-skill/
│           └── SKILL.md
├── AGENTS.md
└── GEMINI.md

👍 / 👎 individual comments to help improve reviews for you

endpointType: entry.type,
scriptConfig: entry,
models: entry.usedModels.map((name) => ({ name, def: jsonSchema.definitions![name]! })),
models: entry.usedModels.map((name) => ({ name, def: entry.json_schema?.definitions?.[name] as JSONSchema7Definition })),
Copy link
Contributor

Choose a reason for hiding this comment

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

Important

[Logic] # Potential Runtime Error: Missing json_schema on Flows

In packages/cli/lib/services/docs.service.ts on line 63, the code uses optional chaining for entry.json_schema?.definitions?.[name] but then casts to JSONSchema7Definition without handling the undefined case. If a function doesn't have json_schema populated, this will pass undefined to the models array, causing potential errors when the documentation generator tries to use these definitions.

Specific scenario that fails:

  • Legacy syncs/actions that don't have json_schema populated yet (the field is optional during transition)
  • Doc generation tries to access undefined.definitions[name]
  • Results in broken documentation or runtime errors

Fix:

Add proper fallback handling:

models: entry.usedModels.map((name) => {
    const def = entry.json_schema?.definitions?.[name];
    if (!def) {
        throw new Error(`Missing JSON schema definition for model "${name}" in ${entry.type} "${entry.name}"`);
    }
    return { name, def: def as JSONSchema7Definition };
}),

Or provide a safe default:

models: entry.usedModels
    .map((name) => {
        const def = entry.json_schema?.definitions?.[name];
        return def ? { name, def: def as JSONSchema7Definition } : null;
    })
    .filter((model): model is { name: string; def: JSONSchema7Definition } => model !== null),
Context for Agents
# Potential Runtime Error: Missing `json_schema` on Flows

In `packages/cli/lib/services/docs.service.ts` on line 63, the code uses optional chaining for `entry.json_schema?.definitions?.[name]` but then casts to `JSONSchema7Definition` without handling the `undefined` case. If a function doesn't have `json_schema` populated, this will pass `undefined` to the models array, causing potential errors when the documentation generator tries to use these definitions.

**Specific scenario that fails:**
- Legacy syncs/actions that don't have `json_schema` populated yet (the field is optional during transition)
- Doc generation tries to access `undefined.definitions[name]`
- Results in broken documentation or runtime errors

**Fix:**

Add proper fallback handling:

```typescript
models: entry.usedModels.map((name) => {
    const def = entry.json_schema?.definitions?.[name];
    if (!def) {
        throw new Error(`Missing JSON schema definition for model "${name}" in ${entry.type} "${entry.name}"`);
    }
    return { name, def: def as JSONSchema7Definition };
}),
```

Or provide a safe default:

```typescript
models: entry.usedModels
    .map((name) => {
        const def = entry.json_schema?.definitions?.[name];
        return def ? { name, def: def as JSONSchema7Definition } : null;
    })
    .filter((model): model is { name: string; def: JSONSchema7Definition } => model !== null),
```

File: packages/cli/lib/services/docs.service.ts
Line: 63

import type { JSONSchema7 } from 'json-schema';

function zodSchemaToJsonSchema(schema: z.ZodTypeAny): JSONSchema7 | null {
if (schema.constructor.name === 'ZodVoid') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

would it be less fragile to use instanceOf instead of .name === or at least use the _zod private api?

* Converts a map of named Zod schemas into a single JSON Schema document
* with a `definitions` block containing each model.
*
* Zod schemas that resolve to `void` are silently skipped.
Copy link
Collaborator

Choose a reason for hiding this comment

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

that's not what the code above is saying

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.

2 participants