Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/flow-schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./structure": {
"types": "./dist/structure.d.ts",
"import": "./dist/structure.js"
},
"./schema/v1": "./dist/generated/flow.v1.schema.json"
},
"files": [
Expand Down
26 changes: 6 additions & 20 deletions packages/flow-schema/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
// in-repo copy so the Phase 3 swap is a dependency change, not a code
// change.

import { Ajv2020 } from 'ajv/dist/2020.js';
import type { ErrorObject } from 'ajv';

// The schema as a generated TS module, not a JSON import: import
// attributes (`with { type: 'json' }`) don't survive every consumer
// bundler. The raw JSON is still shipped at the `./schema/v1` export.
//
// Note: Ajv-based structural validation (`validateStructure`) deliberately
// does NOT live here — it compiles a schema via runtime code generation,
// which crashes code-gen-disallowed runtimes (Cloudflare Workers) at
// import time. It lives behind the `@wavekat/flow-schema/structure`
// subpath so this barrel stays Ajv-free and safe to bundle on the edge.
import schema from './generated/schema.js';

// Model: generated types + hand-written constants & helpers.
Expand Down Expand Up @@ -50,20 +53,3 @@ export {
setNodeValue,
stampIdentity,
} from './mutate.js';

const ajv = new Ajv2020({ allErrors: true, strict: false });
const validateFn = ajv.compile(schema);

export type StructuralResult =
| { valid: true }
| { valid: false; errors: ErrorObject[] };

/**
* Validate an already-parsed document against the normative JSON Schema
* (structure only — no reachability / exit-wiring / hours semantics; use
* `checkFlow` / `validateFlow` for those).
*/
export function validateStructure(doc: unknown): StructuralResult {
const valid = validateFn(doc);
return valid ? { valid: true } : { valid: false, errors: validateFn.errors ?? [] };
}
40 changes: 40 additions & 0 deletions packages/flow-schema/src/structure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// JSON-Schema structural validation — kept OUT of the package barrel.
//
// Ajv compiles a schema by *generating JavaScript source and `new
// Function`-ing it* into a validator (its whole speed model is JIT). Any
// runtime that disallows code-generation-from-strings — notably the
// Cloudflare Workers isolate the platform API runs on — throws
// `EvalError: Code generation from strings disallowed` the moment this
// module is evaluated. Because the compile happens at import time, merely
// importing the barrel used to crash such consumers at startup even when
// they never called `validateStructure`.
//
// So this lives behind the `@wavekat/flow-schema/structure` subpath: the
// main entry (`.`) stays pure hand-written code with no Ajv in its module
// graph, and only Node-side consumers that actually want meta-schema
// structural validation opt into the Ajv dependency. This subpath is NOT
// safe to import from a Workers/edge bundle.

import { Ajv2020 } from 'ajv/dist/2020.js';
import type { ErrorObject } from 'ajv';

import schema from './generated/schema.js';

const ajv = new Ajv2020({ allErrors: true, strict: false });
const validateFn = ajv.compile(schema);

export type StructuralResult =
| { valid: true }
| { valid: false; errors: ErrorObject[] };

/**
* Validate an already-parsed document against the normative JSON Schema
* (structure only — no reachability / exit-wiring / hours semantics; use
* `checkFlow` / `validateFlow` for those).
*
* Node-side only — see the module header on Workers incompatibility.
*/
export function validateStructure(doc: unknown): StructuralResult {
const valid = validateFn(doc);
return valid ? { valid: true } : { valid: false, errors: validateFn.errors ?? [] };
}
3 changes: 2 additions & 1 deletion packages/flow-schema/test/conformance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { parse } from 'yaml';

import { checkFlow, validateStructure } from '../src/index.js';
import { checkFlow } from '../src/index.js';
import { validateStructure } from '../src/structure.js';

const here = dirname(fileURLToPath(import.meta.url));
const corpusRoot = resolve(here, '..', '..', '..', 'conformance', 'v1');
Expand Down
Loading