@pipelex/mthds-form 0.33.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brand/index.js +2 -2
- package/dist/{chunk-UKTYADY3.js → chunk-5RV3ASMO.js} +141 -69
- package/dist/chunk-5RV3ASMO.js.map +1 -0
- package/dist/{chunk-VH5CDQDR.js → chunk-AURDKAIC.js} +3 -3
- package/dist/{chunk-VH5CDQDR.js.map → chunk-AURDKAIC.js.map} +1 -1
- package/dist/{chunk-ZX52CQJ4.js → chunk-CFBK7NO7.js} +3 -3
- package/dist/{chunk-ZX52CQJ4.js.map → chunk-CFBK7NO7.js.map} +1 -1
- package/dist/{chunk-3GO65HWP.js → chunk-PNNWNRPG.js} +11 -7
- package/dist/chunk-PNNWNRPG.js.map +1 -0
- package/dist/{chunk-XO5OENNF.js → chunk-TF4RTKLH.js} +32 -5
- package/dist/chunk-TF4RTKLH.js.map +1 -0
- package/dist/{chunk-IBBAJXIO.js → chunk-XIWBKV7T.js} +3 -3
- package/dist/{chunk-IBBAJXIO.js.map → chunk-XIWBKV7T.js.map} +1 -1
- package/dist/{chunk-4SRSGZWM.js → chunk-XYS5KCXB.js} +158 -31
- package/dist/chunk-XYS5KCXB.js.map +1 -0
- package/dist/core/gate.js +2 -2
- package/dist/core/index.js +4 -4
- package/dist/core/readiness.d.ts +34 -1
- package/dist/core/readiness.js +1 -1
- package/dist/core/seed.js +2 -2
- package/dist/core/values.js +2 -2
- package/dist/generative/headless/index.d.ts +15 -9
- package/dist/generative/headless/index.js +3 -2
- package/dist/generative/index.d.ts +27 -4
- package/dist/generative/index.js +628 -240
- package/dist/generative/index.js.map +1 -1
- package/dist/react/index.d.ts +30 -3
- package/dist/react/index.js +4 -4
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/chunk-3GO65HWP.js.map +0 -1
- package/dist/chunk-4SRSGZWM.js.map +0 -1
- package/dist/chunk-UKTYADY3.js.map +0 -1
- package/dist/chunk-XO5OENNF.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pruneEmptyOptionals, healStringWrappers } from './chunk-E6BIXJ6C.js';
|
|
2
|
-
import { isFilled, mustBeFilled, fieldFilled } from './chunk-
|
|
2
|
+
import { isFilled, mustBeFilled, fieldFilled } from './chunk-TF4RTKLH.js';
|
|
3
3
|
import { gatingFieldsFromInputs } from './chunk-Y2ND6W4W.js';
|
|
4
4
|
import { prepareSchemaForRjsf } from './chunk-LBFZXYCY.js';
|
|
5
5
|
import { inputMustBeFilled, isOptionalInput, isPluralInput } from './chunk-WYH56SOZ.js';
|
|
@@ -95,5 +95,5 @@ function gateRunInputs(contract, data) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
export { apiInputsFromSchemaData, buildRunInputsSchema, gateRunInputs, prepareRunInputs, validateRunInputs };
|
|
98
|
-
//# sourceMappingURL=chunk-
|
|
99
|
-
//# sourceMappingURL=chunk-
|
|
98
|
+
//# sourceMappingURL=chunk-XIWBKV7T.js.map
|
|
99
|
+
//# sourceMappingURL=chunk-XIWBKV7T.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/gate.ts"],"names":[],"mappings":"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","file":"chunk-IBBAJXIO.js","sourcesContent":["/**\n * The gate every run passes through, whichever surface starts it.\n *\n * A host may offer more than one place to run a method - a schema-driven\n * inputs panel (an RJSF form) and a standalone run page (a bespoke field\n * renderer), say. **Only the rendering of inputs differs.** Everything between\n * \"the user pressed Run\" and \"the payload goes on the wire\" lives here, so\n * those surfaces cannot drift:\n *\n * 1. `buildRunInputsSchema` - the combined JSON Schema, whose `required` list\n * is `inputMustBeFilled` (optional `?` and variable-plural `[]` never\n * block; a fixed-count `[N]` list does).\n * 2. `prepareRunInputs` - heal legacy wrappers, then prune empty optionals.\n * 3. `validateRunInputs` - ajv, plus the missing-input scan that names the\n * VARIABLE at fault.\n * 4. `apiInputsFromSchemaData` - the `{concept, content}` payload, built from\n * the PREPARED data so the pruning reaches the wire too.\n *\n * **`gateRunInputs` is that whole chain as one call**, and it is what a server\n * should use. The four steps stay exported because a host that renders its own\n * panel needs the schema and the verdict separately, but assembling them is not\n * a host's job: the step between validation and the payload is an emptiness\n * check with four look-alike predicates to choose from, only two of which are\n * the ones the Run button reads, and choosing the near-miss pair produces a\n * server that is more permissive than the button in front of it.\n *\n * This module is pure: no React, no toasts, no i18n. Each caller renders the\n * verdict in its own idiom.\n */\nimport { inputMustBeFilled, isOptionalInput, isPluralInput } from './contracts';\nimport { gatingFieldsFromInputs } from './gating-fields';\nimport { fieldFilled, isFilled, mustBeFilled } from './readiness';\nimport { validateRunInputsSchema, type RunInputError } from './gate-validator';\nimport { healStringWrappers, pruneEmptyOptionals } from './wire-format';\nimport { prepareSchemaForRjsf } from './normalize-schema';\nimport { ownProp } from './own-property';\nimport type { PipeIOContract, PipeInputContract } from './contracts';\n\ntype Dict = Record<string, unknown>;\n\n/**\n * Combine the per-input schemas into one object schema.\n *\n * Each input variable becomes a property, titled with the variable name and\n * described with the concept code. Only the inputs the method actually demands\n * land in `required` - an optional (`?`) or variable-plural (`[]`) input must\n * not block the form (see `inputMustBeFilled`).\n *\n * The per-input schema travels verbatim, which is how a fixed-count (`[N]`)\n * list's declared count reaches ajv: pipelex states it as `minItems`/`maxItems`\n * on the array wrapper, so the count is enforced here without the kernel\n * restating it.\n */\nexport function buildRunInputsSchema(inputs: Record<string, PipeInputContract>): Dict {\n const properties: Dict = {};\n const required: string[] = [];\n\n for (const [varName, input] of Object.entries(inputs)) {\n const inputSchema = { ...input.json_schema } as Dict;\n inputSchema.title = varName;\n if (input.concept_ref && input.concept_ref !== varName) {\n inputSchema.description = input.concept_ref;\n }\n properties[varName] = inputSchema;\n if (inputMustBeFilled(input)) required.push(varName);\n }\n\n return prepareSchemaForRjsf({ type: 'object', properties, required });\n}\n\n/**\n * Last-gate repair, in two passes against the exact schema ajv validates.\n *\n * 1. **HEAL** - persisted `inputData` may still carry the legacy double-wrapped\n * `{ text: { text } }` shape (or a form's state may lag a store fix).\n * 2. **PRUNE** - RJSF fills every unset string with `\"\"` on mount, which an\n * optional CONSTRAINED field rejects (`DateContent.time` is `format: \"time\"`)\n * - blocking a run on a field the user never opened. An absent optional field\n * is what pipelex means by `None` anyway, so dropping it is the semantically\n * correct repair, not a validation dodge.\n */\nexport function prepareRunInputs(data: Dict, schema: Dict): Dict {\n return pruneEmptyOptionals(healStringWrappers(data, schema), schema) as Dict;\n}\n\nexport interface RunInputsVerdict {\n isValid: boolean;\n /** Variable names the scan could pin a missing required field on. */\n missingInputs: string[];\n /** Raw ajv errors, for when the scan cannot name anything. */\n errors: RunInputError[];\n}\n\n/**\n * Validate PREPARED data against the combined schema.\n *\n * `missingInputs` is reported by VARIABLE NAME - the name on the node the user\n * clicks - never by `concept_ref`, which used to produce \"Missing required\n * fields in: native.Text\" and named neither the input at fault nor, when several\n * inputs share a concept, which of them. Inputs the method declared omittable\n * are skipped: an empty `?` input is not a missing one.\n *\n * The scan can come up empty even on an invalid form (a wrong value shape, a\n * nested mismatch); `errors` is what the caller falls back to then, so a failure\n * is never undiagnosable.\n *\n * `preparedData` is `unknown` for the same reason `gateRunInputs`' `data` is: on\n * a public endpoint the body is whatever was sent, and ajv must be shown that\n * rather than a repair of it, or `must be object` is a verdict no caller can\n * ever receive. The name-the-variable scan below needs something indexable, so\n * a body that is not a record scans against nothing and names every input the\n * method demands - which is the honest answer for a body that carried none.\n */\nexport function validateRunInputs(\n preparedData: unknown,\n inputs: Record<string, PipeInputContract>,\n schema: Dict,\n): RunInputsVerdict {\n const errors = validateRunInputsSchema(preparedData, schema);\n if (errors.length === 0) return { isValid: true, missingInputs: [], errors: [] };\n\n const record0 =\n typeof preparedData === 'object' && preparedData !== null && !Array.isArray(preparedData)\n ? (preparedData as Dict)\n : undefined;\n const missingInputs: string[] = [];\n for (const [varName, input] of Object.entries(inputs)) {\n if (!inputMustBeFilled(input)) continue;\n const varData = ownProp(record0, varName);\n // A demanded input that is not there at all is missing whatever its concept\n // declares inside. The check has to come BEFORE the required-children one:\n // a struct whose concept demands no child used to fall out of the scan\n // here, so the run was refused by the combined schema's `required` list\n // while the scan named nothing and the caller could only quote ajv.\n if (varData == null) {\n missingInputs.push(varName);\n continue;\n }\n const inputSchema = input.json_schema as Dict;\n const requiredFields = (inputSchema.required as string[]) || [];\n if (requiredFields.length === 0) continue;\n if (typeof varData !== 'object') {\n missingInputs.push(varName);\n continue;\n }\n const record = varData as Dict;\n if (\n requiredFields.some((f) => {\n const child = ownProp(record, f);\n return child === undefined || child === '';\n })\n ) {\n missingInputs.push(varName);\n }\n }\n\n return { isValid: false, missingInputs, errors };\n}\n\n/**\n * Build the `{ concept, content }` map a run expects, from PREPARED data.\n *\n * The two ways a method says \"this may be nothing\" land differently on the wire:\n * - an **optional (`?`)** input left blank is OMITTED, so the runtime records a\n * real absence the method can branch on - sending `{ text: \"\" }` would look\n * like the caller supplied an empty string;\n * - a **plural (`[]`)** input keeps its key, because a plural slot is never\n * absent - its empty form IS the empty list, and the runtime requires the key.\n * When that list is EMPTY it is sent BARE, without the envelope: the envelope\n * is the runtime's \"explicit form\", which bypasses the top-down shaper and\n * hands the value to the bottom-up factory, and that factory types a list from\n * its first item - so an empty list there raises \"Cannot create Stuff from\n * empty list in content\". The bare form keeps the shaper, which reads the\n * DECLARED concept and builds the empty list correctly. (pipelex/#1096 makes\n * the envelope agree; the bare form runs today and stays correct after.)\n *\n * A fixed-count (`[N]`) list takes that same empty-list path, and an empty one\n * is not a payload the method can accept. It is unreachable through the gate,\n * which demands the input and rejects a short list on `minItems`; building the\n * payload straight from unvalidated data is what would reach it, and inventing\n * a different absence here would only hide that.\n */\nexport function apiInputsFromSchemaData(\n preparedData: Dict,\n inputs: Record<string, PipeInputContract>,\n): Dict {\n const out: Dict = {};\n for (const [varName, input] of Object.entries(inputs)) {\n const raw = ownProp(preparedData, varName);\n if (isOptionalInput(input) && !isFilled(raw)) continue;\n if (isPluralInput(input) && !isFilled(raw)) {\n out[varName] = [];\n continue;\n }\n const content = isPluralInput(input) && !Array.isArray(raw) ? [] : raw;\n out[varName] = { concept: input.concept_ref, content };\n }\n return out;\n}\n\n// ─── The whole chain, as one call ────────────────────────────────────────────\n\n/** The gate's verdict: the payload to send, or why the run may not start. */\nexport type RunInputsGateResult =\n | { ok: true; inputs: Dict }\n | {\n ok: false;\n /** Variable names the caller left empty or short. */\n missingInputs: string[];\n /** Raw ajv errors, for when the scan cannot name anything. */\n errors: RunInputError[];\n /** The repaired data the verdict was reached on - what\n * `describeValidationError` needs to quote the value it received. */\n preparedData: Dict;\n };\n\n/**\n * One schema object per contract, for the lifetime of the process.\n *\n * `buildRunInputsSchema` is a pure function of the contract, so rebuilding it is\n * *semantically* free - but validation runs through a module-level ajv whose\n * compiled-schema cache is keyed on **schema object identity** and is never\n * evicted. A fresh object per call therefore misses every time and retains\n * another compiled validator. On a publicly callable server endpoint that is\n * unbounded growth driven by the cheapest request there is: an empty body,\n * rejected in a fraction of a millisecond, costing nothing to send.\n *\n * Weakly keyed so the map never pins a contract that goes out of scope. The\n * schema a run has been gated against is pinned for the process lifetime anyway\n * by ajv's own cache; the point is that the NUMBER of them is bounded by the\n * number of distinct contracts rather than by traffic.\n */\nconst SCHEMA_CACHE = new WeakMap<PipeIOContract, Dict>();\n\nfunction schemaForContract(contract: PipeIOContract): Dict {\n const cached = SCHEMA_CACHE.get(contract);\n if (cached) return cached;\n const schema = buildRunInputsSchema(contract.inputs);\n SCHEMA_CACHE.set(contract, schema);\n return schema;\n}\n\n/**\n * Gate a run: repair the caller's data, validate it against the contract,\n * refuse anything the Run button would have refused, and build the payload.\n *\n * Returns a verdict rather than throwing. A server endpoint must not throw\n * across the boundary to its client - frameworks routinely strip the message to\n * an opaque digest in a production build - and a rejection here is an ordinary\n * outcome, not an exceptional one.\n *\n * **`data` is `unknown` because that is what it is.** A run endpoint is public\n * and no framework enforces a declared parameter type, so the argument is\n * whatever the caller put in the body. The chain indexes the payload by\n * variable name without first checking it is indexable, so a `null` body would\n * throw *after* ajv had already reported `must be object` and before any\n * verdict came back.\n *\n * So the two uses are separated rather than collapsed into one repair. **ajv\n * judges the value the caller actually sent**, which is what makes `must be\n * object` the verdict for a body that is not one; the indexing that follows\n * uses a safe `{}` stand-in, which is what keeps the gate from throwing. Repair\n * FIRST - coercing a non-object to `{}` before validating - is the shape that\n * looks equivalent and is not: a contract whose inputs are all optional has an\n * empty `required` list, so `{}` validates, and `gateRunInputs(\"garbage\", …)`\n * came back `ok: true` on a body the caller never meant as a run. Fail-closed\n * costs a malformed caller an error it earned; the other direction bills a run.\n *\n * **The schema alone is not the rule.** ajv's `required` asserts only that a\n * key is PRESENT, and a content model carries no `minLength` - so a required\n * input that arrived empty (`{document: {url: \"\"}}`, `{text: {text: \"\"}}`)\n * satisfies the schema. That is the natural payload, not a contrived one:\n * `rjsfDataFromRunValues({}, fields)` emits exactly that when nothing is\n * selected. So the gate re-applies the emptiness rule using\n * `computeReadiness`'s OWN two functions over the same derived fields, which is\n * the only way to be sure the two sides cannot disagree.\n *\n * Picking a pair that merely looks equivalent is not enough, and this is the\n * whole reason the assembly lives here rather than in each host: the obvious\n * choice, `inputMustBeFilled` + `isFilled`, agrees on every leaf kind and\n * diverges on a structured concept in BOTH directions - `isFilled` on an object\n * is `some(child filled)` where `fieldFilled` is `every(required child\n * filled)`. That accepts a half-filled struct the browser refuses (a paid run\n * past a disabled button) and rejects a filled all-optional struct the browser\n * accepts. A host whose methods happen to use only native concepts has no test\n * that can catch either.\n */\nexport function gateRunInputs(contract: PipeIOContract, data: unknown): RunInputsGateResult {\n const isRecord = typeof data === 'object' && data !== null && !Array.isArray(data);\n const payload = isRecord ? (data as Dict) : {};\n const schema = schemaForContract(contract);\n const preparedData = prepareRunInputs(payload, schema);\n\n // The value ajv is shown is the caller's, not the stand-in - see above.\n const verdict = validateRunInputs(isRecord ? preparedData : data, contract.inputs, schema);\n if (!verdict.isValid) {\n return {\n ok: false,\n missingInputs: verdict.missingInputs,\n errors: verdict.errors,\n preparedData,\n };\n }\n\n // The SELECTION is half the rule, and leaving it out is what made the two\n // halves disagree. `computeReadiness` reckons with an input the method\n // demands AND one it left optional that the user has touched - because a\n // structure with something in it owes its concept every field the concept\n // declares. Filtering on `mustBeFilled` alone dropped the second kind, so a\n // half-filled optional struct was refused by the button and accepted here.\n //\n // ajv cannot cover for it: a required child that is a plain string arrives as\n // `''`, which satisfies `required`. And the payload builder below then SENDS\n // that input, because `isFilled` is exactly what it tests. Which is the rule\n // stated properly: the gate holds to its concept precisely the inputs it is\n // about to put on the wire, and nothing it will omit.\n //\n // The tree the predicates run over is the gate's own, walked structurally\n // from the same `json_schema` ajv just validated - NOT the render tree, which\n // is built from the input-form descriptor. A server validating a payload must\n // never need that presentation artifact; see `gating-fields.ts`.\n const missingInputs = gatingFieldsFromInputs(contract.inputs)\n .filter((field) => mustBeFilled(field) || isFilled(ownProp(preparedData, field.name)))\n .filter((field) => !fieldFilled(field, ownProp(preparedData, field.name)))\n .map((field) => field.name);\n if (missingInputs.length) return { ok: false, missingInputs, errors: [], preparedData };\n\n return { ok: true, inputs: apiInputsFromSchemaData(preparedData, contract.inputs) };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/gate.ts"],"names":[],"mappings":"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","file":"chunk-XIWBKV7T.js","sourcesContent":["/**\n * The gate every run passes through, whichever surface starts it.\n *\n * A host may offer more than one place to run a method - a schema-driven\n * inputs panel (an RJSF form) and a standalone run page (a bespoke field\n * renderer), say. **Only the rendering of inputs differs.** Everything between\n * \"the user pressed Run\" and \"the payload goes on the wire\" lives here, so\n * those surfaces cannot drift:\n *\n * 1. `buildRunInputsSchema` - the combined JSON Schema, whose `required` list\n * is `inputMustBeFilled` (optional `?` and variable-plural `[]` never\n * block; a fixed-count `[N]` list does).\n * 2. `prepareRunInputs` - heal legacy wrappers, then prune empty optionals.\n * 3. `validateRunInputs` - ajv, plus the missing-input scan that names the\n * VARIABLE at fault.\n * 4. `apiInputsFromSchemaData` - the `{concept, content}` payload, built from\n * the PREPARED data so the pruning reaches the wire too.\n *\n * **`gateRunInputs` is that whole chain as one call**, and it is what a server\n * should use. The four steps stay exported because a host that renders its own\n * panel needs the schema and the verdict separately, but assembling them is not\n * a host's job: the step between validation and the payload is an emptiness\n * check with four look-alike predicates to choose from, only two of which are\n * the ones the Run button reads, and choosing the near-miss pair produces a\n * server that is more permissive than the button in front of it.\n *\n * This module is pure: no React, no toasts, no i18n. Each caller renders the\n * verdict in its own idiom.\n */\nimport { inputMustBeFilled, isOptionalInput, isPluralInput } from './contracts';\nimport { gatingFieldsFromInputs } from './gating-fields';\nimport { fieldFilled, isFilled, mustBeFilled } from './readiness';\nimport { validateRunInputsSchema, type RunInputError } from './gate-validator';\nimport { healStringWrappers, pruneEmptyOptionals } from './wire-format';\nimport { prepareSchemaForRjsf } from './normalize-schema';\nimport { ownProp } from './own-property';\nimport type { PipeIOContract, PipeInputContract } from './contracts';\n\ntype Dict = Record<string, unknown>;\n\n/**\n * Combine the per-input schemas into one object schema.\n *\n * Each input variable becomes a property, titled with the variable name and\n * described with the concept code. Only the inputs the method actually demands\n * land in `required` - an optional (`?`) or variable-plural (`[]`) input must\n * not block the form (see `inputMustBeFilled`).\n *\n * The per-input schema travels verbatim, which is how a fixed-count (`[N]`)\n * list's declared count reaches ajv: pipelex states it as `minItems`/`maxItems`\n * on the array wrapper, so the count is enforced here without the kernel\n * restating it.\n */\nexport function buildRunInputsSchema(inputs: Record<string, PipeInputContract>): Dict {\n const properties: Dict = {};\n const required: string[] = [];\n\n for (const [varName, input] of Object.entries(inputs)) {\n const inputSchema = { ...input.json_schema } as Dict;\n inputSchema.title = varName;\n if (input.concept_ref && input.concept_ref !== varName) {\n inputSchema.description = input.concept_ref;\n }\n properties[varName] = inputSchema;\n if (inputMustBeFilled(input)) required.push(varName);\n }\n\n return prepareSchemaForRjsf({ type: 'object', properties, required });\n}\n\n/**\n * Last-gate repair, in two passes against the exact schema ajv validates.\n *\n * 1. **HEAL** - persisted `inputData` may still carry the legacy double-wrapped\n * `{ text: { text } }` shape (or a form's state may lag a store fix).\n * 2. **PRUNE** - RJSF fills every unset string with `\"\"` on mount, which an\n * optional CONSTRAINED field rejects (`DateContent.time` is `format: \"time\"`)\n * - blocking a run on a field the user never opened. An absent optional field\n * is what pipelex means by `None` anyway, so dropping it is the semantically\n * correct repair, not a validation dodge.\n */\nexport function prepareRunInputs(data: Dict, schema: Dict): Dict {\n return pruneEmptyOptionals(healStringWrappers(data, schema), schema) as Dict;\n}\n\nexport interface RunInputsVerdict {\n isValid: boolean;\n /** Variable names the scan could pin a missing required field on. */\n missingInputs: string[];\n /** Raw ajv errors, for when the scan cannot name anything. */\n errors: RunInputError[];\n}\n\n/**\n * Validate PREPARED data against the combined schema.\n *\n * `missingInputs` is reported by VARIABLE NAME - the name on the node the user\n * clicks - never by `concept_ref`, which used to produce \"Missing required\n * fields in: native.Text\" and named neither the input at fault nor, when several\n * inputs share a concept, which of them. Inputs the method declared omittable\n * are skipped: an empty `?` input is not a missing one.\n *\n * The scan can come up empty even on an invalid form (a wrong value shape, a\n * nested mismatch); `errors` is what the caller falls back to then, so a failure\n * is never undiagnosable.\n *\n * `preparedData` is `unknown` for the same reason `gateRunInputs`' `data` is: on\n * a public endpoint the body is whatever was sent, and ajv must be shown that\n * rather than a repair of it, or `must be object` is a verdict no caller can\n * ever receive. The name-the-variable scan below needs something indexable, so\n * a body that is not a record scans against nothing and names every input the\n * method demands - which is the honest answer for a body that carried none.\n */\nexport function validateRunInputs(\n preparedData: unknown,\n inputs: Record<string, PipeInputContract>,\n schema: Dict,\n): RunInputsVerdict {\n const errors = validateRunInputsSchema(preparedData, schema);\n if (errors.length === 0) return { isValid: true, missingInputs: [], errors: [] };\n\n const record0 =\n typeof preparedData === 'object' && preparedData !== null && !Array.isArray(preparedData)\n ? (preparedData as Dict)\n : undefined;\n const missingInputs: string[] = [];\n for (const [varName, input] of Object.entries(inputs)) {\n if (!inputMustBeFilled(input)) continue;\n const varData = ownProp(record0, varName);\n // A demanded input that is not there at all is missing whatever its concept\n // declares inside. The check has to come BEFORE the required-children one:\n // a struct whose concept demands no child used to fall out of the scan\n // here, so the run was refused by the combined schema's `required` list\n // while the scan named nothing and the caller could only quote ajv.\n if (varData == null) {\n missingInputs.push(varName);\n continue;\n }\n const inputSchema = input.json_schema as Dict;\n const requiredFields = (inputSchema.required as string[]) || [];\n if (requiredFields.length === 0) continue;\n if (typeof varData !== 'object') {\n missingInputs.push(varName);\n continue;\n }\n const record = varData as Dict;\n if (\n requiredFields.some((f) => {\n const child = ownProp(record, f);\n return child === undefined || child === '';\n })\n ) {\n missingInputs.push(varName);\n }\n }\n\n return { isValid: false, missingInputs, errors };\n}\n\n/**\n * Build the `{ concept, content }` map a run expects, from PREPARED data.\n *\n * The two ways a method says \"this may be nothing\" land differently on the wire:\n * - an **optional (`?`)** input left blank is OMITTED, so the runtime records a\n * real absence the method can branch on - sending `{ text: \"\" }` would look\n * like the caller supplied an empty string;\n * - a **plural (`[]`)** input keeps its key, because a plural slot is never\n * absent - its empty form IS the empty list, and the runtime requires the key.\n * When that list is EMPTY it is sent BARE, without the envelope: the envelope\n * is the runtime's \"explicit form\", which bypasses the top-down shaper and\n * hands the value to the bottom-up factory, and that factory types a list from\n * its first item - so an empty list there raises \"Cannot create Stuff from\n * empty list in content\". The bare form keeps the shaper, which reads the\n * DECLARED concept and builds the empty list correctly. (pipelex/#1096 makes\n * the envelope agree; the bare form runs today and stays correct after.)\n *\n * A fixed-count (`[N]`) list takes that same empty-list path, and an empty one\n * is not a payload the method can accept. It is unreachable through the gate,\n * which demands the input and rejects a short list on `minItems`; building the\n * payload straight from unvalidated data is what would reach it, and inventing\n * a different absence here would only hide that.\n */\nexport function apiInputsFromSchemaData(\n preparedData: Dict,\n inputs: Record<string, PipeInputContract>,\n): Dict {\n const out: Dict = {};\n for (const [varName, input] of Object.entries(inputs)) {\n const raw = ownProp(preparedData, varName);\n if (isOptionalInput(input) && !isFilled(raw)) continue;\n if (isPluralInput(input) && !isFilled(raw)) {\n out[varName] = [];\n continue;\n }\n const content = isPluralInput(input) && !Array.isArray(raw) ? [] : raw;\n out[varName] = { concept: input.concept_ref, content };\n }\n return out;\n}\n\n// ─── The whole chain, as one call ────────────────────────────────────────────\n\n/** The gate's verdict: the payload to send, or why the run may not start. */\nexport type RunInputsGateResult =\n | { ok: true; inputs: Dict }\n | {\n ok: false;\n /** Variable names the caller left empty or short. */\n missingInputs: string[];\n /** Raw ajv errors, for when the scan cannot name anything. */\n errors: RunInputError[];\n /** The repaired data the verdict was reached on - what\n * `describeValidationError` needs to quote the value it received. */\n preparedData: Dict;\n };\n\n/**\n * One schema object per contract, for the lifetime of the process.\n *\n * `buildRunInputsSchema` is a pure function of the contract, so rebuilding it is\n * *semantically* free - but validation runs through a module-level ajv whose\n * compiled-schema cache is keyed on **schema object identity** and is never\n * evicted. A fresh object per call therefore misses every time and retains\n * another compiled validator. On a publicly callable server endpoint that is\n * unbounded growth driven by the cheapest request there is: an empty body,\n * rejected in a fraction of a millisecond, costing nothing to send.\n *\n * Weakly keyed so the map never pins a contract that goes out of scope. The\n * schema a run has been gated against is pinned for the process lifetime anyway\n * by ajv's own cache; the point is that the NUMBER of them is bounded by the\n * number of distinct contracts rather than by traffic.\n */\nconst SCHEMA_CACHE = new WeakMap<PipeIOContract, Dict>();\n\nfunction schemaForContract(contract: PipeIOContract): Dict {\n const cached = SCHEMA_CACHE.get(contract);\n if (cached) return cached;\n const schema = buildRunInputsSchema(contract.inputs);\n SCHEMA_CACHE.set(contract, schema);\n return schema;\n}\n\n/**\n * Gate a run: repair the caller's data, validate it against the contract,\n * refuse anything the Run button would have refused, and build the payload.\n *\n * Returns a verdict rather than throwing. A server endpoint must not throw\n * across the boundary to its client - frameworks routinely strip the message to\n * an opaque digest in a production build - and a rejection here is an ordinary\n * outcome, not an exceptional one.\n *\n * **`data` is `unknown` because that is what it is.** A run endpoint is public\n * and no framework enforces a declared parameter type, so the argument is\n * whatever the caller put in the body. The chain indexes the payload by\n * variable name without first checking it is indexable, so a `null` body would\n * throw *after* ajv had already reported `must be object` and before any\n * verdict came back.\n *\n * So the two uses are separated rather than collapsed into one repair. **ajv\n * judges the value the caller actually sent**, which is what makes `must be\n * object` the verdict for a body that is not one; the indexing that follows\n * uses a safe `{}` stand-in, which is what keeps the gate from throwing. Repair\n * FIRST - coercing a non-object to `{}` before validating - is the shape that\n * looks equivalent and is not: a contract whose inputs are all optional has an\n * empty `required` list, so `{}` validates, and `gateRunInputs(\"garbage\", …)`\n * came back `ok: true` on a body the caller never meant as a run. Fail-closed\n * costs a malformed caller an error it earned; the other direction bills a run.\n *\n * **The schema alone is not the rule.** ajv's `required` asserts only that a\n * key is PRESENT, and a content model carries no `minLength` - so a required\n * input that arrived empty (`{document: {url: \"\"}}`, `{text: {text: \"\"}}`)\n * satisfies the schema. That is the natural payload, not a contrived one:\n * `rjsfDataFromRunValues({}, fields)` emits exactly that when nothing is\n * selected. So the gate re-applies the emptiness rule using\n * `computeReadiness`'s OWN two functions over the same derived fields, which is\n * the only way to be sure the two sides cannot disagree.\n *\n * Picking a pair that merely looks equivalent is not enough, and this is the\n * whole reason the assembly lives here rather than in each host: the obvious\n * choice, `inputMustBeFilled` + `isFilled`, agrees on every leaf kind and\n * diverges on a structured concept in BOTH directions - `isFilled` on an object\n * is `some(child filled)` where `fieldFilled` is `every(required child\n * filled)`. That accepts a half-filled struct the browser refuses (a paid run\n * past a disabled button) and rejects a filled all-optional struct the browser\n * accepts. A host whose methods happen to use only native concepts has no test\n * that can catch either.\n */\nexport function gateRunInputs(contract: PipeIOContract, data: unknown): RunInputsGateResult {\n const isRecord = typeof data === 'object' && data !== null && !Array.isArray(data);\n const payload = isRecord ? (data as Dict) : {};\n const schema = schemaForContract(contract);\n const preparedData = prepareRunInputs(payload, schema);\n\n // The value ajv is shown is the caller's, not the stand-in - see above.\n const verdict = validateRunInputs(isRecord ? preparedData : data, contract.inputs, schema);\n if (!verdict.isValid) {\n return {\n ok: false,\n missingInputs: verdict.missingInputs,\n errors: verdict.errors,\n preparedData,\n };\n }\n\n // The SELECTION is half the rule, and leaving it out is what made the two\n // halves disagree. `computeReadiness` reckons with an input the method\n // demands AND one it left optional that the user has touched - because a\n // structure with something in it owes its concept every field the concept\n // declares. Filtering on `mustBeFilled` alone dropped the second kind, so a\n // half-filled optional struct was refused by the button and accepted here.\n //\n // ajv cannot cover for it: a required child that is a plain string arrives as\n // `''`, which satisfies `required`. And the payload builder below then SENDS\n // that input, because `isFilled` is exactly what it tests. Which is the rule\n // stated properly: the gate holds to its concept precisely the inputs it is\n // about to put on the wire, and nothing it will omit.\n //\n // The tree the predicates run over is the gate's own, walked structurally\n // from the same `json_schema` ajv just validated - NOT the render tree, which\n // is built from the input-form descriptor. A server validating a payload must\n // never need that presentation artifact; see `gating-fields.ts`.\n const missingInputs = gatingFieldsFromInputs(contract.inputs)\n .filter((field) => mustBeFilled(field) || isFilled(ownProp(preparedData, field.name)))\n .filter((field) => !fieldFilled(field, ownProp(preparedData, field.name)))\n .map((field) => field.name);\n if (missingInputs.length) return { ok: false, missingInputs, errors: [], preparedData };\n\n return { ok: true, inputs: apiInputsFromSchemaData(preparedData, contract.inputs) };\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isNativeValueNode, readDateContent, formatDateContent, isNativeHtmlNode, isNativeDateNode, isNativeCompositeNode } from './chunk-QQK6Y55E.js';
|
|
2
|
-
import { APP_STYLES } from './chunk-
|
|
2
|
+
import { APP_STYLES } from './chunk-PNNWNRPG.js';
|
|
3
|
+
import { isFilled } from './chunk-TF4RTKLH.js';
|
|
3
4
|
import { hasOwnProp } from './chunk-V3OV2XQT.js';
|
|
4
5
|
import { defineSchema, defineCatalog, validateSpec, compileSpecStream } from '@json-render/core';
|
|
5
6
|
import { z } from 'zod';
|
|
@@ -958,9 +959,10 @@ function condBranches(value) {
|
|
|
958
959
|
}
|
|
959
960
|
|
|
960
961
|
// src/generative/share-amount.ts
|
|
962
|
+
var DECIMAL = /^[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?$/i;
|
|
961
963
|
function shareAmount(raw) {
|
|
962
964
|
if (typeof raw === "number") return raw;
|
|
963
|
-
if (typeof raw === "string" && raw.trim()
|
|
965
|
+
if (typeof raw === "string" && DECIMAL.test(raw.trim())) return Number(raw);
|
|
964
966
|
return Number.NaN;
|
|
965
967
|
}
|
|
966
968
|
|
|
@@ -980,6 +982,9 @@ function isRecord(value) {
|
|
|
980
982
|
}
|
|
981
983
|
function specMalformation(spec) {
|
|
982
984
|
if (!isRecord(spec)) return `it is ${shown(spec)}, not a structure`;
|
|
985
|
+
if (spec.root !== void 0 && typeof spec.root !== "string") {
|
|
986
|
+
return `its "root" is ${shown(spec.root)}, not an element's key`;
|
|
987
|
+
}
|
|
983
988
|
if (!isRecord(spec.elements)) return 'it has no "elements" map';
|
|
984
989
|
for (const [key, element] of Object.entries(spec.elements)) {
|
|
985
990
|
const at = JSON.stringify(key);
|
|
@@ -990,15 +995,26 @@ function specMalformation(spec) {
|
|
|
990
995
|
return `the element ${at} has "props" that are not a structure`;
|
|
991
996
|
if (children != null && !Array.isArray(children))
|
|
992
997
|
return `the element ${at} has "children" that are not a list`;
|
|
998
|
+
const child = Array.isArray(children) ? firstNotAKey(children) : void 0;
|
|
999
|
+
if (child)
|
|
1000
|
+
return `the element ${at} has a child that is ${shown(child.entry)}, not an element's key`;
|
|
993
1001
|
if (slots != null && (!isRecord(slots) || !Object.values(slots).every(Array.isArray))) {
|
|
994
1002
|
return `the element ${at} has "slots" that are not lists`;
|
|
995
1003
|
}
|
|
1004
|
+
const slotted = isRecord(slots) ? firstNotAKey(Object.values(slots).flatMap((entries) => entries)) : void 0;
|
|
1005
|
+
if (slotted) {
|
|
1006
|
+
return `the element ${at} has a slot entry that is ${shown(slotted.entry)}, not an element's key`;
|
|
1007
|
+
}
|
|
996
1008
|
if (repeat !== void 0 && !repeatsOver(repeat)) {
|
|
997
1009
|
return `the element ${at} has a "repeat" that names no "statePath"`;
|
|
998
1010
|
}
|
|
999
1011
|
}
|
|
1000
1012
|
return void 0;
|
|
1001
1013
|
}
|
|
1014
|
+
function firstNotAKey(entries) {
|
|
1015
|
+
for (const entry of entries) if (typeof entry !== "string") return { entry };
|
|
1016
|
+
return void 0;
|
|
1017
|
+
}
|
|
1002
1018
|
function repeatsOver(repeat) {
|
|
1003
1019
|
if (!isRecord(repeat)) return false;
|
|
1004
1020
|
const { statePath } = repeat;
|
|
@@ -1325,27 +1341,67 @@ function nonScalarReads(spec, parents, root, fieldAt) {
|
|
|
1325
1341
|
const branches = depth < MAX_COND_DEPTH ? condBranches(value) : void 0;
|
|
1326
1342
|
return branches ? branches.flatMap((branch) => outcomes(branch, depth + 1)) : [value];
|
|
1327
1343
|
};
|
|
1344
|
+
const amountPathRefusal = (path) => {
|
|
1345
|
+
const field = isUnder(root, path) ? fieldAt(path) : void 0;
|
|
1346
|
+
if (field && field.kind !== "number" && field.kind !== "unknown") {
|
|
1347
|
+
return `reads ${path}, which holds ${kindWords(field.kind)}, not a number`;
|
|
1348
|
+
}
|
|
1349
|
+
return void 0;
|
|
1350
|
+
};
|
|
1351
|
+
const amountRefusal = (amount) => {
|
|
1352
|
+
if (Number.isFinite(shareAmount(amount)) || readsIndex(amount)) return void 0;
|
|
1353
|
+
const path = readPath(amount);
|
|
1354
|
+
if (path === void 0) {
|
|
1355
|
+
const shown2 = isExpression(amount) ? "an expression that reads no number" : String(JSON.stringify(amount ?? null));
|
|
1356
|
+
return `is ${shown2}, not a number`;
|
|
1357
|
+
}
|
|
1358
|
+
return amountPathRefusal(path);
|
|
1359
|
+
};
|
|
1360
|
+
if (element.type === "ProportionBar") {
|
|
1361
|
+
const props = element.props ?? {};
|
|
1362
|
+
for (const prop of ["total", "of"]) {
|
|
1363
|
+
const refusals = outcomes(props[prop]).filter((outcome) => outcome !== null && outcome !== void 0).map(
|
|
1364
|
+
(outcome) => readsIndex(outcome) ? "is the repeat's index, not an amount" : amountRefusal(outcome)
|
|
1365
|
+
).filter((refusal) => refusal !== void 0);
|
|
1366
|
+
for (const refusal of new Set(refusals)) {
|
|
1367
|
+
problems.push(
|
|
1368
|
+
`${key}: ProportionBar's ${prop} ${refusal}; a total or a ceiling is a number, or reads a path that holds one with { "$state" }`
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1328
1373
|
const inlineShares = (entries) => {
|
|
1329
1374
|
if (element.type !== "ProportionBar") return entries;
|
|
1330
1375
|
const { labelKey, valueKey } = element.props ?? {};
|
|
1331
1376
|
if (typeof labelKey !== "string" || typeof valueKey !== "string") return entries;
|
|
1332
1377
|
const how = `each part is written as a structure with ${memberWords(labelKey)} member and ${memberWords(valueKey)} member holding a number, or the whole list is read with { "$state" }`;
|
|
1333
|
-
const
|
|
1334
|
-
if (
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
return
|
|
1378
|
+
const templateAmount = (amount) => {
|
|
1379
|
+
if (expressionForm(amount) !== "$template") return void 0;
|
|
1380
|
+
const text = amount.$template;
|
|
1381
|
+
const sole = /^\s*(\$\{[^}]+\})\s*$/.exec(text)?.[1];
|
|
1382
|
+
if (sole !== void 0) {
|
|
1383
|
+
const [path] = interpolatedPaths(sole, itemPath);
|
|
1384
|
+
return path === void 0 ? "more" : { path };
|
|
1340
1385
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
return true;
|
|
1386
|
+
return interpolatedPaths(text, itemPath).length > 0 ? "more" : { text };
|
|
1387
|
+
};
|
|
1388
|
+
const inlineAmountRefusal = (amount) => {
|
|
1389
|
+
if (expressionForm(amount) === "$computed") {
|
|
1390
|
+
return "is a $computed, which a page has no function to resolve, not a number";
|
|
1347
1391
|
}
|
|
1348
|
-
|
|
1392
|
+
const written = templateAmount(amount);
|
|
1393
|
+
if (written === void 0) return amountRefusal(amount);
|
|
1394
|
+
if (written === "more") {
|
|
1395
|
+
return "is a template that writes text beside its value, not a number";
|
|
1396
|
+
}
|
|
1397
|
+
if ("path" in written) return amountPathRefusal(written.path);
|
|
1398
|
+
return Number.isFinite(shareAmount(written.text)) ? void 0 : `is a template that writes ${JSON.stringify(written.text)} out, not a number`;
|
|
1399
|
+
};
|
|
1400
|
+
const amountRefused = (part, amount) => {
|
|
1401
|
+
const refusal = inlineAmountRefusal(amount);
|
|
1402
|
+
if (refusal === void 0) return false;
|
|
1403
|
+
problems.push(`${part}'s ${JSON.stringify(valueKey)} ${refusal}; ${how}`);
|
|
1404
|
+
return true;
|
|
1349
1405
|
};
|
|
1350
1406
|
const sharePart = (part, entry) => {
|
|
1351
1407
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
@@ -1413,6 +1469,7 @@ function nonScalarReads(spec, parents, root, fieldAt) {
|
|
|
1413
1469
|
}
|
|
1414
1470
|
};
|
|
1415
1471
|
for (const [prop, value] of Object.entries(element.props ?? {})) {
|
|
1472
|
+
if (element.type === "ProportionBar" && (prop === "total" || prop === "of")) continue;
|
|
1416
1473
|
const items = listy?.get(prop);
|
|
1417
1474
|
if (items === void 0 || Array.isArray(value)) {
|
|
1418
1475
|
walk(prop, prop === "items" && Array.isArray(value) ? inlineShares(value) : value);
|
|
@@ -1472,10 +1529,16 @@ function isOffered(path, offered) {
|
|
|
1472
1529
|
if (offered.bound.has(path) || offered.repeated.has(path)) return true;
|
|
1473
1530
|
return offered.delegated.some((at) => path === at || path.startsWith(`${at}/`));
|
|
1474
1531
|
}
|
|
1475
|
-
function uncoveredUnder(field, path, offered) {
|
|
1532
|
+
function uncoveredUnder(field, path, offered, seeded) {
|
|
1476
1533
|
if (isOffered(path, offered)) return [];
|
|
1477
1534
|
if (field.kind === "object") {
|
|
1478
|
-
|
|
1535
|
+
const seededBelow = seeded && field.required && field.defaultValue === void 0;
|
|
1536
|
+
const under = (child) => uncoveredUnder(child, joinPath(path, child.name), offered, seededBelow);
|
|
1537
|
+
const required = field.fields.filter((child) => child.required);
|
|
1538
|
+
if (required.length > 0) return required.flatMap(under);
|
|
1539
|
+
return field.fields.some(
|
|
1540
|
+
(child) => seededBelow && isFilled(child.defaultValue) || under(child).length === 0
|
|
1541
|
+
) ? [] : [path];
|
|
1479
1542
|
}
|
|
1480
1543
|
return [path];
|
|
1481
1544
|
}
|
|
@@ -1490,14 +1553,75 @@ function descriptorMalformation(descriptor) {
|
|
|
1490
1553
|
if (typeof descriptor !== "object" || descriptor === null) {
|
|
1491
1554
|
return "the layout has no descriptor to be checked against";
|
|
1492
1555
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1556
|
+
try {
|
|
1557
|
+
const budget = { fields: 0 };
|
|
1558
|
+
const { inputs, result } = descriptor;
|
|
1559
|
+
if (inputs != null && !Array.isArray(inputs)) {
|
|
1560
|
+
return `the descriptor's "inputs" is not a list of fields`;
|
|
1561
|
+
}
|
|
1562
|
+
for (const [index, input] of (inputs ?? []).entries()) {
|
|
1563
|
+
const problem = fieldMalformation(input, `input ${index + 1}`, /* @__PURE__ */ new Set(), budget);
|
|
1564
|
+
if (problem !== void 0) return `the descriptor's ${problem}`;
|
|
1565
|
+
}
|
|
1566
|
+
if (result != null) {
|
|
1567
|
+
if (!isField(result)) return `the descriptor's "result" is not a field`;
|
|
1568
|
+
const problem = fieldMalformation(result, '"result"', /* @__PURE__ */ new Set(), budget);
|
|
1569
|
+
if (problem !== void 0) return `the descriptor's ${problem}`;
|
|
1570
|
+
}
|
|
1571
|
+
return void 0;
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
let said;
|
|
1574
|
+
try {
|
|
1575
|
+
said = String(error);
|
|
1576
|
+
} catch {
|
|
1577
|
+
said = "an error that cannot be written out";
|
|
1578
|
+
}
|
|
1579
|
+
return `the descriptor cannot be read (${said})`;
|
|
1496
1580
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1581
|
+
}
|
|
1582
|
+
var MAX_FIELD_DEPTH = 64;
|
|
1583
|
+
var MAX_DESCRIPTOR_FIELDS = 1e4;
|
|
1584
|
+
function isField(value) {
|
|
1585
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.kind === "string" && typeof value.name === "string";
|
|
1586
|
+
}
|
|
1587
|
+
function fieldMalformation(value, subject, ancestors, budget) {
|
|
1588
|
+
if (++budget.fields > MAX_DESCRIPTOR_FIELDS) {
|
|
1589
|
+
return `fields number more than ${MAX_DESCRIPTOR_FIELDS}, counted at each place one is held`;
|
|
1590
|
+
}
|
|
1591
|
+
if (!isField(value)) return `${subject} is not a field`;
|
|
1592
|
+
const named = `${subject} (${JSON.stringify(value.name)})`;
|
|
1593
|
+
if (ancestors.has(value)) return `${named} contains itself`;
|
|
1594
|
+
if (ancestors.size >= MAX_FIELD_DEPTH) {
|
|
1595
|
+
return `${named} is nested more than ${MAX_FIELD_DEPTH} fields deep`;
|
|
1596
|
+
}
|
|
1597
|
+
ancestors.add(value);
|
|
1598
|
+
try {
|
|
1599
|
+
if (value.kind === "object") {
|
|
1600
|
+
const { fields } = value;
|
|
1601
|
+
if (!Array.isArray(fields)) return `${named} has no list of member "fields"`;
|
|
1602
|
+
for (const [index, member] of fields.entries()) {
|
|
1603
|
+
const problem = fieldMalformation(
|
|
1604
|
+
member,
|
|
1605
|
+
`${named} member ${index + 1}`,
|
|
1606
|
+
ancestors,
|
|
1607
|
+
budget
|
|
1608
|
+
);
|
|
1609
|
+
if (problem !== void 0) return problem;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
if (value.kind === "list") {
|
|
1613
|
+
const problem = fieldMalformation(
|
|
1614
|
+
value.item,
|
|
1615
|
+
`${named} item`,
|
|
1616
|
+
ancestors,
|
|
1617
|
+
budget
|
|
1618
|
+
);
|
|
1619
|
+
if (problem !== void 0) return problem;
|
|
1620
|
+
}
|
|
1621
|
+
return void 0;
|
|
1622
|
+
} finally {
|
|
1623
|
+
ancestors.delete(value);
|
|
1499
1624
|
}
|
|
1500
|
-
return void 0;
|
|
1501
1625
|
}
|
|
1502
1626
|
function fitCheck(descriptor, spec) {
|
|
1503
1627
|
try {
|
|
@@ -1581,7 +1705,7 @@ function collectLayoutProblems(descriptor, spec) {
|
|
|
1581
1705
|
};
|
|
1582
1706
|
for (const field of inputs) {
|
|
1583
1707
|
if (!field.required) continue;
|
|
1584
|
-
for (const path of uncoveredUnder(field, joinPath(INPUTS_ROOT, field.name), offered)) {
|
|
1708
|
+
for (const path of uncoveredUnder(field, joinPath(INPUTS_ROOT, field.name), offered, true)) {
|
|
1585
1709
|
problems.push(`${path} is required, and the layout offers nowhere to enter it`);
|
|
1586
1710
|
}
|
|
1587
1711
|
}
|
|
@@ -2072,12 +2196,15 @@ var REPAIR_ATTEMPTS = 2;
|
|
|
2072
2196
|
function layoutCheck(spec, { frame, descriptor }) {
|
|
2073
2197
|
const refused = (line) => ({ problems: [line], checksFailed: false });
|
|
2074
2198
|
if (!FRAMES.includes(frame)) {
|
|
2075
|
-
return
|
|
2076
|
-
|
|
2077
|
-
|
|
2199
|
+
return {
|
|
2200
|
+
problems: [
|
|
2201
|
+
`the layout is checked against the frame ${JSON.stringify(frame)}, which is none of ${FRAMES.join(", ")}`
|
|
2202
|
+
],
|
|
2203
|
+
checksFailed: true
|
|
2204
|
+
};
|
|
2078
2205
|
}
|
|
2079
2206
|
const misshapen = descriptorMalformation(descriptor);
|
|
2080
|
-
if (misshapen !== void 0) return
|
|
2207
|
+
if (misshapen !== void 0) return { problems: [misshapen], checksFailed: true };
|
|
2081
2208
|
const malformed = specMalformation(spec);
|
|
2082
2209
|
if (malformed !== void 0) return refused(malformedLine(malformed));
|
|
2083
2210
|
try {
|
|
@@ -2443,5 +2570,5 @@ function findFixture(fixtures, pipeRef, id) {
|
|
|
2443
2570
|
}
|
|
2444
2571
|
|
|
2445
2572
|
export { COMPONENT_NAMES, COMPONENT_RENDERINGS, COMPOSER_COMPONENTS, CUSTOM_COMPONENTS, DESIGNER_BRIEF_CONCEPT, DESIGNER_CATALOG_CONCEPT, FRAMES, FRAME_COMPONENTS, ICON_NAMES, INPUTS_ROOT, METRIC_FORMATS, PICKED_SHADCN, PRODUCT_COMPONENTS, PROMPT_HASH, REPAIR_ATTEMPTS, RESULT_COMPONENTS, RESULT_ROOT, absoluteHatchPath, baseCatalog, catalog, componentNamesFor, definitionChain, designerCatalog, fieldAtSegments, findFixture, fixtureId, fixtureLabel, formatProblems, frameCatalog, frameComponents, inputBrief, inputFieldAtPath, isDelegatedInput, isDelegatedResult, joinPath, jsonlLines, layoutCheck, layoutCheckProblems, layoutFits, layoutProblems, payloadToState, promptHashSubject, repeatBasePathOf, resultBrief, resultFieldAtPath, seedLine, segmentsUnder, shareAmount, specFromJsonl, specToJsonl, unwrapped, validateAgainstCatalog };
|
|
2446
|
-
//# sourceMappingURL=chunk-
|
|
2447
|
-
//# sourceMappingURL=chunk-
|
|
2573
|
+
//# sourceMappingURL=chunk-XYS5KCXB.js.map
|
|
2574
|
+
//# sourceMappingURL=chunk-XYS5KCXB.js.map
|