@stonecrop/schema 0.35.0 → 0.38.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/README.md CHANGED
@@ -34,11 +34,12 @@ Every field declares a `component` — the Vue widget that renders it. `componen
34
34
  import { CANONICAL_COMPONENTS, componentCategory } from '@stonecrop/schema'
35
35
 
36
36
  // Components by value category (componentCategory):
37
- // text: ATextInput, ATextboxInput, ADuration
37
+ // text: ATextInput, ATextboxInput
38
38
  // number: ANumericInput
39
39
  // boolean: ACheckbox
40
40
  // date: ADate, ADatePicker, ADateSelection, ADateRange
41
41
  // datetime: ADateTime
42
+ // duration: ADuration (an ISO 8601 duration, such as `PT1H`)
42
43
  // code: ACodeEditor (pair with `language: 'json' | 'javascript' | …`)
43
44
  // select: ADropdown
44
45
  // link: AFormLink (inline picker)
@@ -1 +1 @@
1
- {"version":3,"file":"converter-2mU9FiFz.js","names":[],"sources":["../src/table.ts","../src/field.ts","../src/naming.ts","../src/doctype.ts","../src/validation.ts","../src/converter/scalars.ts","../src/converter/heuristics.ts","../../node_modules/.pnpm/pluralize@8.0.0/node_modules/pluralize/pluralize.js","../src/converter/aggregate.ts","../src/converter/authored.ts","../src/converter/merge.ts","../src/converter/index.ts"],"sourcesContent":["import { z } from 'zod'\n\n/**\n * JSON-safe view configuration for table fields in doctype authoring.\n *\n * This is the authoring-time subset of `@stonecrop/atable`'s `TableConfig`. It covers\n * the view discriminator and structural options that can be expressed in static JSON.\n * `rowActions` (which requires function-typed handlers) stays in the runtime `TableConfig`.\n *\n * @public\n */\nexport const TableViewConfig = z\n\t.object({\n\t\t/** The table view type */\n\t\tview: z.enum(['list', 'uncounted', 'list-expansion', 'tree', 'gantt', 'tree-gantt']).optional(),\n\n\t\t/** Allow the table to use the full width of its container */\n\t\tfullWidth: z.boolean().optional(),\n\n\t\t/** Default expansion state for tree views */\n\t\tdefaultTreeExpansion: z.enum(['root', 'branch', 'leaf']).optional(),\n\n\t\t/** Enable dependency graph connections for Gantt views */\n\t\tdependencyGraph: z.boolean().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'TableViewConfig',\n\t\tdescription: 'JSON-safe view configuration for table fields in doctype authoring',\n\t})\n\n/**\n * Table view configuration type inferred from Zod schema\n * @public\n */\nexport type TableViewConfig = z.infer<typeof TableViewConfig>\n","import { z } from 'zod'\n\nimport type { ColumnSchema } from './column-schema'\nimport { flattenFields } from './flatten'\nimport type { InteractionMode } from './mode'\nimport { TableViewConfig } from './table'\n\n// Re-exported so callers already on this module keep one import; `flatten.ts` says why the\n// definition itself sits off to the side.\nexport { flattenFields }\n\n/**\n * Field options - flexible bag for type-specific configuration.\n *\n * Usage:\n * - Select: array of choices ([\"Draft\", \"Submitted\", \"Cancelled\"])\n * - Select with badges: \\{ choices: [...], badges: \\{ Open: \"warning\", ... \\} \\} or bare map\n * - Decimal: config object (\\{ precision: 10, scale: 2 \\})\n * - Code: config object (\\{ language: \"python\" \\})\n *\n * Deliberately *not* a bare string: a string once meant \"link target\", which made the value's\n * shape encode its meaning. That job belongs to `ValueField.doctype`, leaving this a plain\n * choices-or-config bag.\n *\n * @public\n */\nexport const FieldOptions = z\n\t.union([\n\t\tz.array(z.string()), // Select choices: [\"A\", \"B\", \"C\"]\n\t\tz.record(z.string(), z.unknown()), // Config: \\{ precision: 10, scale: 2 \\}\n\t])\n\t.meta({\n\t\ttitle: 'FieldOptions',\n\t\tdescription: 'Field options - flexible bag for type-specific configuration',\n\t})\n\n/**\n * Field options type inferred from Zod schema\n * @public\n */\nexport type FieldOptions = z.infer<typeof FieldOptions>\n\n/**\n * Validation configuration for form fields\n * @public\n */\nexport const FieldValidation = z\n\t.looseObject({\n\t\t/** Error message to display when validation fails */\n\t\terrorMessage: z.string(),\n\t})\n\t.meta({\n\t\ttitle: 'FieldValidation',\n\t\tdescription: 'Validation configuration for form fields',\n\t})\n\n/**\n * Field validation type inferred from Zod schema\n * @public\n */\nexport type FieldValidation = z.infer<typeof FieldValidation>\n\n// ---------------------------------------------------------------------------\n// DoctypeField — the discriminated union of authoring-time field variants\n// ---------------------------------------------------------------------------\n\n/**\n * A field that holds a scalar value, a link to another record, or a select choice.\n * The most common kind of field. `component` determines how it renders; the attributes below\n * carry everything else that is not a rendering concern.\n * @public\n */\nexport interface ValueField {\n\t/** Discriminator — identifies this as a value-holding field */\n\tkind: 'field'\n\t/** Unique identifier for this field within its doctype */\n\tfieldname: string\n\t/**\n\t * Vue component that renders this field — the primary (and only) rendering axis. Required:\n\t * there is nothing left to derive it from, and a field without one has nothing to render it.\n\t * Any string is valid; naming a custom component is how an app renders a field Stonecrop\n\t * ships no widget for. See `CANONICAL_COMPONENTS` for the set Stonecrop provides.\n\t */\n\tcomponent: string\n\t/** True for the field that identifies the record's primary-key column. */\n\tprimaryKey?: boolean\n\t/** True for a computed/display field with no backing DB column — excluded from SQL SELECT. */\n\tcomputed?: boolean\n\t/** Editor language for code fields (e.g. `'json'`, `'typescript'`) — the only thing distinguishing\n\t * a JSON editor from a code editor, since both render with `ACodeEditor`. */\n\tlanguage?: string\n\t/**\n\t * Target doctype slug. Presence is what makes a field a link.\n\t *\n\t * How it renders is decided by `component`, not by this: `AFormLink` renders an\n\t * inline id-picker, while `AForm`/`ATable` expand the target (see `linkRenderMode`). Expansion\n\t * metadata — backlink, fetch strategy, authoritative cardinality — lives in the doctype's\n\t * `links` map, which is additive and never required for a plain foreign key.\n\t */\n\tdoctype?: string\n\t/** Human-readable label */\n\tlabel?: string\n\t/** CSS width (e.g. `\"40ch\"`, `\"200px\"`) */\n\twidth?: string\n\t/** CSS height (e.g. `\"100%\"`, `\"40vh\"`) — used by full-viewport fields such as Planner */\n\theight?: string\n\t/** Text alignment */\n\talign?: 'left' | 'center' | 'right' | 'start' | 'end'\n\t/** Whether the field is editable in table cell context */\n\tedit?: boolean\n\t/** Input mask pattern or serialized function */\n\tmask?: string\n\t/** Serialized display formatter — distinct from `mask` (input). Spreads through\n\t * `schemaToColumns` to `ColumnSchema.format`; deserialized at render time by ATable's\n\t * `getFormattedValue`. Returns a plain string, HTML, or a {@link BadgeDescriptor} for badge\n\t * cells. When a descriptor is returned it wins over any badge map on `options`. */\n\tformat?: string\n\t/** Per-field interaction mode override */\n\tmode?: InteractionMode\n\t/** Type-specific options: Select choices, Decimal precision config, etc. A link's target is not\n\t * here — it is `doctype`. */\n\toptions?: FieldOptions\n\t/** Whether the field is required */\n\trequired?: boolean\n\t/** Whether the field is read-only */\n\treadOnly?: boolean\n\t/** Whether the field is hidden from the UI */\n\thidden?: boolean\n\t/** Default value for new records */\n\tdefault?: unknown\n\t/** Validation configuration */\n\tvalidation?: FieldValidation\n\t/** Cardinality for Link fields — authoritative value on LinkDeclaration takes precedence */\n\tcardinality?: 'atMostOne' | 'one' | 'noneOrMany' | 'atLeastOne'\n\t/**\n\t * Provenance marker — stamped only by the GraphQL converter; absence means hand-authored.\n\t * When present, the docbuilder freezes the field's identity set (`fieldname`, `primaryKey`,\n\t * `required`, `options`, `cardinality`, `doctype`), since `fieldname` is the GraphQL/column\n\t * binding and `doctype` is the FK's target. `component` is deliberately **not** frozen: it\n\t * chooses the widget, which is an authoring decision the database has no opinion about.\n\t */\n\tsource?: 'introspected'\n\t/** View configuration when this link field expands to a table (`ATable`). */\n\tconfig?: TableViewConfig\n}\n\n/**\n * A layout container that groups other fields. Resolves to a nested AForm.\n * @public\n */\nexport interface FieldsetField {\n\t/** Discriminator — identifies this as a fieldset container */\n\tkind: 'fieldset'\n\t/** Unique identifier for this fieldset within its doctype */\n\tfieldname: string\n\t/** Vue component to render this fieldset. Defaults to `'AFieldset'` in resolveSchema. */\n\tcomponent?: string\n\t/** Human-readable label for the fieldset legend */\n\tlabel?: string\n\t/** Whether the fieldset can be collapsed */\n\tcollapsible?: boolean\n\t/** Interaction mode for all children inside this fieldset */\n\tmode?: InteractionMode\n\t/** Nested field definitions — resolved recursively by resolveSchema */\n\tschema: DoctypeField[]\n}\n\n/**\n * An inline table whose columns are defined directly in the schema (no linked doctype).\n * Use when the table data does not warrant a separate doctype.\n * @public\n */\nexport interface TableField {\n\t/** Discriminator — identifies this as an inline table */\n\tkind: 'table'\n\t/** Unique identifier for this table within its doctype */\n\tfieldname: string\n\t/** Vue component to render this table. Defaults to `'ATable'` in resolveSchema. */\n\tcomponent?: string\n\t/** Human-readable label */\n\tlabel?: string\n\t/** Column definitions — use ColumnSchema (fieldname key) from \\@stonecrop/schema */\n\tcolumns: ColumnSchema[]\n\t/** View configuration — defaults to `{ view: 'list' }` in resolveSchema when absent */\n\tconfig?: TableViewConfig\n\t/** Interaction mode for all cells inside this table */\n\tmode?: InteractionMode\n}\n\n/**\n * Union of all authoring-time field variants.\n * Use `kind` to discriminate: `'field'` | `'fieldset'` | `'table'`.\n * @public\n */\nexport type DoctypeField = ValueField | FieldsetField | TableField\n\n// ---------------------------------------------------------------------------\n// Zod runtime validation schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Which of the three field shapes an entry has, read from the entry's own structure.\n *\n * The single definition of that question. It had three copies before this — the parser's\n * `injectKind`, {@link stripFieldKind}'s agreement check, and the docbuilder's own\n * `isValueField` in another package — each free to drift, and drift here re-types a field rather\n * than throwing: a value field read as a fieldset loses its column, a fieldset read as a value\n * field loses every child.\n *\n * Deliberately **shape-only**: a declared `kind` is ignored. Two callers depend on that. The\n * stripper compares this against the declaration to decide whether removing it is lossless, which\n * it cannot do if this honours it. The docbuilder reads raw JSON off disk and classifies entries to\n * decide which to render as editable rows — and `kind` is Stonecrop's own discriminant, not\n * something a doctype author writes, so a tool reading a file has no business consulting it.\n *\n * `injectKind` is the one place a declaration still wins, and only to leave an already-parsed\n * object untouched on its way back through.\n *\n * @param field - a field entry, authored or parsed\n * @returns the kind its shape implies\n * @public\n */\nexport function inferFieldKind(field: unknown): DoctypeField['kind'] {\n\tif (typeof field !== 'object' || field === null || Array.isArray(field)) return 'field'\n\tif ('schema' in field) return 'fieldset'\n\tif ('columns' in field) return 'table'\n\treturn 'field'\n}\n\n/**\n * Infers the `kind` discriminant from the structural properties of a raw field\n * object, then injects it if absent. This allows authored JSON to omit `kind`\n * entirely — a `schema` key means fieldset, `columns` means table, anything else\n * is a value field.\n *\n * Rules (applied in order):\n * has `schema` → fieldset\n * has `columns` → table\n * otherwise → field (value-holding scalar or link)\n *\n * Objects that already carry `kind` pass through unchanged (backward-compatible).\n *\n * Single-node only. Zod applies this at every level of the discriminated union (via the\n * `z.lazy` in the fieldset schema), so nested fieldset children are normalized during a\n * parse. Callers that bypass Zod — notably `Doctype.fromObject` — must use the exported\n * {@link normalizeFieldKind} instead, which replicates that recursion.\n */\nfunction injectKind(data: unknown): unknown {\n\tif (typeof data !== 'object' || data === null || Array.isArray(data)) return data\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- safe: non-null, non-array object verified by guards above\n\tconst obj = data as Record<string, unknown>\n\t// An explicit `kind` is left exactly as it was found: this is the one place a declaration still\n\t// beats the shape, and only so an already-parsed object survives a second pass unchanged.\n\t// Returning `data` itself rather than rebuilding it also preserves identity and key order.\n\t// Nothing outside this function shares that precedence — a reader classifying a file on disk\n\t// wants `inferFieldKind`, because `kind` is ours and no author writes it.\n\tif ('kind' in obj) return data\n\treturn { kind: inferFieldKind(obj), ...obj }\n}\n\n/**\n * Recursively injects the `kind` discriminant into a raw field object and, for fieldsets,\n * into each of its nested `schema` children — mirroring exactly what Zod's `preprocess`\n * does at every level of the discriminated union.\n *\n * Table `columns` are {@link ColumnSchema} entries, not `DoctypeField`s, so they are left\n * untouched — the Zod table schema validates them with a plain passthrough and never injects\n * `kind` there either.\n *\n * Needed because `Doctype.fromObject` constructs a Doctype without running Zod, yet the\n * registry's `resolveFields` gates link and fieldset handling on `field.kind`. Without this,\n * a JSON-authored link resolves to a flat scalar and a fieldset's children are dropped.\n *\n * @public\n */\nexport function normalizeFieldKind(field: unknown): unknown {\n\tconst injected = injectKind(field)\n\tif (typeof injected !== 'object' || injected === null) return injected\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- injectKind returns a non-null object for object input; guarded above\n\tconst obj = injected as Record<string, unknown>\n\tif (obj.kind === 'fieldset' && Array.isArray(obj.schema)) {\n\t\treturn { ...obj, schema: obj.schema.map(normalizeFieldKind) }\n\t}\n\treturn injected\n}\n\n/**\n * Remove the `kind` discriminant from a field, recursing into a fieldset's children.\n *\n * The outbound half of the boundary {@link normalizeFieldKind} owns inbound. `kind` is a\n * discriminated-union tag the parser synthesizes, not something an author writes, so nothing that\n * *writes* a doctype should put it on disk — the generator and the docbuilder's save both call\n * this. Without it the two round-trip asymmetrically: every save adds a key the file never had.\n *\n * Strips only when `injectKind` would restore exactly what was removed. A fieldset carrying no\n * `schema` re-infers as a plain field, so its `kind` is kept rather than silently re-typing the\n * document; `DoctypeMeta` requires `schema` on a fieldset, so that shape is already invalid and\n * belongs to the load gate, not here.\n *\n * Table `columns` are {@link ColumnSchema} entries rather than `DoctypeField`s and never carry an\n * injected `kind`, so they are passed through untouched — the same asymmetry `injectKind` has.\n *\n * @param field - a field object, as held in memory after parsing\n * @returns the field without `kind`, safe to serialize\n * @public\n */\nexport function stripFieldKind(field: unknown): unknown {\n\tif (typeof field !== 'object' || field === null || Array.isArray(field)) return field\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- safe: non-null, non-array object verified by the guard above\n\tconst obj = field as Record<string, unknown>\n\n\tif (obj.kind !== undefined && obj.kind !== inferFieldKind(obj)) return field\n\n\tconst { kind: _kind, ...rest } = obj\n\tif (Array.isArray(rest.schema)) {\n\t\treturn { ...rest, schema: rest.schema.map(stripFieldKind) }\n\t}\n\treturn rest\n}\n\n/**\n * The field properties a `source: 'introspected'` marker freezes — the ones the database owns.\n *\n * This is the single definition of the identity set. The docbuilder greys these inputs on an\n * introspected field, and the converter's merge refuses to rewrite them. Stating it twice is how\n * the two drift, so both read this constant.\n *\n * Everything absent from this list is author-owned, `component` most importantly: it chooses the\n * widget, which is an authoring decision the database has no opinion about.\n *\n * @public\n */\nexport const INTROSPECTED_IDENTITY_PROPS = [\n\t'fieldname',\n\t'primaryKey',\n\t'required',\n\t'options',\n\t'cardinality',\n\t'doctype',\n] as const\n\n/**\n * Find the field a doctype marks as its primary key, or `undefined` when none is marked.\n *\n * This is the single definition of \"which field identifies a record\". Both sides depend on it:\n * the middleware builds the SQL identity predicate from it, and the client resolves a record's\n * route/store key from it. Call this; never re-derive the rule at the call site, or the two will\n * drift and the client will key records by a column the server never queried.\n *\n * Two deliberate rules, both matching the shape `primaryKey` actually has:\n * - Fieldset children are **included**, via {@link flattenFields}. A fieldset is layout, not\n * scope: its children are fields of the doctype with columns of their own, which is why the\n * adapter's SELECT already descends and why `getDisplayField` does too. Scanning top level only\n * did not *refuse* a nested declaration — it ignored one, so an author marked identity and\n * nothing honoured it and nothing said so.\n * - The **first** match in document order wins. Identity is single-valued by design — a doctype\n * describes the API surface, and mapping a composite database key onto one identity there is the\n * adapter's job — so a doctype declaring several is malformed rather than composite.\n * `DoctypeMeta` rejects that at the load gate; this stays total for callers holding fields that\n * never went through it.\n *\n * @param fields - the doctype's fields; fieldset children are descended into\n * @returns the primary-key field, or `undefined` for a PK-less doctype\n * @public\n */\nexport function getPrimaryKeyField(fields: readonly DoctypeField[]): ValueField | undefined {\n\treturn flattenFields(fields).find((f): f is ValueField => f.kind === 'field' && Boolean(f.primaryKey))\n}\n\n/**\n * Resolve the field a doctype nominates as its display text, or `undefined` when the nomination\n * does not name a readable column.\n *\n * This is the single definition of \"is this a usable `displayField`\". Both sides depend on it:\n * `DoctypeMeta` refuses a bad nomination at the load gate, and the adapter builds a SELECT from\n * the field it returns. Call this; never re-derive the rule, or the gate and the query will\n * disagree about which nominations are legal — which they did, in both directions at once.\n *\n * Two things disqualify a nomination, and both are the doctype saying so itself:\n * - it names no field at all, fieldset children included\n * - it names a `computed` field, which is declared precisely to state it has no column, so a\n * SELECT built from it would reference a column the database does not have\n *\n * @param fields - the doctype's top-level fields\n * @param displayField - the nominated fieldname\n * @returns the nominated field, or `undefined` when it is not a readable column\n * @public\n */\nexport function getDisplayField(\n\tfields: readonly DoctypeField[],\n\tdisplayField: string | undefined\n): ValueField | undefined {\n\tif (!displayField) return undefined\n\treturn flattenFields(fields).find(\n\t\t(f): f is ValueField => f.kind === 'field' && !f.computed && f.fieldname === displayField\n\t)\n}\n\n/**\n * The name of the field a record is identified by: the declared `primaryKey`, or `id` when the\n * doctype declares none.\n *\n * The `id` fallback is load-bearing, not defensive — a surrogate-key doctype carries an `id`\n * column and marks no primary key, so \"nothing declared\" means `id`, not \"no identity\".\n *\n * This exists because that one-line rule had been restated at four sites — the client's\n * `Doctype.recordIdField`, both nuxt hosts' `recordLookupField`, and the Postgres adapter — and\n * the fourth had omitted the fallback, so a doctype the client keyed by `id` was one the adapter\n * could not look up at all. Call this; a fifth restatement is how they diverge again.\n *\n * The returned name is not guaranteed to be a declared field: a doctype that declares no\n * `primaryKey` and no `id` yields `'id'` regardless. An adapter that must build a SQL predicate\n * from it has to confirm the field exists and say so when it does not, because selecting a column\n * the doctype never declared returns nothing rather than failing.\n *\n * @param fields - the doctype's top-level fields\n * @returns the identifying fieldname\n * @public\n */\nexport function getRecordIdField(fields: readonly DoctypeField[]): string {\n\treturn getPrimaryKeyField(fields)?.fieldname ?? 'id'\n}\n\n/**\n * Resolve a record's identity value using the doctype's declared primary key.\n *\n * Falls back to `record.id` when the doctype declares no `primaryKey`. That fallback is\n * load-bearing, not defensive: surrogate-key doctypes carry an `id` column and never mark a\n * primary key, and PostGraphile renames a single-column `id` PK to `rowId` — so the declared\n * field and `id` are both real sources, in that order.\n *\n * @param fields - the doctype's top-level fields\n * @param record - the record to read the identity from\n * @returns the identity as a string, or `undefined` when neither source yields a usable value\n * @public\n */\nexport function getRecordIdentity(\n\tfields: readonly DoctypeField[],\n\trecord: Record<string, unknown>\n): string | undefined {\n\tconst pkField = getPrimaryKeyField(fields)\n\tconst candidates = pkField ? [record[pkField.fieldname], record.id] : [record.id]\n\n\tfor (const value of candidates) {\n\t\t// Numbers are valid keys (a serial PK); 0 is a legitimate id, so test the type, not truthiness.\n\t\tif (typeof value === 'number') return String(value)\n\t\tif (typeof value === 'string' && value !== '') return value\n\t}\n\treturn undefined\n}\n\nfunction createDoctypeFieldSchemas() {\n\tconst ValueFieldSchema = z\n\t\t.object({\n\t\t\tkind: z.literal('field'),\n\t\t\tfieldname: z.string().min(1),\n\t\t\tcomponent: z.string().min(1),\n\t\t\tprimaryKey: z.boolean().optional(),\n\t\t\tcomputed: z.boolean().optional(),\n\t\t\tlanguage: z.string().optional(),\n\t\t\tdoctype: z.string().min(1).optional(),\n\t\t\tlabel: z.string().optional(),\n\t\t\twidth: z.string().optional(),\n\t\t\theight: z.string().optional(),\n\t\t\talign: z.enum(['left', 'center', 'right', 'start', 'end']).optional(),\n\t\t\tedit: z.boolean().optional(),\n\t\t\tmask: z.string().optional(),\n\t\t\tformat: z.string().optional(),\n\t\t\tmode: z.enum(['edit', 'read', 'display']).optional(),\n\t\t\toptions: FieldOptions.optional(),\n\t\t\trequired: z.boolean().optional(),\n\t\t\treadOnly: z.boolean().optional(),\n\t\t\thidden: z.boolean().optional(),\n\t\t\tdefault: z.unknown().optional(),\n\t\t\tvalidation: FieldValidation.optional(),\n\t\t\tcardinality: z.enum(['atMostOne', 'one', 'noneOrMany', 'atLeastOne']).optional(),\n\t\t\tsource: z.literal('introspected').optional(),\n\t\t\tconfig: TableViewConfig.optional(),\n\t\t})\n\t\t.meta({ title: 'ValueField' })\n\n\tconst TableFieldSchema = z\n\t\t.object({\n\t\t\tkind: z.literal('table'),\n\t\t\tfieldname: z.string().min(1),\n\t\t\tcomponent: z.string().optional(),\n\t\t\tlabel: z.string().optional(),\n\t\t\t// Validates that each column has fieldname; allows all other ColumnSchema properties\n\t\t\tcolumns: z.array(z.object({ fieldname: z.string().min(1) }).passthrough()),\n\t\t\tconfig: TableViewConfig.optional(),\n\t\t\tmode: z.enum(['edit', 'read', 'display']).optional(),\n\t\t})\n\t\t.meta({ title: 'TableField' })\n\n\t// DoctypeFieldSchema must be declared before FieldsetFieldSchema so the z.lazy\n\t// callback can close over it. The placeholder is overwritten below; the callback\n\t// only runs at parse time, after the real discriminated union is assigned.\n\t// See: https://zod.dev/api?id=discriminated-unions#discriminated-unions\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- required by Zod's recursive schema pattern; z.never() placeholder is overwritten before any parse call\n\tlet DoctypeFieldSchema: z.ZodType<DoctypeField> = z.never() as unknown as z.ZodType<DoctypeField>\n\n\t// FieldsetFieldSchema stays as a plain ZodObject (not z.ZodType<T>) so that\n\t// z.discriminatedUnion can inspect its 'kind' discriminant property.\n\tconst FieldsetFieldSchema = z\n\t\t.object({\n\t\t\tkind: z.literal('fieldset'),\n\t\t\tfieldname: z.string().min(1),\n\t\t\tcomponent: z.string().optional(),\n\t\t\tlabel: z.string().optional(),\n\t\t\tcollapsible: z.boolean().optional(),\n\t\t\tmode: z.enum(['edit', 'read', 'display']).optional(),\n\t\t\tschema: z.lazy(() => DoctypeFieldSchema.array()),\n\t\t})\n\t\t.meta({ title: 'FieldsetField' })\n\n\tconst rawUnion = z.discriminatedUnion('kind', [ValueFieldSchema, FieldsetFieldSchema, TableFieldSchema])\n\n\t// Overwrite the placeholder with the preprocessed schema. Because z.lazy captures\n\t// DoctypeFieldSchema by closure reference, the lazy callback in FieldsetFieldSchema\n\t// will resolve to this preprocessed version — so nested fieldsets also inject `kind`.\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- ZodPipe output is DoctypeField; same pattern as the z.never() placeholder above\n\tDoctypeFieldSchema = z.preprocess(injectKind, rawUnion) as unknown as z.ZodType<DoctypeField>\n\n\treturn { ValueFieldSchema, TableFieldSchema, FieldsetFieldSchema, DoctypeFieldSchema }\n}\n\nconst schemas = createDoctypeFieldSchemas()\n\n/**\n * Zod runtime validation schema for ValueField.\n * @public\n */\nexport const ValueFieldSchema = schemas.ValueFieldSchema\n\n/**\n * Zod runtime validation schema for FieldsetField.\n * Recursive — FieldsetField.schema is validated against DoctypeFieldSchema.\n * @public\n */\nexport const FieldsetFieldSchema = schemas.FieldsetFieldSchema\n\n/**\n * Zod runtime validation schema for TableField.\n * @public\n */\nexport const TableFieldSchema = schemas.TableFieldSchema\n\n/**\n * Zod runtime validation schema for the DoctypeField discriminated union.\n * Validates all three field variants: `'field'`, `'fieldset'`, `'table'`.\n * @public\n */\nexport const DoctypeFieldSchema = schemas.DoctypeFieldSchema\n","/**\n * Naming Convention Utilities\n * Converts between various naming conventions (snake_case, camelCase, PascalCase, kebab-case)\n * @packageDocumentation\n */\n\n/**\n * Converts snake_case to camelCase\n * @param snakeCase - Snake case string\n * @returns Camel case string\n * @public\n * @example\n * ```typescript\n * snakeToCamel('user_email') // 'userEmail'\n * snakeToCamel('created_at') // 'createdAt'\n * ```\n */\nexport function snakeToCamel(snakeCase: string): string {\n\treturn snakeCase.replace(/_([a-z])/g, (_: string, letter: string) => letter.toUpperCase())\n}\n\n/**\n * Converts camelCase to snake_case\n * @param camelCase - Camel case string\n * @returns Snake case string\n * @public\n * @example\n * ```typescript\n * camelToSnake('userEmail') // 'user_email'\n * camelToSnake('createdAt') // 'created_at'\n * ```\n */\nexport function camelToSnake(camelCase: string): string {\n\treturn camelCase.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`)\n}\n\n/**\n * Converts snake_case to Title Case label\n * @param snakeCase - Snake case string\n * @returns Title case label\n * @public\n * @example\n * ```typescript\n * snakeToLabel('user_email') // 'User Email'\n * snakeToLabel('first_name') // 'First Name'\n * ```\n */\nexport function snakeToLabel(snakeCase: string): string {\n\treturn snakeCase\n\t\t.split('_')\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(' ')\n}\n\n/**\n * Converts camelCase to Title Case label\n * @param camelCase - Camel case string\n * @returns Title case label\n * @public\n * @example\n * ```typescript\n * camelToLabel('userEmail') // 'User Email'\n * camelToLabel('firstName') // 'First Name'\n * ```\n */\nexport function camelToLabel(camelCase: string): string {\n\tconst withSpaces = camelCase.replace(/([A-Z])/g, ' $1').trim()\n\treturn withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1)\n}\n\n/**\n * Convert table name to PascalCase doctype name\n * @param tableName - SQL table name (snake_case)\n * @returns PascalCase name\n * @public\n */\nexport function toPascalCase(tableName: string): string {\n\treturn tableName\n\t\t.split(/[-_\\s]+/)\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join('')\n}\n\n/**\n * Convert to kebab-case slug\n * @param name - Name to convert\n * @returns kebab-case slug\n * @public\n */\nexport function toSlug(name: string): string {\n\treturn name\n\t\t.replace(/([a-z])([A-Z])/g, '$1-$2')\n\t\t.replace(/[\\s_]+/g, '-')\n\t\t.toLowerCase()\n}\n\n/**\n * Convert PascalCase to snake_case (e.g., for deriving table names from type names)\n * @param pascal - PascalCase string\n * @returns snake_case string\n * @public\n * @example\n * ```typescript\n * pascalToSnake('SalesOrder') // 'sales_order'\n * pascalToSnake('SalesOrderItem') // 'sales_order_item'\n * ```\n */\nexport function pascalToSnake(pascal: string): string {\n\treturn pascal\n\t\t.replace(/([a-z])([A-Z])/g, '$1_$2')\n\t\t.replace(/[\\s-]+/g, '_')\n\t\t.toLowerCase()\n}\n","import { z } from 'zod'\n\nimport { DoctypeFieldSchema, flattenFields, getDisplayField } from './field'\nimport { toSlug } from './naming'\n\n/**\n * Cardinality for relationship links.\n * @public\n */\nexport const Cardinality = z.enum(['atMostOne', 'one', 'noneOrMany', 'atLeastOne']).meta({\n\ttitle: 'Cardinality',\n\tdescription: 'Cardinality for relationship links between doctypes',\n})\n\n/**\n * Cardinality type inferred from Zod schema\n * @public\n */\nexport type Cardinality = z.infer<typeof Cardinality>\n\n/**\n * Serialized function type - a function serialized to a string.\n * Used for custom fetch handlers.\n * @public\n */\nexport type SerializedFunction = string\n\n/**\n * Sync fetch strategy - data is fetched in the initial query.\n * @public\n */\nexport const SyncFetch = z\n\t.object({\n\t\t/** Fetch method type */\n\t\tmethod: z.literal('sync'),\n\t\t/** Optional limit on number of records to fetch */\n\t\tlimit: z.number().int().positive().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'SyncFetch',\n\t\tdescription: 'Sync fetch strategy - data is fetched in the initial query',\n\t})\n\n/**\n * Sync fetch strategy type\n * @public\n */\nexport type SyncFetch = z.infer<typeof SyncFetch>\n\n/**\n * Lazy fetch strategy - data is fetched on demand in a separate query.\n * @public\n */\nexport const LazyFetch = z\n\t.object({\n\t\t/** Fetch method type */\n\t\tmethod: z.literal('lazy'),\n\t})\n\t.meta({\n\t\ttitle: 'LazyFetch',\n\t\tdescription: 'Lazy fetch strategy - data is fetched on demand in a separate query',\n\t})\n\n/**\n * Lazy fetch strategy type\n * @public\n */\nexport type LazyFetch = z.infer<typeof LazyFetch>\n\n/**\n * Custom fetch strategy - uses a custom handler function.\n * @public\n */\nexport const CustomFetch = z\n\t.object({\n\t\t/** Fetch method type */\n\t\tmethod: z.literal('custom'),\n\t\t/** Serialized handler function to invoke */\n\t\thandler: z.string(),\n\t})\n\t.meta({\n\t\ttitle: 'CustomFetch',\n\t\tdescription: 'Custom fetch strategy - uses a custom handler function',\n\t})\n\n/**\n * Custom fetch strategy type\n * @public\n */\nexport type CustomFetch = z.infer<typeof CustomFetch>\n\n/**\n * Fetch strategy for link data loading.\n * - sync: fetched in the initial query\n * - lazy: fetched on demand in a separate query\n * - custom: uses a custom handler function\n * @public\n */\nexport const FetchStrategy = z.discriminatedUnion('method', [SyncFetch, LazyFetch, CustomFetch]).meta({\n\ttitle: 'FetchStrategy',\n\tdescription: 'Fetch strategy for link data loading',\n})\n\n/**\n * Fetch strategy type\n * @public\n */\nexport type FetchStrategy = z.infer<typeof FetchStrategy>\n\n/**\n * Link declaration - describes a relationship from one doctype to another.\n * @public\n */\nexport const LinkDeclaration = z\n\t.object({\n\t\t/** Target doctype slug */\n\t\ttarget: z.string().min(1),\n\n\t\t/** Cardinality of the relationship */\n\t\tcardinality: Cardinality,\n\n\t\t/** Backlink fieldname on the target doctype that points back to this link */\n\t\tbacklink: z.string().optional(),\n\n\t\t/** Override default rendering component (AForm for 1:1, ATable for 1:many) */\n\t\tcomponent: z.string().optional(),\n\n\t\t/** Fieldname of the corresponding Link field in the fields array */\n\t\tfieldname: z.string().min(1).optional(),\n\n\t\t/** Fetch strategy for loading nested data */\n\t\tfetch: FetchStrategy.optional(),\n\n\t\t/** Whether to block workflow actions until nested data is loaded (default: true) */\n\t\tblockWorkflows: z.boolean().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'LinkDeclaration',\n\t\tdescription: 'Declares a relationship from one doctype to another',\n\t})\n\n/**\n * Link declaration type inferred from Zod schema\n * @public\n */\nexport type LinkDeclaration = z.infer<typeof LinkDeclaration>\n\n/**\n * Action definition within a workflow\n * @public\n */\nexport const ActionDefinition = z\n\t.object({\n\t\t/** Display label for the action */\n\t\tlabel: z.string().min(1),\n\n\t\t/** Fields that must have values before action can execute */\n\t\trequiredFields: z.array(z.string()).optional(),\n\n\t\t/** Workflow states where this action is available */\n\t\tallowedStates: z.array(z.string()).optional(),\n\n\t\t/** The state the record transitions to after this action executes */\n\t\tnextState: z.string().optional(),\n\n\t\t/** True for stateless command actions with no workflow effect at all (print, email, etc.) */\n\t\tstateless: z.boolean().optional(),\n\n\t\t/**\n\t\t * True for an internal self-transition: the action runs within the current state without\n\t\t * advancing the workflow (e.g. `save`, which mutates record data but stays put). Scoped by\n\t\t * `allowedStates`, rendered as a self-loop in the graph, and has no `nextState`. Distinct from\n\t\t * `stateless` (which has no workflow presence at all): a self-transition is graph-owned and,\n\t\t * unlike a stateless command, persists record data on dispatch.\n\t\t */\n\t\tselfTransition: z.boolean().optional(),\n\n\t\t/** JS function body stored as a string; executed client-side via AsyncFunction with injected API surface */\n\t\tclientHandler: z.string().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'ActionDefinition',\n\t\tdescription: 'Action definition within a workflow',\n\t})\n\n/**\n * Action definition type inferred from Zod schema\n * @public\n */\nexport type ActionDefinition = z.infer<typeof ActionDefinition>\n\n/**\n * Reactive field-validation trigger — advisory, client-side only.\n *\n * A Trigger is a docbuilder-authored validator: when any field in `on` is edited, its\n * `clientHandler` runs (client-side, no rollback) and may flag a field inline to block save\n * in the UI. It is deliberately a **sibling** to {@link (ActionDefinition:type)}, not a member of it —\n * a reactive validator is not a user-invoked action, so it lives in the `triggers` map on\n * {@link (WorkflowMeta:type)} and never appears to action readers (transition/command dropdowns, the FSM graph).\n *\n * The two bindings are independent: `on` is the fire-set (which fields' edits run it), while the\n * `setError(field, msg)` call inside `clientHandler` chooses which field displays the error.\n * @public\n */\nexport const TriggerDefinition = z\n\t.object({\n\t\t/** Optional display label; the map key is the trigger's identity */\n\t\tlabel: z.string().optional(),\n\n\t\t/** Fieldnames whose edits fire this trigger (fires when any listed field changes) */\n\t\ton: z.array(z.string()),\n\n\t\t/** JS function body stored as a string; run client-side with `{ record, value, setError }`. Advisory. */\n\t\tclientHandler: z.string(),\n\t})\n\t.meta({\n\t\ttitle: 'TriggerDefinition',\n\t\tdescription: 'Reactive field-validation trigger — advisory client-side',\n\t})\n\n/**\n * Trigger definition type inferred from Zod schema\n * @public\n */\nexport type TriggerDefinition = z.infer<typeof TriggerDefinition>\n\n/**\n * Whether a workflow action may run from `currentState`.\n *\n * Single source of truth for the \"is this action available here\" rule, shared by\n * the frontend (`getAvailableTransitions`) and the server-side dispatch guard so\n * the two can never disagree. Empty or absent `allowedStates` means the action is\n * available in ALL states — a plain `allowedStates.includes(currentState)` would\n * wrongly block such actions everywhere.\n *\n * @public\n */\nexport function isActionAllowedInState(action: { allowedStates?: string[] | null }, currentState: string): boolean {\n\tconst allowedStates = action.allowedStates\n\tif (!allowedStates || allowedStates.length === 0) return true\n\treturn allowedStates.includes(currentState)\n}\n\n/**\n * DocBuilder graph layout — node positions for the workflow-state graph, keyed by state name.\n * Pure authoring view-state: persisted in the doctype JSON so an author's manual arrangement\n * survives reloads, but — exactly like {@link (WorkflowMeta:type)}'s `triggers` — it is client-only\n * and never mirrored into the runtime GraphQL SDL (see the WorkflowMeta type in the host SDLs, which\n * expose only `states`/`actions`). The shape mirrors VueFlow's node fields; `position` is the node's\n * canvas coordinate and `targetPosition`/`sourcePosition` are the handle sides.\n * @public\n */\nexport const WorkflowLayout = z.record(\n\tz.string(),\n\tz.object({\n\t\tposition: z.object({ x: z.number(), y: z.number() }).optional(),\n\t\ttargetPosition: z.enum(['left', 'top', 'right', 'bottom']).optional(),\n\t\tsourcePosition: z.enum(['left', 'top', 'right', 'bottom']).optional(),\n\t})\n)\n\n/**\n * Workflow layout type inferred from Zod schema\n * @public\n */\nexport type WorkflowLayout = z.infer<typeof WorkflowLayout>\n\n/**\n * Workflow metadata - states and actions for a doctype\n * @public\n */\nexport const WorkflowMeta = z\n\t.object({\n\t\t/** List of workflow states */\n\t\tstates: z.array(z.string()).optional(),\n\n\t\t/** Actions available in this workflow */\n\t\tactions: z.record(z.string(), ActionDefinition).optional(),\n\n\t\t/** Reactive field-validation triggers (advisory, client-side), keyed by trigger name */\n\t\ttriggers: z.record(z.string(), TriggerDefinition).optional(),\n\n\t\t/**\n\t\t * DocBuilder node positions keyed by state name — authoring view-state. Persisted here so a\n\t\t * doctype author's manual graph arrangement survives reloads; like `triggers`, it is client-only\n\t\t * and never enters the runtime GraphQL SDL. See {@link (WorkflowLayout:variable)}.\n\t\t */\n\t\tlayout: WorkflowLayout.optional(),\n\t})\n\t.meta({\n\t\ttitle: 'WorkflowMeta',\n\t\tdescription: 'Workflow metadata - states and actions for a doctype',\n\t})\n\n/**\n * Workflow metadata type inferred from Zod schema\n * @public\n */\nexport type WorkflowMeta = z.infer<typeof WorkflowMeta>\n\n/**\n * Doctype metadata - complete definition of a doctype\n * @public\n */\nexport const DoctypeMeta = z\n\t.object({\n\t\t/** Display name of the doctype */\n\t\tname: z.string().min(1),\n\n\t\t/** URL-friendly slug (kebab-case) */\n\t\tslug: z.string().min(1).optional(),\n\n\t\t/**\n\t\t * Field on this doctype used when displaying a reference to one of its records.\n\t\t * When a record elsewhere holds an inline foreign key to this doctype, the middleware\n\t\t * returns that field as `{ id, displayText }`, reading `displayText` from this field.\n\t\t */\n\t\tdisplayField: z.string().min(1).optional(),\n\n\t\t/**\n\t\t * URL path this doctype registers at, written literally — `/order` for a collection,\n\t\t * `/order/:id` for a record. Absent means the doctype has no page of its own, which is the\n\t\t * common case: a child table is reached inside its parent, never at a URL.\n\t\t *\n\t\t * A path rather than a segment because the record parameter has to be somewhere, and a host\n\t\t * that reads a bare segment has to know which kind of doctype it is holding to decide where\n\t\t * to put it. Writing it out means nothing downstream re-derives it.\n\t\t */\n\t\troute: z.string().startsWith('/').optional(),\n\n\t\t/** Field definitions (a link field is one carrying `doctype`) */\n\t\tfields: z.array(DoctypeFieldSchema),\n\n\t\t/** Relationship links to other doctypes */\n\t\tlinks: z.record(z.string(), LinkDeclaration).optional(),\n\n\t\t/** Workflow configuration */\n\t\tworkflow: WorkflowMeta.optional(),\n\n\t\t/** Parent doctype for inheritance */\n\t\tinherits: z.string().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'DoctypeMeta',\n\t\tdescription: 'Doctype metadata - complete definition of a doctype',\n\t})\n\t.superRefine((doctype, ctx) => {\n\t\t// A record is identified by exactly one field here, and that is the design rather than a\n\t\t// limitation awaiting composite support. A doctype describes the **API surface** a client\n\t\t// interacts with, not the table behind it; how a composite database key maps onto a single\n\t\t// identity on that surface is the server's business, and the client neither sees nor\n\t\t// encodes the parts. So there is nothing for a doctype-level composite key to express.\n\t\t//\n\t\t// Declaring several is therefore malformed, and silently so: `getPrimaryKeyField` takes the\n\t\t// first match and the rest are ignored, leaving an adapter to key records on a column that\n\t\t// need not be unique — `stonecropRecord`'s row map then keeps whichever row comes last.\n\t\t// Refusing at the gate is what makes it say so.\n\t\t//\n\t\t// Counts the flattened set, because that is the set `getPrimaryKeyField` resolves over. The\n\t\t// two asked different questions while this scanned top level only: a doctype with one key\n\t\t// declared at each level passed the gate and then had one of them silently dropped.\n\t\t//\n\t\t// Zero keys stays legal and is not an omission: a surrogate-key doctype declares none and\n\t\t// resolves through `getRecordIdField`'s documented `id` fallback.\n\t\tconst declared = flattenFields(doctype.fields).filter(f => f.kind === 'field' && f.primaryKey)\n\t\tif (declared.length > 1) {\n\t\t\tctx.addIssue({\n\t\t\t\tcode: 'custom',\n\t\t\t\tpath: ['fields'],\n\t\t\t\tmessage: `Doctype declares ${declared.length} primaryKey fields (${declared\n\t\t\t\t\t.map(f => (f.kind === 'field' ? f.fieldname : ''))\n\t\t\t\t\t.join(\n\t\t\t\t\t\t', '\n\t\t\t\t\t)}); a record is identified by exactly one field. A composite database key is mapped to a single identity by the adapter, so a doctype never declares its parts`,\n\t\t\t})\n\t\t}\n\n\t\t// Through `getDisplayField` rather than a scan written here, because the adapter builds its\n\t\t// SELECT from that same call. The two hand-rolled versions disagreed in both directions at\n\t\t// once: this gate scanned top-level only, so it rejected a fieldset-nested field that would\n\t\t// have worked, while neither side excluded `computed` fields, so a nomination naming one\n\t\t// passed the gate and then failed as a missing column at query time.\n\t\tif (doctype.displayField && !getDisplayField(doctype.fields, doctype.displayField)) {\n\t\t\tconst named = flattenFields(doctype.fields).find(f => f.fieldname === doctype.displayField)\n\t\t\tctx.addIssue({\n\t\t\t\tcode: 'custom',\n\t\t\t\tpath: ['displayField'],\n\t\t\t\tmessage: named\n\t\t\t\t\t? `displayField \"${doctype.displayField}\" names a computed field, which has no column to read a display value from`\n\t\t\t\t\t: `displayField \"${doctype.displayField}\" is not declared on this doctype`,\n\t\t\t})\n\t\t}\n\t})\n\n/**\n * Doctype metadata type inferred from Zod schema\n * @public\n */\nexport type DoctypeMeta = z.infer<typeof DoctypeMeta>\n\n/**\n * The one string a doctype is addressed by.\n *\n * A doctype carries two names — `name` (`OrderItem`) and `slug` (`order-item`) — and every registry\n * must agree on which one keys it. Three implementations had drifted apart: the adapter's registry is\n * keyed by `name` and its `getMeta` also scans for a matching `slug`, so it accepts **either**; the\n * client's registry is keyed by a slug it derives itself and accepts **only** that; and\n * `Doctype.fromObject` dropped an authored `slug` on the floor and re-derived one regardless. The\n * adapter's accepted set was therefore a strict superset of the client's, and a link target written\n * as the Name booted the server, passed its reference check, served rows over GraphQL, and was\n * silently dropped by the client — an expanding child table rendering as one empty text input, with\n * nothing logged.\n *\n * Resolving through this in both runtimes is what makes the two answers the same answer. It is the\n * derivation only; a *lookup* still belongs to whichever registry owns the corpus, because the two\n * corpora legitimately differ (a client registers lazily, and a client-only host has no adapter at\n * all).\n *\n * An authored `slug` wins over the derived one because the authored doctype is the source of truth:\n * generation verifies a file and never overwrites it, so a doctype that states its own slug means it.\n * Deriving unconditionally is what `fromObject` did, and it made an authored `slug` a silent no-op on\n * one side of the wire while the other honoured it.\n *\n * @param doctype - anything carrying a doctype's `name` and optional authored `slug`\n * @returns the canonical slug\n * @public\n *\n * @example\n * ```typescript\n * getDoctypeSlug({ name: 'OrderItem' }) // 'order-item'\n * getDoctypeSlug({ name: 'Planner', slug: 'planner-board' }) // 'planner-board'\n * ```\n */\nexport function getDoctypeSlug(doctype: { name: string; slug?: string }): string {\n\t// `||` rather than `??`: an empty authored slug is not a usable registry key, and this is\n\t// reachable — `Doctype.fromObject` builds a doctype without going through the Zod gate, which is\n\t// where `slug: z.string().min(1)` would have refused it.\n\treturn doctype.slug || toSlug(doctype.name)\n}\n\n/**\n * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.\n *\n * @deprecated The `__display` suffix pattern is no longer used. Inline link fields are enriched\n * server-side by `@stonecrop/graphql-middleware` as `{ id, displayText }` objects on the link\n * field itself.\n * @public\n */\nexport const LINK_DISPLAY_SUFFIX = '__display'\n\n/**\n * Build the payload key for a link field's display text (e.g. `customerId__display`).\n *\n * @deprecated The `__display` suffix pattern is no longer used. Inline link fields are enriched\n * server-side by `@stonecrop/graphql-middleware` as `{ id, displayText }` objects on the link\n * field itself.\n * @public\n */\nexport function linkDisplayFieldname(fieldname: string): string {\n\treturn `${fieldname}${LINK_DISPLAY_SUFFIX}`\n}\n\n/**\n * Context for identifying what doctype/record we're working with.\n * Used by graphql-middleware and graphql-client to resolve schema metadata.\n * @public\n */\nexport interface DoctypeContext {\n\t/** Doctype name (e.g., 'Task', 'Customer') */\n\tdoctype: string\n\t/** Optional record ID for viewing/editing a specific record */\n\trecordId?: string\n\t/** Additional context properties */\n\t[key: string]: unknown\n}\n\n/**\n * Base interface for doctype metadata passed to DataClient methods.\n * Only requires properties needed for record fetching.\n * @public\n */\nexport interface DoctypeRef {\n\t/** Doctype name (e.g., 'Task', 'Customer') */\n\tname: string\n\t/** URL-friendly slug (e.g., 'task', 'customer') */\n\tslug?: string\n}\n\n/**\n * Options for fetching a single record\n * @public\n */\nexport interface GetRecordOptions {\n\t/**\n\t * Include nested link sub-selections.\n\t * - `true`: include all descendant links\n\t * - `string[]`: include only named links\n\t * - `false` / omitted: scalar fields only (default)\n\t */\n\tincludeNested?: boolean | string[]\n\n\t/**\n\t * Maximum depth for recursive sub-selections.\n\t * No default — unlimited when omitted.\n\t */\n\tmaxDepth?: number\n}\n\n/**\n * Options for fetching multiple records\n * @public\n */\nexport interface GetRecordsOptions {\n\t/** Filter expression (field-value pairs) */\n\tfilters?: Record<string, unknown>\n\t/** Order by expression (e.g. 'NAME_ASC') */\n\torderBy?: string\n\t/** Maximum number of records to return */\n\tlimit?: number\n\t/** Number of records to skip */\n\toffset?: number\n\t/**\n\t * Ask the backend for the total matching the filters as well as the page.\n\t *\n\t * Off by default because it costs a second query — a full scan on Postgres — and knowing\n\t * *whether* more exist (`hasMore`) is what a list view actually needs. Turn it on for a\n\t * \"showing 20 of 4,312\" style display.\n\t */\n\tincludeTotal?: boolean\n}\n\n/**\n * Result from getRecord - includes the record data\n * @public\n */\nexport interface GetRecordResult {\n\t/** The record data, or null if not found */\n\trecord: Record<string, unknown> | null\n}\n\n/**\n * Result from getRecords — a page of records, and enough to tell that it is one.\n *\n * A bare array used to be returned here, which claimed to be the whole collection. It is not:\n * a limit always applies, so a caller could not distinguish a complete list from a truncated\n * one. That is the entire reason this type exists.\n *\n * @public\n */\nexport interface GetRecordsResult {\n\t/** The records in this page */\n\tdata: Record<string, unknown>[]\n\t/** Whether the backend holds further records beyond this page */\n\thasMore: boolean\n\t/**\n\t * Total records matching the filters, ignoring limit/offset. Present only when the caller\n\t * asked for it via {@link GetRecordsOptions.includeTotal} — counting is a full scan on most\n\t * backends, so it is never computed speculatively.\n\t */\n\tcount?: number\n}\n\n/**\n * Interface for data clients that fetch doctype metadata and records.\n * Implemented by \\@stonecrop/graphql-client's StonecropClient.\n * Custom implementations can use any backend (REST, local storage, etc.).\n *\n * @typeParam T - Doctype reference type for record operations (defaults to DoctypeRef)\n * @typeParam M - Doctype metadata return type for getMeta (defaults to DoctypeMeta)\n * @public\n */\nexport interface DataClient<T extends DoctypeRef = DoctypeRef, M = DoctypeMeta> {\n\t/**\n\t * Fetch doctype metadata\n\t * @param context - Doctype context identifying the doctype\n\t * @returns Doctype metadata or null if not found\n\t */\n\tgetMeta(context: DoctypeContext): Promise<M | null>\n\n\t/**\n\t * Fetch a single record by ID\n\t *\n\t * When `includeNested` is set, builds a query with sub-selections for descendant\n\t * links and returns ancestor + merged descendants. When omitted, returns flat scalar data.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param recordId - Record ID to fetch\n\t * @param options - Query options\n\t * @returns Record data wrapped in GetRecordResult\n\t */\n\tgetRecord(doctype: T, recordId: string, options?: GetRecordOptions): Promise<GetRecordResult>\n\n\t/**\n\t * Fetch a page of records\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param options - Query options\n\t * @returns The page, plus whether more exist and (on request) the total\n\t */\n\tgetRecords(doctype: T, options?: GetRecordsOptions): Promise<GetRecordsResult>\n\n\t/**\n\t * Execute a doctype action (e.g., SUBMIT, APPROVE, save).\n\t * All state changes flow through this single mutation endpoint.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param action - Action name to execute (e.g., 'SUBMIT', 'APPROVE', 'save')\n\t * @param args - Action arguments (typically record ID and/or form data)\n\t * @returns Action result with success status, response data, and any error\n\t */\n\trunAction(\n\t\tdoctype: T,\n\t\taction: string,\n\t\targs?: unknown[]\n\t): Promise<{ success: boolean; data: unknown; error: string | null }>\n}\n","import { DoctypeFieldSchema } from './field'\nimport { DoctypeMeta } from './doctype'\n\n/**\n * Validation error with path information\n * @public\n */\nexport interface ValidationError {\n\t/** Path to the invalid property */\n\tpath: PropertyKey[]\n\n\t/** Error message */\n\tmessage: string\n}\n\n/**\n * Result of a validation operation\n * @public\n */\nexport interface ValidationResult {\n\t/** Whether validation passed */\n\tsuccess: boolean\n\n\t/** List of validation errors (empty if success) */\n\terrors: ValidationError[]\n}\n\n/**\n * Validate a field definition against the DoctypeField discriminated union\n * @param data - Data to validate\n * @returns Validation result\n * @public\n */\nexport function validateField(data: unknown): ValidationResult {\n\tconst result = DoctypeFieldSchema.safeParse(data)\n\n\tif (result.success) {\n\t\treturn { success: true, errors: [] }\n\t}\n\n\treturn {\n\t\tsuccess: false,\n\t\terrors: result.error.issues.map(issue => ({\n\t\t\tpath: issue.path,\n\t\t\tmessage: issue.message,\n\t\t})),\n\t}\n}\n\n/**\n * Validate a doctype definition\n * @param data - Data to validate\n * @returns Validation result\n * @public\n */\nexport function validateDoctype(data: unknown): ValidationResult {\n\tconst result = DoctypeMeta.safeParse(data)\n\n\tif (result.success) {\n\t\treturn { success: true, errors: [] }\n\t}\n\n\treturn {\n\t\tsuccess: false,\n\t\terrors: result.error.issues.map(issue => ({\n\t\t\tpath: issue.path,\n\t\t\tmessage: issue.message,\n\t\t})),\n\t}\n}\n\n/**\n * Parse and validate a field, throwing on failure\n * @param data - Data to parse\n * @returns Validated DoctypeField\n * @throws ZodError if validation fails\n * @public\n */\nexport function parseField(data: unknown): import('./field').DoctypeField {\n\treturn DoctypeFieldSchema.parse(data)\n}\n\n/**\n * Parse and validate a doctype, throwing on failure\n * @param data - Data to parse\n * @returns Validated DoctypeMeta\n * @throws ZodError if validation fails\n * @public\n */\nexport function parseDoctype(data: unknown): DoctypeMeta {\n\treturn DoctypeMeta.parse(data)\n}\n\n// Re-export types for convenience\nexport type { DoctypeField, ValueField, FieldsetField, TableField } from './field'\nexport type { DoctypeMeta } from './doctype'\n","/**\n * GraphQL Scalar Type Mappings\n *\n * Maps standard GraphQL scalars and well-known custom scalars to Stonecrop field types.\n * Source-agnostic — covers scalars commonly emitted by PostGraphile, Hasura, Apollo, etc.\n *\n * Users can extend these via the `customScalars` option in `GraphQLConversionOptions`.\n *\n * @packageDocumentation\n */\n\nimport type { FieldTemplate } from './types'\n\n/**\n * Mapping from standard GraphQL scalar types to Stonecrop field types.\n * These are defined by the GraphQL specification and are always available.\n *\n * @public\n */\nexport const GQL_SCALAR_MAP: Record<string, FieldTemplate> = {\n\tString: { component: 'ATextInput' },\n\tInt: { component: 'ANumericInput' },\n\tFloat: { component: 'ANumericInput' },\n\tBoolean: { component: 'ACheckbox' },\n\tID: { component: 'ATextInput' },\n}\n\n/**\n * Mapping from well-known custom GraphQL scalars to Stonecrop field types.\n * These cover scalars commonly used across GraphQL servers (PostGraphile, Hasura, etc.)\n * without baking in knowledge of any specific server.\n *\n * Entries here have lower precedence than `customScalars` from options, but higher\n * precedence than unknown/unmapped scalars.\n *\n * @public\n */\nexport const WELL_KNOWN_SCALARS: Record<string, FieldTemplate> = {\n\t// Arbitrary precision / large numbers — all numeric variants render with ANumericInput.\n\tBigFloat: { component: 'ANumericInput' },\n\tBigDecimal: { component: 'ANumericInput' },\n\tDecimal: { component: 'ANumericInput' },\n\tBigInt: { component: 'ANumericInput' },\n\tLong: { component: 'ANumericInput' },\n\n\t// Identifiers\n\tUUID: { component: 'ATextInput' },\n\n\t// Date / Time — no dedicated Time SFC exists; Time falls back to a plain text input.\n\tDateTime: { component: 'ADateTime' },\n\tDatetime: { component: 'ADateTime' },\n\tDate: { component: 'ADate' },\n\tTime: { component: 'ATextInput' },\n\tInterval: { component: 'ADuration' },\n\tDuration: { component: 'ADuration' },\n\n\t// Structured data\n\tJSON: { component: 'ACodeEditor' },\n\tJSONObject: { component: 'ACodeEditor' },\n\tJsonNode: { component: 'ACodeEditor' },\n}\n\n/**\n * Set of scalar type names that are internal to GraphQL servers and should be skipped\n * during field conversion (they don't represent meaningful data fields).\n *\n * @public\n */\nexport const INTERNAL_SCALARS = new Set(['Cursor'])\n\n/**\n * Build a merged scalar map from the built-in maps and user-provided custom scalars.\n * Precedence (highest to lowest): customScalars → GQL_SCALAR_MAP → WELL_KNOWN_SCALARS\n *\n * @param customScalars - User-provided scalar overrides\n * @returns Merged scalar map\n * @public\n */\nexport function buildScalarMap(customScalars?: Record<string, Partial<FieldTemplate>>): Record<string, FieldTemplate> {\n\tconst merged: Record<string, FieldTemplate> = { ...WELL_KNOWN_SCALARS }\n\n\t// Standard scalars override well-known\n\tfor (const [key, value] of Object.entries(GQL_SCALAR_MAP)) {\n\t\tmerged[key] = value\n\t}\n\n\t// Custom scalars override everything\n\tif (customScalars) {\n\t\tfor (const [key, value] of Object.entries(customScalars)) {\n\t\t\tmerged[key] = { component: value.component ?? 'ATextInput' }\n\t\t}\n\t}\n\n\treturn merged\n}\n","/**\n * Default heuristics for identifying entity types and fields in a GraphQL schema.\n *\n * These heuristics work across common GraphQL servers (PostGraphile, Hasura, Apollo, etc.)\n * by detecting widely-adopted conventions like the Relay connection pattern.\n *\n * All heuristics can be overridden via the `isEntityType`, `isEntityField`, and\n * `classifyField` options in `GraphQLConversionOptions`.\n *\n * @packageDocumentation\n */\n\nimport {\n\tisScalarType,\n\tisEnumType,\n\tisObjectType,\n\tisListType,\n\tisNonNullType,\n\tisNamedType,\n\ttype GraphQLObjectType,\n\ttype GraphQLField,\n\ttype GraphQLOutputType,\n\ttype GraphQLNamedType,\n} from 'graphql'\n\nimport type { FieldTemplate } from './types'\nimport type { GraphQLConversionFieldMeta, GraphQLConversionOptions } from './types'\nimport { buildScalarMap, INTERNAL_SCALARS } from './scalars'\nimport { toSlug, camelToLabel, toPascalCase } from '../naming'\n\n/**\n * Suffixes that identify synthetic/framework types generated by GraphQL servers.\n * Types ending with these suffixes are typically not entities.\n */\nconst SYNTHETIC_SUFFIXES = [\n\t'Connection',\n\t'Edge',\n\t'Input',\n\t'Patch',\n\t'Payload',\n\t'Condition',\n\t'Filter',\n\t'OrderBy',\n\t'Aggregate',\n\t'AggregateResult',\n\t'AggregateFilter',\n\t'DeleteResponse',\n\t'InsertResponse',\n\t'UpdateResponse',\n\t'MutationResponse',\n]\n\n/**\n * Root operation type names that are never entities.\n */\nconst ROOT_TYPE_NAMES = new Set(['Query', 'Mutation', 'Subscription'])\n\n/**\n * Default heuristic to determine if a GraphQL object type represents an entity.\n * An entity type becomes a Stonecrop doctype.\n *\n * This heuristic excludes:\n * - Introspection types (`__*`)\n * - Root operation types (`Query`, `Mutation`, `Subscription`)\n * - Types with synthetic suffixes (e.g., `*Connection`, `*Edge`, `*Input`)\n * - Types starting with `Node` interface marker (exact match only)\n *\n * @param typeName - The GraphQL type name\n * @param type - The GraphQL object type definition\n * @returns `true` if this type should become a Stonecrop doctype\n * @public\n */\nexport function defaultIsEntityType(typeName: string, type: GraphQLObjectType): boolean {\n\t// Exclude introspection types\n\tif (typeName.startsWith('__')) {\n\t\treturn false\n\t}\n\n\t// Exclude root operation types\n\tif (ROOT_TYPE_NAMES.has(typeName)) {\n\t\treturn false\n\t}\n\n\t// Exclude the Node interface marker type\n\tif (typeName === 'Node') {\n\t\treturn false\n\t}\n\n\t// Exclude types matching synthetic suffixes\n\tfor (const suffix of SYNTHETIC_SUFFIXES) {\n\t\tif (typeName.endsWith(suffix)) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t// Must have at least one field\n\tconst fields = type.getFields()\n\tif (Object.keys(fields).length === 0) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n/**\n * Fields to skip by default on entity types.\n * These are internal to GraphQL servers and don't represent semantic data.\n *\n * Relay's global object identifier is deliberately absent: which field carries it is a\n * declaration, not a name. See {@link relayNodeIdField}.\n */\nconst SKIP_FIELDS = new Set(['__typename', 'clientMutationId'])\n\n/**\n * The name Relay's Object Identification spec gives its marker interface.\n */\nconst RELAY_NODE_INTERFACE = 'Node'\n\n/**\n * The field carrying Relay's global object identifier on this type, or `undefined` for a type that\n * declares none.\n *\n * Read off the interface rather than matched against a list of names, because the name is a server\n * setting: PostGraphile exposes it as `nodeIdFieldName`, which is `id` under the un-overridden\n * Amber preset, `nodeId` under Stonecrop's, and whatever a foreign host chose under theirs. A\n * hardcoded name is a snapshot of one of those, and gets it wrong in both directions at once — it\n * emits an opaque identifier as a column (whose every read then fails on a column that does not\n * exist), and drops a real column that happens to share the name.\n *\n * The interface must be Relay's marker and not a domain interface that shares its name, so it has\n * to declare exactly one field, a non-null `ID`, and nothing else — anything carrying domain fields\n * is a different interface, and skipping against it would drop real columns.\n *\n * @internal\n */\nfunction relayNodeIdField(type: GraphQLObjectType): string | undefined {\n\tfor (const iface of type.getInterfaces()) {\n\t\tif (iface.name !== RELAY_NODE_INTERFACE) continue\n\n\t\tconst declared = Object.values(iface.getFields())\n\t\tif (declared.length !== 1) continue\n\n\t\tconst { namedType, required, isList } = unwrapType(declared[0].type)\n\t\tif (required && !isList && namedType.name === 'ID') return declared[0].name\n\t}\n\n\treturn undefined\n}\n\n/**\n * Default heuristic to filter fields on entity types.\n * Skips internal fields that don't represent meaningful data.\n *\n * @param fieldName - The GraphQL field name\n * @param _field - The GraphQL field definition (unused in default implementation)\n * @param parentType - The parent entity type, whose interfaces declare its Relay identifier\n * @returns `true` if this field should be included\n * @public\n */\nexport function defaultIsEntityField(\n\tfieldName: string,\n\t_field: GraphQLField<unknown, unknown>,\n\tparentType: GraphQLObjectType\n): boolean {\n\tif (SKIP_FIELDS.has(fieldName)) return false\n\treturn fieldName !== relayNodeIdField(parentType)\n}\n\n/**\n * Unwrap NonNull and List wrappers from a GraphQL type, tracking nullability.\n *\n * @param type - The GraphQL output type\n * @returns The unwrapped named type, whether it's required, and whether it's a list\n * @internal\n */\nfunction unwrapType(type: GraphQLOutputType): {\n\tnamedType: GraphQLNamedType\n\trequired: boolean\n\tisList: boolean\n} {\n\tlet required = false\n\tlet isList = false\n\tlet current: GraphQLOutputType = type\n\n\t// Unwrap outer NonNull\n\tif (isNonNullType(current)) {\n\t\trequired = true\n\t\tcurrent = current.ofType\n\t}\n\n\t// Unwrap List\n\tif (isListType(current)) {\n\t\tisList = true\n\t\tcurrent = current.ofType\n\n\t\t// Unwrap inner NonNull (e.g., [Type!])\n\t\tif (isNonNullType(current)) {\n\t\t\tcurrent = current.ofType\n\t\t}\n\t}\n\n\t// At this point, current should be a named type (scalar, enum, or object)\n\tif (!isNamedType(current)) {\n\t\tthrow new Error(`Expected a named GraphQL type, got: ${String(current)}`)\n\t}\n\treturn { namedType: current, required, isList }\n}\n\n/**\n * Check if a GraphQL object type looks like a Relay Connection type.\n * A connection type has an `edges` field returning a list of edge types,\n * where each edge has a `node` field.\n *\n * @param type - The GraphQL object type to check\n * @returns The node type name if this is a connection, or `undefined`\n * @internal\n */\nfunction getConnectionNodeType(type: GraphQLObjectType): string | undefined {\n\tconst fields = type.getFields()\n\n\t// Must have an 'edges' field\n\tconst edgesField = fields['edges']\n\tif (!edgesField) return undefined\n\n\t// edges must be a list\n\tconst { namedType: edgesType, isList: edgesIsList } = unwrapType(edgesField.type)\n\tif (!edgesIsList || !isObjectType(edgesType)) return undefined\n\n\t// Each edge must have a 'node' field\n\tconst edgeFields = edgesType.getFields()\n\tconst nodeField = edgeFields['node']\n\tif (!nodeField) return undefined\n\n\tconst { namedType: nodeType } = unwrapType(nodeField.type)\n\tif (!isObjectType(nodeType)) return undefined\n\n\treturn nodeType.name\n}\n\n/**\n * Classify a single GraphQL field into a Stonecrop field definition.\n *\n * Classification rules (in order):\n * 1. Scalar types → look up in merged scalar map\n * 2. Enum types → `Select` with enum values as options\n * 3. Object types that are entities → `Link` with slug as options\n * 4. Object types that are Connections → `Doctype` with node type slug as options\n * 5. List of entity type → `Doctype` with item type slug as options\n * 6. Anything else → `Data` with `_unmapped: true`\n *\n * @param fieldName - The GraphQL field name\n * @param field - The GraphQL field definition\n * @param entityTypes - Set of type names classified as entities\n * @param options - Conversion options (for custom scalars, unmapped meta, etc.)\n * @returns The Stonecrop field definition\n * @public\n */\nexport function classifyFieldType(\n\tfieldName: string,\n\tfield: GraphQLField<unknown, unknown>,\n\tentityTypes: Set<string>,\n\toptions: GraphQLConversionOptions = {}\n): GraphQLConversionFieldMeta {\n\tconst { namedType, required, isList } = unwrapType(field.type)\n\tconst scalarMap = buildScalarMap(options.customScalars)\n\n\tconst base: GraphQLConversionFieldMeta = {\n\t\tkind: 'field',\n\t\tfieldname: fieldName,\n\t\tlabel: camelToLabel(fieldName),\n\t\tcomponent: 'ATextInput',\n\t}\n\n\tif (required) {\n\t\tbase.required = true\n\t}\n\n\t// 1. Scalar types\n\tif (isScalarType(namedType)) {\n\t\t// Skip internal scalars (e.g., Cursor)\n\t\tif (INTERNAL_SCALARS.has(namedType.name)) {\n\t\t\tbase._unmapped = true\n\t\t\tif (options.includeUnmappedMeta) {\n\t\t\t\tbase._graphqlType = namedType.name\n\t\t\t}\n\t\t\treturn base\n\t\t}\n\n\t\t// Special case: ID fields that reference an entity type → Link\n\t\tif (namedType.name === 'ID') {\n\t\t\tconst candidateTypeName = toPascalCase(fieldName)\n\t\t\tif (entityTypes.has(candidateTypeName)) {\n\t\t\t\tbase.component = 'AFormLink'\n\t\t\t\tbase.doctype = toSlug(candidateTypeName)\n\t\t\t\treturn base\n\t\t\t}\n\t\t}\n\n\t\tconst template: FieldTemplate | undefined = scalarMap[namedType.name]\n\t\tif (template) {\n\t\t\tbase.component = template.component\n\t\t} else {\n\t\t\t// Unknown scalar — default to Data with unmapped marker\n\t\t\tbase._unmapped = true\n\t\t\tif (options.includeUnmappedMeta) {\n\t\t\t\tbase._graphqlType = namedType.name\n\t\t\t}\n\t\t}\n\t\treturn base\n\t}\n\n\t// 2. Enum types → Select\n\tif (isEnumType(namedType)) {\n\t\tbase.component = 'ADropdown'\n\t\tbase.options = namedType.getValues().map(v => v.name)\n\t\treturn base\n\t}\n\n\t// 3–5. Object types\n\tif (isObjectType(namedType)) {\n\t\t// 3. Direct reference to an entity type → Link\n\t\tif (!isList && entityTypes.has(namedType.name)) {\n\t\t\tbase.component = 'AFormLink'\n\t\t\tbase.doctype = toSlug(namedType.name)\n\t\t\treturn base\n\t\t}\n\n\t\t// 4. Connection type → link (child table)\n\t\tconst connectionNodeTypeName = getConnectionNodeType(namedType)\n\t\tif (connectionNodeTypeName && entityTypes.has(connectionNodeTypeName)) {\n\t\t\tbase.component = 'ATable'\n\t\t\tbase._isLink = true\n\t\t\tbase.doctype = toSlug(connectionNodeTypeName)\n\t\t\tbase.cardinality = 'noneOrMany'\n\t\t\treturn base\n\t\t}\n\n\t\t// 5. List of entity type → link\n\t\tif (isList && entityTypes.has(namedType.name)) {\n\t\t\tbase.component = 'ATable'\n\t\t\tbase._isLink = true\n\t\t\tbase.doctype = toSlug(namedType.name)\n\t\t\tbase.cardinality = 'noneOrMany'\n\t\t\treturn base\n\t\t}\n\n\t\t// Unknown object type — mark as unmapped\n\t\tbase._unmapped = true\n\t\tif (options.includeUnmappedMeta) {\n\t\t\tbase._graphqlType = namedType.name\n\t\t}\n\t\treturn base\n\t}\n\n\t// Fallback — shouldn't normally be reached\n\tbase._unmapped = true\n\tif (options.includeUnmappedMeta) {\n\t\tbase._graphqlType = namedType.name\n\t}\n\treturn base\n}\n","/* global define */\n\n(function (root, pluralize) {\n /* istanbul ignore else */\n if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {\n // Node.\n module.exports = pluralize();\n } else if (typeof define === 'function' && define.amd) {\n // AMD, registers as an anonymous module.\n define(function () {\n return pluralize();\n });\n } else {\n // Browser global.\n root.pluralize = pluralize();\n }\n})(this, function () {\n // Rule storage - pluralize and singularize need to be run sequentially,\n // while other rules can be optimized using an object for instant lookups.\n var pluralRules = [];\n var singularRules = [];\n var uncountables = {};\n var irregularPlurals = {};\n var irregularSingles = {};\n\n /**\n * Sanitize a pluralization rule to a usable regular expression.\n *\n * @param {(RegExp|string)} rule\n * @return {RegExp}\n */\n function sanitizeRule (rule) {\n if (typeof rule === 'string') {\n return new RegExp('^' + rule + '$', 'i');\n }\n\n return rule;\n }\n\n /**\n * Pass in a word token to produce a function that can replicate the case on\n * another word.\n *\n * @param {string} word\n * @param {string} token\n * @return {Function}\n */\n function restoreCase (word, token) {\n // Tokens are an exact match.\n if (word === token) return token;\n\n // Lower cased words. E.g. \"hello\".\n if (word === word.toLowerCase()) return token.toLowerCase();\n\n // Upper cased words. E.g. \"WHISKY\".\n if (word === word.toUpperCase()) return token.toUpperCase();\n\n // Title cased words. E.g. \"Title\".\n if (word[0] === word[0].toUpperCase()) {\n return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase();\n }\n\n // Lower cased words. E.g. \"test\".\n return token.toLowerCase();\n }\n\n /**\n * Interpolate a regexp string.\n *\n * @param {string} str\n * @param {Array} args\n * @return {string}\n */\n function interpolate (str, args) {\n return str.replace(/\\$(\\d{1,2})/g, function (match, index) {\n return args[index] || '';\n });\n }\n\n /**\n * Replace a word using a rule.\n *\n * @param {string} word\n * @param {Array} rule\n * @return {string}\n */\n function replace (word, rule) {\n return word.replace(rule[0], function (match, index) {\n var result = interpolate(rule[1], arguments);\n\n if (match === '') {\n return restoreCase(word[index - 1], result);\n }\n\n return restoreCase(match, result);\n });\n }\n\n /**\n * Sanitize a word by passing in the word and sanitization rules.\n *\n * @param {string} token\n * @param {string} word\n * @param {Array} rules\n * @return {string}\n */\n function sanitizeWord (token, word, rules) {\n // Empty string or doesn't need fixing.\n if (!token.length || uncountables.hasOwnProperty(token)) {\n return word;\n }\n\n var len = rules.length;\n\n // Iterate over the sanitization rules and use the first one to match.\n while (len--) {\n var rule = rules[len];\n\n if (rule[0].test(word)) return replace(word, rule);\n }\n\n return word;\n }\n\n /**\n * Replace a word with the updated word.\n *\n * @param {Object} replaceMap\n * @param {Object} keepMap\n * @param {Array} rules\n * @return {Function}\n */\n function replaceWord (replaceMap, keepMap, rules) {\n return function (word) {\n // Get the correct token and case restoration functions.\n var token = word.toLowerCase();\n\n // Check against the keep object map.\n if (keepMap.hasOwnProperty(token)) {\n return restoreCase(word, token);\n }\n\n // Check against the replacement map for a direct word replacement.\n if (replaceMap.hasOwnProperty(token)) {\n return restoreCase(word, replaceMap[token]);\n }\n\n // Run all the rules against the word.\n return sanitizeWord(token, word, rules);\n };\n }\n\n /**\n * Check if a word is part of the map.\n */\n function checkWord (replaceMap, keepMap, rules, bool) {\n return function (word) {\n var token = word.toLowerCase();\n\n if (keepMap.hasOwnProperty(token)) return true;\n if (replaceMap.hasOwnProperty(token)) return false;\n\n return sanitizeWord(token, token, rules) === token;\n };\n }\n\n /**\n * Pluralize or singularize a word based on the passed in count.\n *\n * @param {string} word The word to pluralize\n * @param {number} count How many of the word exist\n * @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks)\n * @return {string}\n */\n function pluralize (word, count, inclusive) {\n var pluralized = count === 1\n ? pluralize.singular(word) : pluralize.plural(word);\n\n return (inclusive ? count + ' ' : '') + pluralized;\n }\n\n /**\n * Pluralize a word.\n *\n * @type {Function}\n */\n pluralize.plural = replaceWord(\n irregularSingles, irregularPlurals, pluralRules\n );\n\n /**\n * Check if a word is plural.\n *\n * @type {Function}\n */\n pluralize.isPlural = checkWord(\n irregularSingles, irregularPlurals, pluralRules\n );\n\n /**\n * Singularize a word.\n *\n * @type {Function}\n */\n pluralize.singular = replaceWord(\n irregularPlurals, irregularSingles, singularRules\n );\n\n /**\n * Check if a word is singular.\n *\n * @type {Function}\n */\n pluralize.isSingular = checkWord(\n irregularPlurals, irregularSingles, singularRules\n );\n\n /**\n * Add a pluralization rule to the collection.\n *\n * @param {(string|RegExp)} rule\n * @param {string} replacement\n */\n pluralize.addPluralRule = function (rule, replacement) {\n pluralRules.push([sanitizeRule(rule), replacement]);\n };\n\n /**\n * Add a singularization rule to the collection.\n *\n * @param {(string|RegExp)} rule\n * @param {string} replacement\n */\n pluralize.addSingularRule = function (rule, replacement) {\n singularRules.push([sanitizeRule(rule), replacement]);\n };\n\n /**\n * Add an uncountable word rule.\n *\n * @param {(string|RegExp)} word\n */\n pluralize.addUncountableRule = function (word) {\n if (typeof word === 'string') {\n uncountables[word.toLowerCase()] = true;\n return;\n }\n\n // Set singular and plural references for the word.\n pluralize.addPluralRule(word, '$0');\n pluralize.addSingularRule(word, '$0');\n };\n\n /**\n * Add an irregular word definition.\n *\n * @param {string} single\n * @param {string} plural\n */\n pluralize.addIrregularRule = function (single, plural) {\n plural = plural.toLowerCase();\n single = single.toLowerCase();\n\n irregularSingles[single] = plural;\n irregularPlurals[plural] = single;\n };\n\n /**\n * Irregular rules.\n */\n [\n // Pronouns.\n ['I', 'we'],\n ['me', 'us'],\n ['he', 'they'],\n ['she', 'they'],\n ['them', 'them'],\n ['myself', 'ourselves'],\n ['yourself', 'yourselves'],\n ['itself', 'themselves'],\n ['herself', 'themselves'],\n ['himself', 'themselves'],\n ['themself', 'themselves'],\n ['is', 'are'],\n ['was', 'were'],\n ['has', 'have'],\n ['this', 'these'],\n ['that', 'those'],\n // Words ending in with a consonant and `o`.\n ['echo', 'echoes'],\n ['dingo', 'dingoes'],\n ['volcano', 'volcanoes'],\n ['tornado', 'tornadoes'],\n ['torpedo', 'torpedoes'],\n // Ends with `us`.\n ['genus', 'genera'],\n ['viscus', 'viscera'],\n // Ends with `ma`.\n ['stigma', 'stigmata'],\n ['stoma', 'stomata'],\n ['dogma', 'dogmata'],\n ['lemma', 'lemmata'],\n ['schema', 'schemata'],\n ['anathema', 'anathemata'],\n // Other irregular rules.\n ['ox', 'oxen'],\n ['axe', 'axes'],\n ['die', 'dice'],\n ['yes', 'yeses'],\n ['foot', 'feet'],\n ['eave', 'eaves'],\n ['goose', 'geese'],\n ['tooth', 'teeth'],\n ['quiz', 'quizzes'],\n ['human', 'humans'],\n ['proof', 'proofs'],\n ['carve', 'carves'],\n ['valve', 'valves'],\n ['looey', 'looies'],\n ['thief', 'thieves'],\n ['groove', 'grooves'],\n ['pickaxe', 'pickaxes'],\n ['passerby', 'passersby']\n ].forEach(function (rule) {\n return pluralize.addIrregularRule(rule[0], rule[1]);\n });\n\n /**\n * Pluralization rules.\n */\n [\n [/s?$/i, 's'],\n [/[^\\u0000-\\u007F]$/i, '$0'],\n [/([^aeiou]ese)$/i, '$1'],\n [/(ax|test)is$/i, '$1es'],\n [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'],\n [/(e[mn]u)s?$/i, '$1s'],\n [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'],\n [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'],\n [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'],\n [/(seraph|cherub)(?:im)?$/i, '$1im'],\n [/(her|at|gr)o$/i, '$1oes'],\n [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'],\n [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'],\n [/sis$/i, 'ses'],\n [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'],\n [/([^aeiouy]|qu)y$/i, '$1ies'],\n [/([^ch][ieo][ln])ey$/i, '$1ies'],\n [/(x|ch|ss|sh|zz)$/i, '$1es'],\n [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'],\n [/\\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'],\n [/(pe)(?:rson|ople)$/i, '$1ople'],\n [/(child)(?:ren)?$/i, '$1ren'],\n [/eaux$/i, '$0'],\n [/m[ae]n$/i, 'men'],\n ['thou', 'you']\n ].forEach(function (rule) {\n return pluralize.addPluralRule(rule[0], rule[1]);\n });\n\n /**\n * Singularization rules.\n */\n [\n [/s$/i, ''],\n [/(ss)$/i, '$1'],\n [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\\w]|^)li)ves$/i, '$1fe'],\n [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'],\n [/ies$/i, 'y'],\n [/\\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, '$1ie'],\n [/\\b(mon|smil)ies$/i, '$1ey'],\n [/\\b((?:tit)?m|l)ice$/i, '$1ouse'],\n [/(seraph|cherub)im$/i, '$1'],\n [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, '$1'],\n [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, '$1sis'],\n [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'],\n [/(test)(?:is|es)$/i, '$1is'],\n [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'],\n [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'],\n [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'],\n [/(alumn|alg|vertebr)ae$/i, '$1a'],\n [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'],\n [/(matr|append)ices$/i, '$1ix'],\n [/(pe)(rson|ople)$/i, '$1rson'],\n [/(child)ren$/i, '$1'],\n [/(eau)x?$/i, '$1'],\n [/men$/i, 'man']\n ].forEach(function (rule) {\n return pluralize.addSingularRule(rule[0], rule[1]);\n });\n\n /**\n * Uncountable rules.\n */\n [\n // Singular words with no plurals.\n 'adulthood',\n 'advice',\n 'agenda',\n 'aid',\n 'aircraft',\n 'alcohol',\n 'ammo',\n 'analytics',\n 'anime',\n 'athletics',\n 'audio',\n 'bison',\n 'blood',\n 'bream',\n 'buffalo',\n 'butter',\n 'carp',\n 'cash',\n 'chassis',\n 'chess',\n 'clothing',\n 'cod',\n 'commerce',\n 'cooperation',\n 'corps',\n 'debris',\n 'diabetes',\n 'digestion',\n 'elk',\n 'energy',\n 'equipment',\n 'excretion',\n 'expertise',\n 'firmware',\n 'flounder',\n 'fun',\n 'gallows',\n 'garbage',\n 'graffiti',\n 'hardware',\n 'headquarters',\n 'health',\n 'herpes',\n 'highjinks',\n 'homework',\n 'housework',\n 'information',\n 'jeans',\n 'justice',\n 'kudos',\n 'labour',\n 'literature',\n 'machinery',\n 'mackerel',\n 'mail',\n 'media',\n 'mews',\n 'moose',\n 'music',\n 'mud',\n 'manga',\n 'news',\n 'only',\n 'personnel',\n 'pike',\n 'plankton',\n 'pliers',\n 'police',\n 'pollution',\n 'premises',\n 'rain',\n 'research',\n 'rice',\n 'salmon',\n 'scissors',\n 'series',\n 'sewage',\n 'shambles',\n 'shrimp',\n 'software',\n 'species',\n 'staff',\n 'swine',\n 'tennis',\n 'traffic',\n 'transportation',\n 'trout',\n 'tuna',\n 'wealth',\n 'welfare',\n 'whiting',\n 'wildebeest',\n 'wildlife',\n 'you',\n /pok[eé]mon$/i,\n // Regexes.\n /[^aeiou]ese$/i, // \"chinese\", \"japanese\"\n /deer$/i, // \"deer\", \"reindeer\"\n /fish$/i, // \"fish\", \"blowfish\", \"angelfish\"\n /measles$/i,\n /o[iu]s$/i, // \"carnivorous\"\n /pox$/i, // \"chickpox\", \"smallpox\"\n /sheep$/i\n ].forEach(pluralize.addUncountableRule);\n\n return pluralize;\n});\n","/**\n * Aggregate doctype derivation.\n *\n * A table gets two generated doctypes: the entity itself, whose `fields` carry every column and\n * which backs the record form, and an **aggregate** — the collection view over the same table.\n * The aggregate starts with identity alone, because the useful default for a collection is the\n * one column that lets a row be opened, not all forty. Widening it is curation, and curation\n * survives regeneration (see `mergeIntrospectedDoctype`).\n *\n * The two are peers: each is a complete doctype with its own `name` and `slug`, and nothing here\n * encodes a relationship between them. Deriving the aggregate's name from the entity's is a\n * generated encoding, not a readable one — no consumer recovers the pair by parsing a slug.\n *\n * @packageDocumentation\n */\n\nimport pluralize from 'pluralize'\n\nimport { toSlug } from '../naming'\nimport { getDoctypeSlug } from '../doctype'\nimport { flattenFields, getPrimaryKeyField } from '../field'\nimport type { ValueField } from '../field'\nimport type { ConvertedGraphQLDoctype } from './types'\n\n/**\n * The name an entity's aggregate doctype is generated under: the entity's name, pluralised.\n *\n * One definition, because the CLI writes the file under `toSlug` of this and any later caller\n * (a scaffolder, a docs generator) must land on the same name or it silently addresses a\n * different file.\n *\n * `pluralize` rather than appending `s`, because the irregulars are not rare in practice —\n * measured against a consumer's 41 hand-authored aggregate doctypes, this rule reproduces every\n * one of their names, slugs and filenames exactly, while `+ 's'` gets five wrong\n * (`Currencys`, `JournalEntrys`, …).\n *\n * The rule is not total: an already-plural name pluralises to itself. Callers must handle that —\n * see {@link buildAggregateDoctype}.\n *\n * @param doctypeName - the entity doctype's `name`\n * @returns the aggregate doctype's `name`\n * @public\n *\n * @example\n * ```typescript\n * aggregateDoctypeName('SalesOrder') // 'SalesOrders' -> slug 'sales-orders'\n * ```\n */\nexport function aggregateDoctypeName(doctypeName: string): string {\n\treturn pluralize.plural(doctypeName)\n}\n\n/**\n * Derive the aggregate doctype for a converted entity.\n *\n * Returns `undefined` when no identity column can be found — a natural-key table whose key the\n * converter refuses to guess and whose author has not declared one, or a foreign PostGraphile\n * endpoint that has left the Relay identifier occupying `id` (Stonecrop's own preset moves it to\n * `nodeId`). That is deliberate: an aggregate with an empty `fields` array is a valid doctype that\n * renders a table with no columns, which looks like a data problem rather than a generation one.\n * Emitting nothing and saying so is the loud failure.\n *\n * Identity resolves the same way `getRecordIdField` resolves it — the declared `primaryKey`, then\n * the conventional `id` — so an aggregate is always keyed on the column the client will later ask\n * for. `declaredIdentity` overrides both: SDL cannot express which `UNIQUE` column is the key, so\n * for a natural-key table the answer only exists in the authored file, and the caller that read it\n * passes the fieldname back.\n *\n * @param doctype - a converted entity doctype, as returned by `convertGraphQLSchema`\n * @param declaredIdentity - fieldname the authored doctype declares as its `primaryKey`, when the\n * caller has read one. Must name a field the converter emitted; the caller checks that, because\n * only it can say whether a missing one is a dropped column or a typo.\n * @returns the aggregate doctype, or `undefined` when no identity column exists\n * @public\n *\n * @example\n * ```typescript\n * const [order] = convertGraphQLSchema(sdl, { include: ['Order'] })\n * const aggregate = buildAggregateDoctype(order)\n * // { name: 'Orders', slug: 'orders', fields: [ the id field ] }\n * ```\n */\nexport function buildAggregateDoctype(\n\tdoctype: ConvertedGraphQLDoctype,\n\tdeclaredIdentity?: string\n): ConvertedGraphQLDoctype | undefined {\n\tconst identity = findIdentityField(doctype.fields, declaredIdentity)\n\tif (!identity) return undefined\n\n\tconst name = aggregateDoctypeName(doctype.name)\n\t// An already-plural name pluralises to itself, which would give the aggregate the entity's own\n\t// `name` *and* its filename. Both write paths are silent about it: the CLI writes the file twice\n\t// in one run, and the middleware's registry is a Map keyed by name, so the later read wins in\n\t// whatever order `readdirSync` returns. Refusing is the only loud option.\n\tif (name === doctype.name) return undefined\n\n\t// `primaryKey` is stamped rather than copied through: a declared identity is not marked on the\n\t// converter's own field, and an aggregate whose one column carries no marker resolves identity\n\t// through `getRecordIdField`'s `id` fallback — a column it does not have, so every listed row is\n\t// silently dropped. Rebuilt with `source` last so the key order matches an entity's identity\n\t// field and both files stay byte-stable.\n\t//\n\t// A copy, not a reference: the two doctypes are written to separate files and an edit to one\n\t// must not reach the other.\n\tconst { source, ...rest } = identity\n\treturn {\n\t\tname,\n\t\tslug: toSlug(name),\n\t\tfields: [{ ...rest, primaryKey: true, ...(source === undefined ? {} : { source }) }],\n\t}\n}\n\n/**\n * The field an aggregate is keyed on: an identity the author declared, else the primary key the\n * converter derived, else the conventional `id`.\n *\n * The author wins because the authored doctype is the source of truth — generation verifies it and\n * never overwrites it (see `mergeIntrospectedDoctype`), and the divergence is already reported as\n * identity drift.\n *\n * Calls `getPrimaryKeyField` for the derived half rather than restating it: a restatement drifted\n * exactly as one does, staying top-level while the helper learned to descend into fieldsets.\n *\n * @internal\n */\nfunction findIdentityField(fields: readonly ValueField[], declared?: string): ValueField | undefined {\n\tif (declared !== undefined) return fields.find(field => field.fieldname === declared)\n\treturn getPrimaryKeyField(fields) ?? fields.find(field => field.fieldname === 'id')\n}\n\n/**\n * The doctypes in a run that get a URL of their own.\n *\n * A child table has no page: its rows exist inside a parent and are edited there, so a route for\n * it is an address nothing can link to. The declaration that says so is the parent's `links` entry\n * with a to-many cardinality — which the server derives from the foreign keys it treats as owning,\n * so this reads what the schema states rather than guessing from a name.\n *\n * The rule is *listed by something, referenced by nothing*. A single reference wins over any number\n * of listings, and the asymmetry is deliberate: a doctype that is both a parent's rows and another\n * doctype's link target — a recipe task, say, embedded in its recipe and pointed at by four other\n * records — needs somewhere for those links to navigate to. Denying it leaves the arrow on an\n * `AFormLink` dead, which fails silently; granting it leaves a URL nobody visits, which does not.\n *\n * Scoped to one run, so a partial generation sees a partial graph and grants more routes than a\n * whole one would. That is the safe direction, and the extra routes are deletable — an authored\n * file's keys survive regeneration untouched.\n *\n * @internal\n */\nfunction routableDoctypes(entities: readonly ConvertedGraphQLDoctype[]): Set<string> {\n\tconst listed = new Set<string>()\n\tconst referenced = new Set<string>()\n\n\tfor (const entity of entities) {\n\t\tfor (const link of Object.values(entity.links ?? {})) {\n\t\t\tif (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne') listed.add(link.target)\n\t\t\telse referenced.add(link.target)\n\t\t}\n\t\t// `flattenFields` rather than a top-level scan: a link inside a fieldset is still a reference,\n\t\t// and the two ways to answer this question have already drifted apart once.\n\t\tfor (const field of flattenFields(entity.fields)) {\n\t\t\tif ('doctype' in field && typeof field.doctype === 'string') referenced.add(field.doctype)\n\t\t}\n\t}\n\n\treturn new Set(\n\t\tentities\n\t\t\t.filter(entity => {\n\t\t\t\tconst slug = getDoctypeSlug(entity)\n\t\t\t\treturn referenced.has(slug) || !listed.has(slug)\n\t\t\t})\n\t\t\t.map(entity => entity.name)\n\t)\n}\n\n/**\n * One file the generator will write, and what that file is verified against.\n *\n * `basis` exists because the two are not always the same document. An aggregate is written from\n * its own one-field generation but verified against the **entity**, since its purpose is to carry\n * fewer columns than the table — checking it against itself reports every curated column as one\n * the table had dropped.\n *\n * @public\n */\nexport interface GenerationPlanEntry {\n\t/** The doctype to write. */\n\tgenerated: ConvertedGraphQLDoctype\n\t/** The doctype whose fields an existing file on disk is verified against. */\n\tbasis: ConvertedGraphQLDoctype\n\t/** Whether the file is a curated subset of `basis` — passed through to `MergeOptions.subset`. */\n\tsubset: boolean\n}\n\n/** Options for {@link planGeneration}. @public */\nexport interface GenerationPlanOptions {\n\t/** Emit only the entity doctypes, skipping their aggregates. Defaults to `false`. */\n\tnoAggregates?: boolean\n\t/** Called with an advisory message for each entity that yields no aggregate. */\n\tonWarning?: (message: string) => void\n\t/**\n\t * Identity the authored doctype on disk declares, keyed by doctype `name`.\n\t *\n\t * SDL cannot say which `UNIQUE` column is a table's key, so for a natural-key table the converter\n\t * derives nothing and the answer exists only in the file. Without this the aggregate is\n\t * unreachable: generation says \"declare a primaryKey and re-run\", and re-running after declaring\n\t * one changes nothing, because planning never reads the file.\n\t *\n\t * Passed in rather than read here so this stays a pure function of its inputs; the CLI owns the\n\t * IO. The plan is then a function of the schema *and* what is already on disk.\n\t */\n\tidentity?: Record<string, string>\n}\n\n/**\n * Expand converted entities into the set of doctype files to write.\n *\n * Each table yields two: the entity, whose fields carry every column and which backs the record\n * form, and its aggregate — the collection view. They are written as peers, one file each, with\n * no key relating them.\n *\n * Separate from the CLI because the pairing of a file to its verification basis is the part that\n * is easy to get wrong and impossible to notice: getting it wrong does not throw, it just reports\n * drift that is not there, forever.\n *\n * @param entities - `convertGraphQLSchema` output\n * @param options - see {@link GenerationPlanOptions}\n * @returns one entry per file to write\n * @public\n */\nexport function planGeneration(\n\tentities: readonly ConvertedGraphQLDoctype[],\n\toptions: GenerationPlanOptions = {}\n): GenerationPlanEntry[] {\n\tconst entityNames = new Set(entities.map(entity => entity.name))\n\tconst claimed = new Set<string>()\n\tconst routable = routableDoctypes(entities)\n\n\treturn entities.flatMap(entity => {\n\t\t// Written out in full rather than as a segment the host assembles: the record parameter has\n\t\t// to live somewhere, and a host given `/order` cannot know whether this doctype is the\n\t\t// collection or the record without asking a second question. The pair shares the entity's\n\t\t// slug, so no URL ever carries a plural.\n\t\tconst segment = `/${getDoctypeSlug(entity)}`\n\t\tconst routed = routable.has(entity.name) ? { ...entity, route: `${segment}/:id` } : entity\n\n\t\t// `basis` is the same object as `generated` for an entity — it is verified against itself.\n\t\tconst self: GenerationPlanEntry = { generated: routed, basis: routed, subset: false }\n\t\tif (options.noAggregates) return [self]\n\n\t\t// Name collisions are checked here rather than in the builder because only this function\n\t\t// holds the whole set. Reported before the identity check so each refusal names its own\n\t\t// cause — the two are repaired differently.\n\t\tconst name = aggregateDoctypeName(entity.name)\n\t\tif (name === entity.name) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name} is already plural, so its aggregate would take the same name and the same ` +\n\t\t\t\t\t`file. No aggregate was generated. Rename the doctype to its singular form, or author ` +\n\t\t\t\t\t`${entity.slug}.json's collection view by hand.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\t\tif (entityNames.has(name) || claimed.has(name)) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name}'s aggregate would be named ${name}, which is already taken by another ` +\n\t\t\t\t\t`doctype in this run. No aggregate was generated — one of the two needs an explicit ` +\n\t\t\t\t\t`name via the doctypeNames option.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\n\t\t// Checked here rather than in the builder because only the caller knows whether a name that\n\t\t// matches nothing is a dropped column or a typo — and an aggregate built around a field the\n\t\t// table has no column for renders a collection whose only column is absent from every row.\n\t\tconst declared = options.identity?.[entity.name]\n\t\tif (declared !== undefined && !entity.fields.some(field => field.fieldname === declared)) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name} declares its primaryKey on '${declared}', which the schema has no column for. ` +\n\t\t\t\t\t`No aggregate was generated — correct the declaration in ${entity.slug}.json, or restore the ` +\n\t\t\t\t\t`column to the table.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\n\t\tconst aggregate = buildAggregateDoctype(entity, declared)\n\t\tif (!aggregate) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name} has no derivable identity column, so no aggregate doctype was generated. ` +\n\t\t\t\t\t`Declare a primaryKey on ${entity.slug}.json and re-run.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\t\tclaimed.add(name)\n\n\t\t// The basis is the entity itself: an aggregate is verified against the table it curates from,\n\t\t// not against its own one-field generation. Drift lines take their name from the authored file\n\t\t// being checked, so they already name the file the reader has to edit.\n\t\tconst listed = routable.has(entity.name) ? { ...aggregate, route: segment } : aggregate\n\t\treturn [self, { generated: listed, basis: entity, subset: true }]\n\t})\n}\n","/**\n * Reading an authored doctype — the JSON as it sits on disk, before any parsing.\n *\n * A separate reader from `@stonecrop/schema`'s `flattenFields`/`getPrimaryKeyField` because the two\n * operate on different *shapes*, not different rules: those take parsed `DoctypeField`s and branch\n * on the `kind` discriminant the Zod parser synthesizes, which authored JSON does not carry.\n * `getPrimaryKeyField` on a raw file therefore returns `undefined` — indistinguishable from \"no key\n * declared\", which is the exact condition its callers are testing.\n *\n * Every question about authored JSON is answered here once, so the rule cannot drift between the\n * merge and the generation plan.\n *\n * @internal\n */\n\n/**\n * A doctype as it exists on disk: a plain object that may carry keys this package does not model\n * (`handler` on an action, `filterFunction` on a field, whatever an app has added). Typing it\n * loosely is what lets the merge round-trip those keys untouched instead of dropping them.\n *\n * @public\n */\nexport type AuthoredDoctype = Record<string, unknown>\n\n/** @internal */\nexport function isAuthoredRecord(value: unknown): value is AuthoredDoctype {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Flatten authored fields, descending into fieldsets.\n *\n * A fieldset is a layout grouping, not a scope: a field inside one is still a field of the doctype,\n * with a column of its own and a key it may declare.\n *\n * @internal\n */\nexport function flattenAuthored(fields: readonly AuthoredDoctype[]): AuthoredDoctype[] {\n\tconst out: AuthoredDoctype[] = []\n\tfor (const field of fields) {\n\t\tif (Array.isArray(field.schema)) {\n\t\t\tout.push(...flattenAuthored(field.schema.filter(isAuthoredRecord)))\n\t\t} else {\n\t\t\tout.push(field)\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * The fieldname an authored doctype declares as its identity, or `undefined` when it declares none.\n *\n * Descends into fieldsets, because a nested `primaryKey` is a real declaration — ignoring one is\n * what `getPrimaryKeyField` was fixed for.\n *\n * @internal\n */\nexport function authoredPrimaryKey(doctype: AuthoredDoctype): string | undefined {\n\tif (!Array.isArray(doctype.fields)) return undefined\n\tconst declared = flattenAuthored(doctype.fields.filter(isAuthoredRecord)).find(f => f.primaryKey === true)\n\treturn typeof declared?.fieldname === 'string' ? declared.fieldname : undefined\n}\n","/**\n * Merge introspected schema facts into an already-authored doctype.\n *\n * The authored doctype is the source of truth. Generation **verifies** it and stamps provenance;\n * it does not overwrite. That polarity is deliberate and load-bearing — a doctype legitimately\n * declares a `primaryKey` the schema cannot express. A natural business key is very often a\n * `UNIQUE` constraint rather than the table's `PRIMARY KEY`, and where a table carries several\n * uniques no rule can pick between them. Overwriting identity from the schema would silently\n * re-key such a doctype on every regeneration and break the handlers that key on the old value.\n *\n * So divergence is **reported, never applied** — a human decides. The only mutation this performs\n * is adding `source: 'introspected'` to fields confirmed to exist in the GraphQL schema.\n *\n * @packageDocumentation\n */\n\nimport { INTROSPECTED_IDENTITY_PROPS } from '../field'\nimport { authoredPrimaryKey, flattenAuthored, isAuthoredRecord } from './authored'\nimport type { AuthoredDoctype } from './authored'\nimport type { ConvertedGraphQLDoctype } from './types'\n\nexport type { AuthoredDoctype }\n\n/**\n * What generation found that the authored doctype does not agree with. Every bucket is advisory —\n * nothing here is applied automatically.\n *\n * @public\n */\nexport interface DoctypeDrift {\n\t/** The authored doctype's name. */\n\tdoctype: string\n\t/**\n\t * `clean` — the authored primary key is the one generation would derive.\n\t * `partial` — the doctype declares an identity generation cannot derive, so identity was left alone.\n\t */\n\tmode: 'clean' | 'partial'\n\t/** Why the mode is `partial`, when it is. */\n\treason?: string\n\t/** Fieldnames confirmed against the schema and stamped. */\n\ttagged: string[]\n\t/** Authored fields with no matching schema field — app components, fieldsets, or stale entries. */\n\torphan: string[]\n\t/** Schema fields absent from the doctype. Usually deliberate curation, occasionally an oversight. */\n\tomitted: string[]\n\t/** `fieldname: authored=… schema=…` where the chosen component differs from the scalar mapping. */\n\tcomponentDrift: string[]\n\t/** `fieldname: authored=… schema=…` where nullability disagrees. */\n\trequiredDrift: string[]\n\t/** Identity properties that differ. These are the ones a human must adjudicate. */\n\tidentityDrift: string[]\n}\n\n/**\n * How to verify the authored doctype against the schema.\n *\n * @public\n */\nexport interface MergeOptions {\n\t/**\n\t * The authored doctype is a curated **subset** of the schema's columns rather than a model of\n\t * all of them — an aggregate being the case this exists for.\n\t *\n\t * This changes what counts as drift in both directions, so `generated` must be passed the\n\t * *entity's* full field set, not the subset's. A column the author added to an aggregate is\n\t * then confirmed against the real table (so a genuinely dropped column still reports as an\n\t * orphan), while the columns deliberately left out stop reporting as omissions. Without it an\n\t * aggregate reports phantom drift on every run, which both spams `--check` and buries the one\n\t * finding that matters.\n\t */\n\tsubset?: boolean\n}\n\n/** Outcome of a merge: the doctype to write, plus what generation disagreed with. @public */\nexport interface MergeResult {\n\t/** The authored doctype with `source` markers added and nothing else changed. */\n\tdoctype: AuthoredDoctype\n\t/** Advisory report. Never applied. */\n\tdrift: DoctypeDrift\n}\n\nfunction describe(value: unknown): string {\n\treturn value === undefined ? '—' : JSON.stringify(value)\n}\n\n/**\n * Verify an authored doctype against freshly generated output and stamp provenance.\n *\n * @param authored - the doctype as it exists on disk; every key not named below is preserved verbatim\n * @param generated - `convertGraphQLSchema` output for the corresponding GraphQL type. For a\n * `subset` merge this is the **entity**, whose fields are the set the subset is curated from\n * @param options - see {@link MergeOptions}\n * @returns the doctype to write, plus a drift report\n *\n * @example\n * ```ts\n * const [generated] = convertGraphQLSchema(introspection, { include: ['Uom'] })\n * const { doctype, drift } = mergeIntrospectedDoctype(JSON.parse(onDisk), generated)\n * if (drift.identityDrift.length) console.warn(drift.identityDrift.join('\\n'))\n * ```\n *\n * @public\n */\nexport function mergeIntrospectedDoctype(\n\tauthored: AuthoredDoctype,\n\tgenerated: ConvertedGraphQLDoctype,\n\toptions: MergeOptions = {}\n): MergeResult {\n\tconst authoredFields = Array.isArray(authored.fields) ? authored.fields.filter(isAuthoredRecord) : []\n\tconst generatedByName = new Map(generated.fields.map(f => [f.fieldname, f]))\n\t// Expanding links live in `links`, not `fields`, so a field naming one is modelled, not orphaned.\n\tconst generatedLinkNames = new Set(Object.keys(generated.links ?? {}))\n\n\tconst drift: DoctypeDrift = {\n\t\tdoctype: typeof authored.name === 'string' ? authored.name : '(unnamed)',\n\t\tmode: 'clean',\n\t\ttagged: [],\n\t\torphan: [],\n\t\tomitted: [],\n\t\tcomponentDrift: [],\n\t\trequiredDrift: [],\n\t\tidentityDrift: [],\n\t}\n\n\tconst tag = (field: AuthoredDoctype): AuthoredDoctype => {\n\t\t// Containers have no column of their own; recurse and leave the container itself alone.\n\t\tif (Array.isArray(field.schema)) {\n\t\t\treturn { ...field, schema: field.schema.filter(isAuthoredRecord).map(tag) }\n\t\t}\n\n\t\tconst name = typeof field.fieldname === 'string' ? field.fieldname : ''\n\t\tconst match = generatedByName.get(name)\n\n\t\tif (!match) {\n\t\t\t// A computed field declares up front that it has no backing column, so it is not a\n\t\t\t// discrepancy. Everything else is worth surfacing — it may be an app component, or a\n\t\t\t// column that has since been dropped.\n\t\t\tif (field.computed !== true && !generatedLinkNames.has(name)) drift.orphan.push(name)\n\t\t\treturn field\n\t\t}\n\n\t\tdrift.tagged.push(name)\n\n\t\tif (match.component !== field.component) {\n\t\t\tdrift.componentDrift.push(`${name}: authored=${describe(field.component)} schema=${describe(match.component)}`)\n\t\t}\n\t\tif (Boolean(match.required) !== Boolean(field.required)) {\n\t\t\tdrift.requiredDrift.push(`${name}: authored=${Boolean(field.required)} schema=${Boolean(match.required)}`)\n\t\t}\n\t\tfor (const prop of INTROSPECTED_IDENTITY_PROPS) {\n\t\t\tif (prop === 'fieldname' || prop === 'required') continue\n\t\t\tconst authoredValue = field[prop]\n\t\t\tconst schemaValue = match[prop]\n\t\t\t// Absent on both sides is agreement, not drift — most fields set none of these.\n\t\t\tif (authoredValue === undefined && schemaValue === undefined) continue\n\t\t\tif (JSON.stringify(authoredValue) !== JSON.stringify(schemaValue)) {\n\t\t\t\tdrift.identityDrift.push(`${name}.${prop}: authored=${describe(authoredValue)} schema=${describe(schemaValue)}`)\n\t\t\t}\n\t\t}\n\n\t\treturn { ...field, source: 'introspected' }\n\t}\n\n\tconst merged: AuthoredDoctype = { ...authored, fields: authoredFields.map(tag) }\n\n\t// A curated subset omits columns by definition, so the bucket that reports omissions has\n\t// nothing true to say about one.\n\tif (!options.subset) {\n\t\tconst authoredNames = new Set(flattenAuthored(authoredFields).map(f => f.fieldname))\n\t\tdrift.omitted = generated.fields.map(f => f.fieldname).filter(n => !authoredNames.has(n))\n\t}\n\n\t// Classify identity last, once every field has been compared.\n\tconst authoredPk = authoredPrimaryKey(authored)\n\tconst generatedPk = generated.fields.find(f => f.primaryKey === true)\n\tif (authoredPk && generatedPk && authoredPk !== generatedPk.fieldname) {\n\t\tdrift.mode = 'partial'\n\t\tdrift.reason = `authored primary key '${authoredPk}' is not the derivable '${generatedPk.fieldname}' — left as authored`\n\t} else if (authoredPk && !generatedPk) {\n\t\tdrift.mode = 'partial'\n\t\tdrift.reason = `authored primary key '${authoredPk}' is not derivable from the schema — left as authored`\n\t} else if (!authoredPk && generatedPk) {\n\t\tdrift.mode = 'partial'\n\t\tdrift.reason = `schema suggests '${generatedPk.fieldname}' as primary key but the doctype declares none — not applied`\n\t}\n\n\treturn { doctype: merged, drift }\n}\n\n/**\n * Render a drift report as human-readable lines. Empty when generation agrees with the doctype.\n *\n * @param drift - a report from {@link mergeIntrospectedDoctype}\n * @returns one line per finding, ready to print\n *\n * @public\n */\nexport function formatDoctypeDrift(drift: DoctypeDrift): string[] {\n\tconst lines: string[] = []\n\tif (drift.reason) lines.push(` ${drift.doctype}: ${drift.reason}`)\n\tconst bucket = (label: string, entries: string[]) => {\n\t\tif (entries.length) lines.push(` ${drift.doctype}: ${label} ${entries.join('; ')}`)\n\t}\n\tbucket('identity drift', drift.identityDrift)\n\tbucket('component drift', drift.componentDrift)\n\tbucket('required drift', drift.requiredDrift)\n\tbucket('authored fields with no schema field:', drift.orphan)\n\tbucket('schema fields not modelled:', drift.omitted)\n\treturn lines\n}\n","/**\n * GraphQL Introspection to Stonecrop Schema Converter\n *\n * Converts a standard GraphQL introspection result (or SDL string) into\n * Stonecrop doctype schemas. Source-agnostic — works with any GraphQL server.\n *\n * @packageDocumentation\n */\n\nimport { buildClientSchema, buildSchema, isObjectType, type GraphQLSchema } from 'graphql'\n\nimport type { LinkDeclaration } from '../doctype'\nimport { toSlug } from '../naming'\nimport type { IntrospectionSource, GraphQLConversionOptions, ConvertedGraphQLDoctype } from './types'\nimport type { ValueField } from '../field'\nimport { defaultIsEntityType, defaultIsEntityField, classifyFieldType } from './heuristics'\n\n/**\n * Convert a GraphQL schema to Stonecrop doctype schemas.\n *\n * Accepts either an `IntrospectionQuery` result object or an SDL string.\n * Entity types are identified using heuristics (or a custom `isEntityType` function)\n * and converted to `DoctypeMeta`-compatible JSON objects.\n *\n * @param source - GraphQL introspection result or SDL string\n * @param options - Conversion options for controlling output format and behavior\n * @returns Array of converted Stonecrop doctype definitions\n *\n * @example\n * ```typescript\n * // From introspection result (fetched from any GraphQL server)\n * const introspection = await fetchIntrospection('http://localhost:5000/graphql')\n * const doctypes = convertGraphQLSchema(introspection)\n *\n * // From SDL string\n * const sdl = fs.readFileSync('schema.graphql', 'utf-8')\n * const doctypes = convertGraphQLSchema(sdl)\n *\n * // With PostGraphile custom scalars\n * const doctypes = convertGraphQLSchema(introspection, {\n * customScalars: {\n * BigFloat: { component: 'ANumericInput' }\n * }\n * })\n * ```\n *\n * @public\n */\nexport function convertGraphQLSchema(\n\tsource: IntrospectionSource,\n\toptions: GraphQLConversionOptions = {}\n): ConvertedGraphQLDoctype[] {\n\tconst schema = buildGraphQLSchema(source)\n\tconst typeMap = schema.getTypeMap()\n\n\t// Determine the root operation type names to exclude\n\tconst rootTypeNames = new Set<string>()\n\tconst queryType = schema.getQueryType()\n\tconst mutationType = schema.getMutationType()\n\tconst subscriptionType = schema.getSubscriptionType()\n\tif (queryType) rootTypeNames.add(queryType.name)\n\tif (mutationType) rootTypeNames.add(mutationType.name)\n\tif (subscriptionType) rootTypeNames.add(subscriptionType.name)\n\n\t// Use custom or default entity type detector\n\tconst isEntityType = options.isEntityType ?? defaultIsEntityType\n\n\t// Phase 1: Identify all entity types\n\tconst entityTypes = new Set<string>()\n\tfor (const [typeName, type] of Object.entries(typeMap)) {\n\t\tif (!isObjectType(type)) continue\n\n\t\t// Always skip root operation types (even if custom isEntityType doesn't)\n\t\tif (rootTypeNames.has(typeName)) continue\n\n\t\tif (isEntityType(typeName, type)) {\n\t\t\tentityTypes.add(typeName)\n\t\t}\n\t}\n\n\t// Phase 2: Apply include/exclude filters\n\tlet filteredEntityTypes = entityTypes\n\n\tif (options.include) {\n\t\tconst includeSet = new Set(options.include)\n\t\tfilteredEntityTypes = new Set([...entityTypes].filter(t => includeSet.has(t)))\n\t}\n\n\tif (options.exclude) {\n\t\tconst excludeSet = new Set(options.exclude)\n\t\tfilteredEntityTypes = new Set([...filteredEntityTypes].filter(t => !excludeSet.has(t)))\n\t}\n\n\t// Phase 3: Convert each entity type to a doctype\n\tconst isEntityField = options.isEntityField ?? defaultIsEntityField\n\n\tconst doctypes: ConvertedGraphQLDoctype[] = []\n\n\tfor (const typeName of filteredEntityTypes) {\n\t\tconst type = typeMap[typeName]\n\t\tif (!isObjectType(type)) continue\n\n\t\tconst fields = type.getFields()\n\n\t\t// A type carrying BOTH `id` and `rowId` is PostGraphile Amber with its default inflection:\n\t\t// the Relay global identifier has taken `id`, displacing the real column to `rowId`. Neither\n\t\t// name can be emitted as-is — `id` is an opaque node id, and `rowId` does not name a column.\n\t\t// Refuse to guess: drop the Relay field and tell the caller to fix it at the inflector, where\n\t\t// it belongs. Normalizing here would bake a database fact into the doctype.\n\t\tconst isUnnormalizedPostGraphile = 'id' in fields && 'rowId' in fields\n\t\tif (isUnnormalizedPostGraphile) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${typeName}: schema exposes both 'id' (Relay identifier) and 'rowId' (the real column). ` +\n\t\t\t\t\t`Skipping 'id' and emitting 'rowId' verbatim — no primary key can be derived. ` +\n\t\t\t\t\t`Override the '_attributeName' and 'nodeIdFieldName' inflectors so the column keeps its own name.`\n\t\t\t)\n\t\t}\n\n\t\tconst entityFields = Object.entries(fields).filter(\n\t\t\t([fieldName, field]) =>\n\t\t\t\tisEntityField(fieldName, field, type) && !(isUnnormalizedPostGraphile && fieldName === 'id')\n\t\t)\n\n\t\t// oxlint-disable-next-line oxc/no-map-spread -- ...custom spread required; Object.assign cannot preserve the metadata-carrying inferred union type from classifyField\n\t\tconst allClassifiedFields = entityFields.map(([fieldName, field]) => {\n\t\t\t// Check for full custom classification first\n\t\t\tif (options.classifyField) {\n\t\t\t\tconst custom = options.classifyField(fieldName, field, type)\n\t\t\t\tif (custom !== null && custom !== undefined) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: 'field' as const,\n\t\t\t\t\t\tfieldname: fieldName,\n\t\t\t\t\t\tlabel: custom.label ?? fieldName,\n\t\t\t\t\t\tcomponent: custom.component ?? 'ATextInput',\n\t\t\t\t\t\t...custom,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Default classification\n\t\t\treturn classifyFieldType(fieldName, field, entityTypes, options)\n\t\t})\n\n\t\t// Derive the primary key, but only for the one case SDL actually settles: a non-null `id`\n\t\t// that is a plain scalar. A natural key is typically a UNIQUE constraint indistinguishable\n\t\t// from any other column here, and a table may carry several — so anything else is left for\n\t\t// the author to declare. Emitting a guess would be worse than emitting nothing, because the\n\t\t// middleware builds its identity predicate from this and the client keys records by it.\n\t\tconst primaryKeyFieldname = allClassifiedFields.find(\n\t\t\tfield => field.fieldname === 'id' && field.required && !field.doctype && !field._isLink\n\t\t)?.fieldname\n\n\t\t// Separate scalar fields from link fields\n\t\tconst links: Record<string, LinkDeclaration> = {}\n\t\tconst convertedFields = allClassifiedFields\n\t\t\t.filter(field => {\n\t\t\t\tif (field._isLink && field.doctype && field.cardinality) {\n\t\t\t\t\tlinks[field.fieldname] = {\n\t\t\t\t\t\ttarget: field.doctype,\n\t\t\t\t\t\tcardinality: field.cardinality,\n\t\t\t\t\t}\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t\t// Clean up internal metadata unless requested, and stamp identity + provenance.\n\t\t\t// Stamped last so every classification path (default, classifyField) carries the marker —\n\t\t\t// the docbuilder's identity lock keys off it, and no classifier may unset it.\n\t\t\t.map(field => {\n\t\t\t\tconst identity = field.fieldname === primaryKeyFieldname ? { primaryKey: true as const } : {}\n\t\t\t\tif (!options.includeUnmappedMeta) {\n\t\t\t\t\tconst { _graphqlType, _unmapped, _isLink, ...clean } = field\n\t\t\t\t\treturn Object.assign(clean, identity, { source: 'introspected' as const })\n\t\t\t\t}\n\t\t\t\tconst { _isLink, ...rest } = field\n\t\t\t\treturn Object.assign(rest, identity, { source: 'introspected' as const })\n\t\t\t})\n\n\t\tconst doctypeName = options.doctypeNames?.[typeName] ?? typeName\n\t\tconst doctype: ConvertedGraphQLDoctype = {\n\t\t\tname: doctypeName,\n\t\t\tslug: toSlug(doctypeName),\n\t\t\tfields: convertedFields as ValueField[],\n\t\t}\n\n\t\tif (Object.keys(links).length > 0) {\n\t\t\tdoctype.links = links\n\t\t}\n\n\t\tif (options.includeUnmappedMeta) {\n\t\t\tdoctype._graphqlTypeName = typeName\n\t\t}\n\n\t\tdoctypes.push(doctype)\n\t}\n\n\treturn doctypes\n}\n\n/**\n * Build a GraphQLSchema from either an introspection result or SDL string.\n *\n * @param source - IntrospectionQuery object or SDL string\n * @returns A complete GraphQLSchema\n * @internal\n */\nfunction buildGraphQLSchema(source: IntrospectionSource): GraphQLSchema {\n\tif (typeof source === 'string') {\n\t\t// SDL string\n\t\treturn buildSchema(source)\n\t}\n\n\t// IntrospectionQuery result\n\treturn buildClientSchema(source)\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Re-exports\n// ═══════════════════════════════════════════════════════════════\n\n// Main converter (this file)\nexport { convertGraphQLSchema as default }\n\n// Types\nexport type {\n\tIntrospectionSource,\n\tGraphQLConversionOptions,\n\tGraphQLConversionFieldMeta,\n\tConvertedGraphQLDoctype,\n} from './types'\n\n// Scalar maps\nexport { GQL_SCALAR_MAP, WELL_KNOWN_SCALARS, INTERNAL_SCALARS, buildScalarMap } from './scalars'\n\n// Heuristics\nexport { defaultIsEntityType, defaultIsEntityField, classifyFieldType } from './heuristics'\n\n// Aggregate — the collection-view doctype derived from an entity, emitted as its own file\nexport { aggregateDoctypeName, buildAggregateDoctype, planGeneration } from './aggregate'\nexport type { GenerationPlanEntry, GenerationPlanOptions } from './aggregate'\n\n// Merge — verifies an authored doctype against the schema and stamps provenance\nexport { mergeIntrospectedDoctype, formatDoctypeDrift } from './merge'\nexport type { AuthoredDoctype, DoctypeDrift, MergeOptions, MergeResult } from './merge'\n\n// Naming utilities\nexport { toSlug, toPascalCase, pascalToSnake, snakeToCamel, camelToSnake, snakeToLabel, camelToLabel } from '../naming'\n"],"x_google_ignoreList":[7],"mappings":"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"}
1
+ {"version":3,"file":"converter-2mU9FiFz.js","names":[],"sources":["../src/table.ts","../src/field.ts","../src/naming.ts","../src/doctype.ts","../src/validation.ts","../src/converter/scalars.ts","../src/converter/heuristics.ts","../../node_modules/.pnpm/pluralize@8.0.0/node_modules/pluralize/pluralize.js","../src/converter/aggregate.ts","../src/converter/authored.ts","../src/converter/merge.ts","../src/converter/index.ts"],"sourcesContent":["import { z } from 'zod'\n\n/**\n * JSON-safe view configuration for table fields in doctype authoring.\n *\n * This is the authoring-time subset of `@stonecrop/atable`'s `TableConfig`. It covers\n * the view discriminator and structural options that can be expressed in static JSON.\n * `rowActions` (which requires function-typed handlers) stays in the runtime `TableConfig`.\n *\n * @public\n */\nexport const TableViewConfig = z\n\t.object({\n\t\t/** The table view type */\n\t\tview: z.enum(['list', 'uncounted', 'list-expansion', 'tree', 'gantt', 'tree-gantt']).optional(),\n\n\t\t/** Allow the table to use the full width of its container */\n\t\tfullWidth: z.boolean().optional(),\n\n\t\t/** Default expansion state for tree views */\n\t\tdefaultTreeExpansion: z.enum(['root', 'branch', 'leaf']).optional(),\n\n\t\t/** Enable dependency graph connections for Gantt views */\n\t\tdependencyGraph: z.boolean().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'TableViewConfig',\n\t\tdescription: 'JSON-safe view configuration for table fields in doctype authoring',\n\t})\n\n/**\n * Table view configuration type inferred from Zod schema\n * @public\n */\nexport type TableViewConfig = z.infer<typeof TableViewConfig>\n","import { z } from 'zod'\n\nimport type { ColumnSchema } from './column-schema'\nimport { flattenFields } from './flatten'\nimport type { InteractionMode } from './mode'\nimport { TableViewConfig } from './table'\n\n// Re-exported so callers already on this module keep one import; `flatten.ts` says why the\n// definition itself sits off to the side.\nexport { flattenFields }\n\n/**\n * Field options - flexible bag for type-specific configuration.\n *\n * Usage:\n * - Select: array of choices ([\"Draft\", \"Submitted\", \"Cancelled\"])\n * - Select with badges: \\{ choices: [...], badges: \\{ Open: \"warning\", ... \\} \\} or bare map\n * - Decimal: config object (\\{ precision: 10, scale: 2 \\})\n * - Code: config object (\\{ language: \"python\" \\})\n *\n * Deliberately *not* a bare string: a string once meant \"link target\", which made the value's\n * shape encode its meaning. That job belongs to `ValueField.doctype`, leaving this a plain\n * choices-or-config bag.\n *\n * @public\n */\nexport const FieldOptions = z\n\t.union([\n\t\tz.array(z.string()), // Select choices: [\"A\", \"B\", \"C\"]\n\t\tz.record(z.string(), z.unknown()), // Config: \\{ precision: 10, scale: 2 \\}\n\t])\n\t.meta({\n\t\ttitle: 'FieldOptions',\n\t\tdescription: 'Field options - flexible bag for type-specific configuration',\n\t})\n\n/**\n * Field options type inferred from Zod schema\n * @public\n */\nexport type FieldOptions = z.infer<typeof FieldOptions>\n\n/**\n * Validation configuration for form fields\n * @public\n */\nexport const FieldValidation = z\n\t.looseObject({\n\t\t/** Error message to display when validation fails */\n\t\terrorMessage: z.string(),\n\t})\n\t.meta({\n\t\ttitle: 'FieldValidation',\n\t\tdescription: 'Validation configuration for form fields',\n\t})\n\n/**\n * Field validation type inferred from Zod schema\n * @public\n */\nexport type FieldValidation = z.infer<typeof FieldValidation>\n\n// ---------------------------------------------------------------------------\n// DoctypeField — the discriminated union of authoring-time field variants\n// ---------------------------------------------------------------------------\n\n/**\n * A field that holds a scalar value, a link to another record, or a select choice.\n * The most common kind of field. `component` determines how it renders; the attributes below\n * carry everything else that is not a rendering concern.\n * @public\n */\nexport interface ValueField {\n\t/** Discriminator — identifies this as a value-holding field */\n\tkind: 'field'\n\t/** Unique identifier for this field within its doctype */\n\tfieldname: string\n\t/**\n\t * Vue component that renders this field — the primary (and only) rendering axis. Required:\n\t * there is nothing left to derive it from, and a field without one has nothing to render it.\n\t * Any string is valid; naming a custom component is how an app renders a field Stonecrop\n\t * ships no widget for. See `CANONICAL_COMPONENTS` for the set Stonecrop provides.\n\t */\n\tcomponent: string\n\t/** True for the field that identifies the record's primary-key column. */\n\tprimaryKey?: boolean\n\t/** True for a computed/display field with no backing DB column — excluded from SQL SELECT. */\n\tcomputed?: boolean\n\t/** Editor language for code fields (e.g. `'json'`, `'typescript'`) — the only thing distinguishing\n\t * a JSON editor from a code editor, since both render with `ACodeEditor`. */\n\tlanguage?: string\n\t/**\n\t * Target doctype slug. Presence is what makes a field a link.\n\t *\n\t * How it renders is decided by `component`, not by this: `AFormLink` renders an\n\t * inline id-picker, while `AForm`/`ATable` expand the target (see `linkRenderMode`). Expansion\n\t * metadata — backlink, fetch strategy, authoritative cardinality — lives in the doctype's\n\t * `links` map, which is additive and never required for a plain foreign key.\n\t */\n\tdoctype?: string\n\t/** Human-readable label */\n\tlabel?: string\n\t/** CSS width (e.g. `\"40ch\"`, `\"200px\"`) */\n\twidth?: string\n\t/** CSS height (e.g. `\"100%\"`, `\"40vh\"`) — used by full-viewport fields such as Planner */\n\theight?: string\n\t/** Text alignment */\n\talign?: 'left' | 'center' | 'right' | 'start' | 'end'\n\t/** Whether the field is editable in table cell context */\n\tedit?: boolean\n\t/** Input mask pattern or serialized function */\n\tmask?: string\n\t/** Serialized display formatter — distinct from `mask` (input). Spreads through\n\t * `schemaToColumns` to `ColumnSchema.format`; deserialized at render time by ATable's\n\t * `getFormattedValue`. Returns a plain string, HTML, or a {@link BadgeDescriptor} for badge\n\t * cells. When a descriptor is returned it wins over any badge map on `options`. */\n\tformat?: string\n\t/** Per-field interaction mode override */\n\tmode?: InteractionMode\n\t/** Type-specific options: Select choices, Decimal precision config, etc. A link's target is not\n\t * here — it is `doctype`. */\n\toptions?: FieldOptions\n\t/** Whether the field is required */\n\trequired?: boolean\n\t/** Whether the field is read-only */\n\treadOnly?: boolean\n\t/** Whether the field is hidden from the UI */\n\thidden?: boolean\n\t/** Default value for new records */\n\tdefault?: unknown\n\t/** Validation configuration */\n\tvalidation?: FieldValidation\n\t/** Cardinality for Link fields — authoritative value on LinkDeclaration takes precedence */\n\tcardinality?: 'atMostOne' | 'one' | 'noneOrMany' | 'atLeastOne'\n\t/**\n\t * Provenance marker — stamped only by the GraphQL converter; absence means hand-authored.\n\t * When present, the docbuilder freezes the field's identity set (`fieldname`, `primaryKey`,\n\t * `required`, `options`, `cardinality`, `doctype`), since `fieldname` is the GraphQL/column\n\t * binding and `doctype` is the FK's target. `component` is deliberately **not** frozen: it\n\t * chooses the widget, which is an authoring decision the database has no opinion about.\n\t */\n\tsource?: 'introspected'\n\t/** View configuration when this link field expands to a table (`ATable`). */\n\tconfig?: TableViewConfig\n}\n\n/**\n * A layout container that groups other fields. Resolves to a nested AForm.\n * @public\n */\nexport interface FieldsetField {\n\t/** Discriminator — identifies this as a fieldset container */\n\tkind: 'fieldset'\n\t/** Unique identifier for this fieldset within its doctype */\n\tfieldname: string\n\t/** Vue component to render this fieldset. Defaults to `'AFieldset'` in resolveSchema. */\n\tcomponent?: string\n\t/** Human-readable label for the fieldset legend */\n\tlabel?: string\n\t/** Whether the fieldset can be collapsed */\n\tcollapsible?: boolean\n\t/** Interaction mode for all children inside this fieldset */\n\tmode?: InteractionMode\n\t/** Nested field definitions — resolved recursively by resolveSchema */\n\tschema: DoctypeField[]\n}\n\n/**\n * An inline table whose columns are defined directly in the schema (no linked doctype).\n * Use when the table data does not warrant a separate doctype.\n * @public\n */\nexport interface TableField {\n\t/** Discriminator — identifies this as an inline table */\n\tkind: 'table'\n\t/** Unique identifier for this table within its doctype */\n\tfieldname: string\n\t/** Vue component to render this table. Defaults to `'ATable'` in resolveSchema. */\n\tcomponent?: string\n\t/** Human-readable label */\n\tlabel?: string\n\t/** Column definitions — use ColumnSchema (fieldname key) from \\@stonecrop/schema */\n\tcolumns: ColumnSchema[]\n\t/** View configuration — defaults to `{ view: 'list' }` in resolveSchema when absent */\n\tconfig?: TableViewConfig\n\t/** Interaction mode for all cells inside this table */\n\tmode?: InteractionMode\n}\n\n/**\n * Union of all authoring-time field variants.\n * Use `kind` to discriminate: `'field'` | `'fieldset'` | `'table'`.\n * @public\n */\nexport type DoctypeField = ValueField | FieldsetField | TableField\n\n// ---------------------------------------------------------------------------\n// Zod runtime validation schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Which of the three field shapes an entry has, read from the entry's own structure.\n *\n * The single definition of that question. It had three copies before this — the parser's\n * `injectKind`, {@link stripFieldKind}'s agreement check, and the docbuilder's own\n * `isValueField` in another package — each free to drift, and drift here re-types a field rather\n * than throwing: a value field read as a fieldset loses its column, a fieldset read as a value\n * field loses every child.\n *\n * Deliberately **shape-only**: a declared `kind` is ignored. Two callers depend on that. The\n * stripper compares this against the declaration to decide whether removing it is lossless, which\n * it cannot do if this honours it. The docbuilder reads raw JSON off disk and classifies entries to\n * decide which to render as editable rows — and `kind` is Stonecrop's own discriminant, not\n * something a doctype author writes, so a tool reading a file has no business consulting it.\n *\n * `injectKind` is the one place a declaration still wins, and only to leave an already-parsed\n * object untouched on its way back through.\n *\n * @param field - a field entry, authored or parsed\n * @returns the kind its shape implies\n * @public\n */\nexport function inferFieldKind(field: unknown): DoctypeField['kind'] {\n\tif (typeof field !== 'object' || field === null || Array.isArray(field)) return 'field'\n\tif ('schema' in field) return 'fieldset'\n\tif ('columns' in field) return 'table'\n\treturn 'field'\n}\n\n/**\n * Infers the `kind` discriminant from the structural properties of a raw field\n * object, then injects it if absent. This allows authored JSON to omit `kind`\n * entirely — a `schema` key means fieldset, `columns` means table, anything else\n * is a value field.\n *\n * Rules (applied in order):\n * has `schema` → fieldset\n * has `columns` → table\n * otherwise → field (value-holding scalar or link)\n *\n * Objects that already carry `kind` pass through unchanged (backward-compatible).\n *\n * Single-node only. Zod applies this at every level of the discriminated union (via the\n * `z.lazy` in the fieldset schema), so nested fieldset children are normalized during a\n * parse. Callers that bypass Zod — notably `Doctype.fromObject` — must use the exported\n * {@link normalizeFieldKind} instead, which replicates that recursion.\n */\nfunction injectKind(data: unknown): unknown {\n\tif (typeof data !== 'object' || data === null || Array.isArray(data)) return data\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- safe: non-null, non-array object verified by guards above\n\tconst obj = data as Record<string, unknown>\n\t// An explicit `kind` is left exactly as it was found: this is the one place a declaration still\n\t// beats the shape, and only so an already-parsed object survives a second pass unchanged.\n\t// Returning `data` itself rather than rebuilding it also preserves identity and key order.\n\t// Nothing outside this function shares that precedence — a reader classifying a file on disk\n\t// wants `inferFieldKind`, because `kind` is ours and no author writes it.\n\tif ('kind' in obj) return data\n\treturn { kind: inferFieldKind(obj), ...obj }\n}\n\n/**\n * Recursively injects the `kind` discriminant into a raw field object and, for fieldsets,\n * into each of its nested `schema` children — mirroring exactly what Zod's `preprocess`\n * does at every level of the discriminated union.\n *\n * Table `columns` are {@link ColumnSchema} entries, not `DoctypeField`s, so they are left\n * untouched — the Zod table schema validates them with a plain passthrough and never injects\n * `kind` there either.\n *\n * Needed because `Doctype.fromObject` constructs a Doctype without running Zod, yet the\n * registry's `resolveFields` gates link and fieldset handling on `field.kind`. Without this,\n * a JSON-authored link resolves to a flat scalar and a fieldset's children are dropped.\n *\n * @public\n */\nexport function normalizeFieldKind(field: unknown): unknown {\n\tconst injected = injectKind(field)\n\tif (typeof injected !== 'object' || injected === null) return injected\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- injectKind returns a non-null object for object input; guarded above\n\tconst obj = injected as Record<string, unknown>\n\tif (obj.kind === 'fieldset' && Array.isArray(obj.schema)) {\n\t\treturn { ...obj, schema: obj.schema.map(normalizeFieldKind) }\n\t}\n\treturn injected\n}\n\n/**\n * Remove the `kind` discriminant from a field, recursing into a fieldset's children.\n *\n * The outbound half of the boundary {@link normalizeFieldKind} owns inbound. `kind` is a\n * discriminated-union tag the parser synthesizes, not something an author writes, so nothing that\n * *writes* a doctype should put it on disk — the generator and the docbuilder's save both call\n * this. Without it the two round-trip asymmetrically: every save adds a key the file never had.\n *\n * Strips only when `injectKind` would restore exactly what was removed. A fieldset carrying no\n * `schema` re-infers as a plain field, so its `kind` is kept rather than silently re-typing the\n * document; `DoctypeMeta` requires `schema` on a fieldset, so that shape is already invalid and\n * belongs to the load gate, not here.\n *\n * Table `columns` are {@link ColumnSchema} entries rather than `DoctypeField`s and never carry an\n * injected `kind`, so they are passed through untouched — the same asymmetry `injectKind` has.\n *\n * @param field - a field object, as held in memory after parsing\n * @returns the field without `kind`, safe to serialize\n * @public\n */\nexport function stripFieldKind(field: unknown): unknown {\n\tif (typeof field !== 'object' || field === null || Array.isArray(field)) return field\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- safe: non-null, non-array object verified by the guard above\n\tconst obj = field as Record<string, unknown>\n\n\tif (obj.kind !== undefined && obj.kind !== inferFieldKind(obj)) return field\n\n\tconst { kind: _kind, ...rest } = obj\n\tif (Array.isArray(rest.schema)) {\n\t\treturn { ...rest, schema: rest.schema.map(stripFieldKind) }\n\t}\n\treturn rest\n}\n\n/**\n * The field properties a `source: 'introspected'` marker freezes — the ones the database owns.\n *\n * This is the single definition of the identity set. The docbuilder greys these inputs on an\n * introspected field, and the converter's merge refuses to rewrite them. Stating it twice is how\n * the two drift, so both read this constant.\n *\n * Everything absent from this list is author-owned, `component` most importantly: it chooses the\n * widget, which is an authoring decision the database has no opinion about.\n *\n * @public\n */\nexport const INTROSPECTED_IDENTITY_PROPS = [\n\t'fieldname',\n\t'primaryKey',\n\t'required',\n\t'options',\n\t'cardinality',\n\t'doctype',\n] as const\n\n/**\n * Find the field a doctype marks as its primary key, or `undefined` when none is marked.\n *\n * This is the single definition of \"which field identifies a record\". Both sides depend on it:\n * the middleware builds the SQL identity predicate from it, and the client resolves a record's\n * route/store key from it. Call this; never re-derive the rule at the call site, or the two will\n * drift and the client will key records by a column the server never queried.\n *\n * Two deliberate rules, both matching the shape `primaryKey` actually has:\n * - Fieldset children are **included**, via {@link flattenFields}. A fieldset is layout, not\n * scope: its children are fields of the doctype with columns of their own, which is why the\n * adapter's SELECT already descends and why `getDisplayField` does too. Scanning top level only\n * did not *refuse* a nested declaration — it ignored one, so an author marked identity and\n * nothing honoured it and nothing said so.\n * - The **first** match in document order wins. Identity is single-valued by design — a doctype\n * describes the API surface, and mapping a composite database key onto one identity there is the\n * adapter's job — so a doctype declaring several is malformed rather than composite.\n * `DoctypeMeta` rejects that at the load gate; this stays total for callers holding fields that\n * never went through it.\n *\n * @param fields - the doctype's fields; fieldset children are descended into\n * @returns the primary-key field, or `undefined` for a PK-less doctype\n * @public\n */\nexport function getPrimaryKeyField(fields: readonly DoctypeField[]): ValueField | undefined {\n\treturn flattenFields(fields).find((f): f is ValueField => f.kind === 'field' && Boolean(f.primaryKey))\n}\n\n/**\n * Resolve the field a doctype nominates as its display text, or `undefined` when the nomination\n * does not name a readable column.\n *\n * This is the single definition of \"is this a usable `displayField`\". Both sides depend on it:\n * `DoctypeMeta` refuses a bad nomination at the load gate, and the adapter builds a SELECT from\n * the field it returns. Call this; never re-derive the rule, or the gate and the query will\n * disagree about which nominations are legal — which they did, in both directions at once.\n *\n * Two things disqualify a nomination, and both are the doctype saying so itself:\n * - it names no field at all, fieldset children included\n * - it names a `computed` field, which is declared precisely to state it has no column, so a\n * SELECT built from it would reference a column the database does not have\n *\n * @param fields - the doctype's top-level fields\n * @param displayField - the nominated fieldname\n * @returns the nominated field, or `undefined` when it is not a readable column\n * @public\n */\nexport function getDisplayField(\n\tfields: readonly DoctypeField[],\n\tdisplayField: string | undefined\n): ValueField | undefined {\n\tif (!displayField) return undefined\n\treturn flattenFields(fields).find(\n\t\t(f): f is ValueField => f.kind === 'field' && !f.computed && f.fieldname === displayField\n\t)\n}\n\n/**\n * The name of the field a record is identified by: the declared `primaryKey`, or `id` when the\n * doctype declares none.\n *\n * The `id` fallback is load-bearing, not defensive — a surrogate-key doctype carries an `id`\n * column and marks no primary key, so \"nothing declared\" means `id`, not \"no identity\".\n *\n * This exists because that one-line rule had been restated at four sites — the client's\n * `Doctype.recordIdField`, both nuxt hosts' `recordLookupField`, and the Postgres adapter — and\n * the fourth had omitted the fallback, so a doctype the client keyed by `id` was one the adapter\n * could not look up at all. Call this; a fifth restatement is how they diverge again.\n *\n * The returned name is not guaranteed to be a declared field: a doctype that declares no\n * `primaryKey` and no `id` yields `'id'` regardless. An adapter that must build a SQL predicate\n * from it has to confirm the field exists and say so when it does not, because selecting a column\n * the doctype never declared returns nothing rather than failing.\n *\n * @param fields - the doctype's top-level fields\n * @returns the identifying fieldname\n * @public\n */\nexport function getRecordIdField(fields: readonly DoctypeField[]): string {\n\treturn getPrimaryKeyField(fields)?.fieldname ?? 'id'\n}\n\n/**\n * Resolve a record's identity value using the doctype's declared primary key.\n *\n * Falls back to `record.id` when the doctype declares no `primaryKey`. That fallback is\n * load-bearing, not defensive: surrogate-key doctypes carry an `id` column and never mark a\n * primary key, and PostGraphile renames a single-column `id` PK to `rowId` — so the declared\n * field and `id` are both real sources, in that order.\n *\n * @param fields - the doctype's top-level fields\n * @param record - the record to read the identity from\n * @returns the identity as a string, or `undefined` when neither source yields a usable value\n * @public\n */\nexport function getRecordIdentity(\n\tfields: readonly DoctypeField[],\n\trecord: Record<string, unknown>\n): string | undefined {\n\tconst pkField = getPrimaryKeyField(fields)\n\tconst candidates = pkField ? [record[pkField.fieldname], record.id] : [record.id]\n\n\tfor (const value of candidates) {\n\t\t// Numbers are valid keys (a serial PK); 0 is a legitimate id, so test the type, not truthiness.\n\t\tif (typeof value === 'number') return String(value)\n\t\tif (typeof value === 'string' && value !== '') return value\n\t}\n\treturn undefined\n}\n\nfunction createDoctypeFieldSchemas() {\n\tconst ValueFieldSchema = z\n\t\t.object({\n\t\t\tkind: z.literal('field'),\n\t\t\tfieldname: z.string().min(1),\n\t\t\tcomponent: z.string().min(1),\n\t\t\tprimaryKey: z.boolean().optional(),\n\t\t\tcomputed: z.boolean().optional(),\n\t\t\tlanguage: z.string().optional(),\n\t\t\tdoctype: z.string().min(1).optional(),\n\t\t\tlabel: z.string().optional(),\n\t\t\twidth: z.string().optional(),\n\t\t\theight: z.string().optional(),\n\t\t\talign: z.enum(['left', 'center', 'right', 'start', 'end']).optional(),\n\t\t\tedit: z.boolean().optional(),\n\t\t\tmask: z.string().optional(),\n\t\t\tformat: z.string().optional(),\n\t\t\tmode: z.enum(['edit', 'read', 'display']).optional(),\n\t\t\toptions: FieldOptions.optional(),\n\t\t\trequired: z.boolean().optional(),\n\t\t\treadOnly: z.boolean().optional(),\n\t\t\thidden: z.boolean().optional(),\n\t\t\tdefault: z.unknown().optional(),\n\t\t\tvalidation: FieldValidation.optional(),\n\t\t\tcardinality: z.enum(['atMostOne', 'one', 'noneOrMany', 'atLeastOne']).optional(),\n\t\t\tsource: z.literal('introspected').optional(),\n\t\t\tconfig: TableViewConfig.optional(),\n\t\t})\n\t\t.meta({ title: 'ValueField' })\n\n\tconst TableFieldSchema = z\n\t\t.object({\n\t\t\tkind: z.literal('table'),\n\t\t\tfieldname: z.string().min(1),\n\t\t\tcomponent: z.string().optional(),\n\t\t\tlabel: z.string().optional(),\n\t\t\t// Validates that each column has fieldname; allows all other ColumnSchema properties\n\t\t\tcolumns: z.array(z.object({ fieldname: z.string().min(1) }).passthrough()),\n\t\t\tconfig: TableViewConfig.optional(),\n\t\t\tmode: z.enum(['edit', 'read', 'display']).optional(),\n\t\t})\n\t\t.meta({ title: 'TableField' })\n\n\t// DoctypeFieldSchema must be declared before FieldsetFieldSchema so the z.lazy\n\t// callback can close over it. The placeholder is overwritten below; the callback\n\t// only runs at parse time, after the real discriminated union is assigned.\n\t// See: https://zod.dev/api?id=discriminated-unions#discriminated-unions\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- required by Zod's recursive schema pattern; z.never() placeholder is overwritten before any parse call\n\tlet DoctypeFieldSchema: z.ZodType<DoctypeField> = z.never() as unknown as z.ZodType<DoctypeField>\n\n\t// FieldsetFieldSchema stays as a plain ZodObject (not z.ZodType<T>) so that\n\t// z.discriminatedUnion can inspect its 'kind' discriminant property.\n\tconst FieldsetFieldSchema = z\n\t\t.object({\n\t\t\tkind: z.literal('fieldset'),\n\t\t\tfieldname: z.string().min(1),\n\t\t\tcomponent: z.string().optional(),\n\t\t\tlabel: z.string().optional(),\n\t\t\tcollapsible: z.boolean().optional(),\n\t\t\tmode: z.enum(['edit', 'read', 'display']).optional(),\n\t\t\tschema: z.lazy(() => DoctypeFieldSchema.array()),\n\t\t})\n\t\t.meta({ title: 'FieldsetField' })\n\n\tconst rawUnion = z.discriminatedUnion('kind', [ValueFieldSchema, FieldsetFieldSchema, TableFieldSchema])\n\n\t// Overwrite the placeholder with the preprocessed schema. Because z.lazy captures\n\t// DoctypeFieldSchema by closure reference, the lazy callback in FieldsetFieldSchema\n\t// will resolve to this preprocessed version — so nested fieldsets also inject `kind`.\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- ZodPipe output is DoctypeField; same pattern as the z.never() placeholder above\n\tDoctypeFieldSchema = z.preprocess(injectKind, rawUnion) as unknown as z.ZodType<DoctypeField>\n\n\treturn { ValueFieldSchema, TableFieldSchema, FieldsetFieldSchema, DoctypeFieldSchema }\n}\n\nconst schemas = createDoctypeFieldSchemas()\n\n/**\n * Zod runtime validation schema for ValueField.\n * @public\n */\nexport const ValueFieldSchema = schemas.ValueFieldSchema\n\n/**\n * Zod runtime validation schema for FieldsetField.\n * Recursive — FieldsetField.schema is validated against DoctypeFieldSchema.\n * @public\n */\nexport const FieldsetFieldSchema = schemas.FieldsetFieldSchema\n\n/**\n * Zod runtime validation schema for TableField.\n * @public\n */\nexport const TableFieldSchema = schemas.TableFieldSchema\n\n/**\n * Zod runtime validation schema for the DoctypeField discriminated union.\n * Validates all three field variants: `'field'`, `'fieldset'`, `'table'`.\n * @public\n */\nexport const DoctypeFieldSchema = schemas.DoctypeFieldSchema\n","/**\n * Naming Convention Utilities\n * Converts between various naming conventions (snake_case, camelCase, PascalCase, kebab-case)\n * @packageDocumentation\n */\n\n/**\n * Converts snake_case to camelCase\n * @param snakeCase - Snake case string\n * @returns Camel case string\n * @public\n * @example\n * ```typescript\n * snakeToCamel('user_email') // 'userEmail'\n * snakeToCamel('created_at') // 'createdAt'\n * ```\n */\nexport function snakeToCamel(snakeCase: string): string {\n\treturn snakeCase.replace(/_([a-z])/g, (_: string, letter: string) => letter.toUpperCase())\n}\n\n/**\n * Converts camelCase to snake_case\n * @param camelCase - Camel case string\n * @returns Snake case string\n * @public\n * @example\n * ```typescript\n * camelToSnake('userEmail') // 'user_email'\n * camelToSnake('createdAt') // 'created_at'\n * ```\n */\nexport function camelToSnake(camelCase: string): string {\n\treturn camelCase.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`)\n}\n\n/**\n * Converts snake_case to Title Case label\n * @param snakeCase - Snake case string\n * @returns Title case label\n * @public\n * @example\n * ```typescript\n * snakeToLabel('user_email') // 'User Email'\n * snakeToLabel('first_name') // 'First Name'\n * ```\n */\nexport function snakeToLabel(snakeCase: string): string {\n\treturn snakeCase\n\t\t.split('_')\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join(' ')\n}\n\n/**\n * Converts camelCase to Title Case label\n * @param camelCase - Camel case string\n * @returns Title case label\n * @public\n * @example\n * ```typescript\n * camelToLabel('userEmail') // 'User Email'\n * camelToLabel('firstName') // 'First Name'\n * ```\n */\nexport function camelToLabel(camelCase: string): string {\n\tconst withSpaces = camelCase.replace(/([A-Z])/g, ' $1').trim()\n\treturn withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1)\n}\n\n/**\n * Convert table name to PascalCase doctype name\n * @param tableName - SQL table name (snake_case)\n * @returns PascalCase name\n * @public\n */\nexport function toPascalCase(tableName: string): string {\n\treturn tableName\n\t\t.split(/[-_\\s]+/)\n\t\t.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n\t\t.join('')\n}\n\n/**\n * Convert to kebab-case slug\n * @param name - Name to convert\n * @returns kebab-case slug\n * @public\n */\nexport function toSlug(name: string): string {\n\treturn name\n\t\t.replace(/([a-z])([A-Z])/g, '$1-$2')\n\t\t.replace(/[\\s_]+/g, '-')\n\t\t.toLowerCase()\n}\n\n/**\n * Convert PascalCase to snake_case (e.g., for deriving table names from type names)\n * @param pascal - PascalCase string\n * @returns snake_case string\n * @public\n * @example\n * ```typescript\n * pascalToSnake('SalesOrder') // 'sales_order'\n * pascalToSnake('SalesOrderItem') // 'sales_order_item'\n * ```\n */\nexport function pascalToSnake(pascal: string): string {\n\treturn pascal\n\t\t.replace(/([a-z])([A-Z])/g, '$1_$2')\n\t\t.replace(/[\\s-]+/g, '_')\n\t\t.toLowerCase()\n}\n","import { z } from 'zod'\n\nimport { DoctypeFieldSchema, flattenFields, getDisplayField } from './field'\nimport { toSlug } from './naming'\n\n/**\n * Cardinality for relationship links.\n * @public\n */\nexport const Cardinality = z.enum(['atMostOne', 'one', 'noneOrMany', 'atLeastOne']).meta({\n\ttitle: 'Cardinality',\n\tdescription: 'Cardinality for relationship links between doctypes',\n})\n\n/**\n * Cardinality type inferred from Zod schema\n * @public\n */\nexport type Cardinality = z.infer<typeof Cardinality>\n\n/**\n * Serialized function type - a function serialized to a string.\n * Used for custom fetch handlers.\n * @public\n */\nexport type SerializedFunction = string\n\n/**\n * Sync fetch strategy - data is fetched in the initial query.\n * @public\n */\nexport const SyncFetch = z\n\t.object({\n\t\t/** Fetch method type */\n\t\tmethod: z.literal('sync'),\n\t\t/** Optional limit on number of records to fetch */\n\t\tlimit: z.number().int().positive().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'SyncFetch',\n\t\tdescription: 'Sync fetch strategy - data is fetched in the initial query',\n\t})\n\n/**\n * Sync fetch strategy type\n * @public\n */\nexport type SyncFetch = z.infer<typeof SyncFetch>\n\n/**\n * Lazy fetch strategy - data is fetched on demand in a separate query.\n * @public\n */\nexport const LazyFetch = z\n\t.object({\n\t\t/** Fetch method type */\n\t\tmethod: z.literal('lazy'),\n\t})\n\t.meta({\n\t\ttitle: 'LazyFetch',\n\t\tdescription: 'Lazy fetch strategy - data is fetched on demand in a separate query',\n\t})\n\n/**\n * Lazy fetch strategy type\n * @public\n */\nexport type LazyFetch = z.infer<typeof LazyFetch>\n\n/**\n * Custom fetch strategy - uses a custom handler function.\n * @public\n */\nexport const CustomFetch = z\n\t.object({\n\t\t/** Fetch method type */\n\t\tmethod: z.literal('custom'),\n\t\t/** Serialized handler function to invoke */\n\t\thandler: z.string(),\n\t})\n\t.meta({\n\t\ttitle: 'CustomFetch',\n\t\tdescription: 'Custom fetch strategy - uses a custom handler function',\n\t})\n\n/**\n * Custom fetch strategy type\n * @public\n */\nexport type CustomFetch = z.infer<typeof CustomFetch>\n\n/**\n * Fetch strategy for link data loading.\n * - sync: fetched in the initial query\n * - lazy: fetched on demand in a separate query\n * - custom: uses a custom handler function\n * @public\n */\nexport const FetchStrategy = z.discriminatedUnion('method', [SyncFetch, LazyFetch, CustomFetch]).meta({\n\ttitle: 'FetchStrategy',\n\tdescription: 'Fetch strategy for link data loading',\n})\n\n/**\n * Fetch strategy type\n * @public\n */\nexport type FetchStrategy = z.infer<typeof FetchStrategy>\n\n/**\n * Link declaration - describes a relationship from one doctype to another.\n * @public\n */\nexport const LinkDeclaration = z\n\t.object({\n\t\t/** Target doctype slug */\n\t\ttarget: z.string().min(1),\n\n\t\t/** Cardinality of the relationship */\n\t\tcardinality: Cardinality,\n\n\t\t/** Backlink fieldname on the target doctype that points back to this link */\n\t\tbacklink: z.string().optional(),\n\n\t\t/** Override default rendering component (AForm for 1:1, ATable for 1:many) */\n\t\tcomponent: z.string().optional(),\n\n\t\t/** Fieldname of the corresponding Link field in the fields array */\n\t\tfieldname: z.string().min(1).optional(),\n\n\t\t/** Fetch strategy for loading nested data */\n\t\tfetch: FetchStrategy.optional(),\n\n\t\t/** Whether to block workflow actions until nested data is loaded (default: true) */\n\t\tblockWorkflows: z.boolean().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'LinkDeclaration',\n\t\tdescription: 'Declares a relationship from one doctype to another',\n\t})\n\n/**\n * Link declaration type inferred from Zod schema\n * @public\n */\nexport type LinkDeclaration = z.infer<typeof LinkDeclaration>\n\n/**\n * Action definition within a workflow\n * @public\n */\nexport const ActionDefinition = z\n\t.object({\n\t\t/** Display label for the action */\n\t\tlabel: z.string().min(1),\n\n\t\t/** Fields that must have values before action can execute */\n\t\trequiredFields: z.array(z.string()).optional(),\n\n\t\t/** Workflow states where this action is available */\n\t\tallowedStates: z.array(z.string()).optional(),\n\n\t\t/** The state the record transitions to after this action executes */\n\t\tnextState: z.string().optional(),\n\n\t\t/** True for stateless command actions with no workflow effect at all (print, email, etc.) */\n\t\tstateless: z.boolean().optional(),\n\n\t\t/**\n\t\t * True for an internal self-transition: the action runs within the current state without\n\t\t * advancing the workflow (e.g. `save`, which mutates record data but stays put). Scoped by\n\t\t * `allowedStates`, rendered as a self-loop in the graph, and has no `nextState`. Distinct from\n\t\t * `stateless` (which has no workflow presence at all): a self-transition is graph-owned and,\n\t\t * unlike a stateless command, persists record data on dispatch.\n\t\t */\n\t\tselfTransition: z.boolean().optional(),\n\n\t\t/** JS function body stored as a string; executed client-side via AsyncFunction with injected API surface */\n\t\tclientHandler: z.string().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'ActionDefinition',\n\t\tdescription: 'Action definition within a workflow',\n\t})\n\n/**\n * Action definition type inferred from Zod schema\n * @public\n */\nexport type ActionDefinition = z.infer<typeof ActionDefinition>\n\n/**\n * Reactive field-validation trigger — advisory, client-side only.\n *\n * A Trigger is a docbuilder-authored validator: when any field in `on` is edited, its\n * `clientHandler` runs (client-side, no rollback) and may flag a field inline to block save\n * in the UI. It is deliberately a **sibling** to {@link (ActionDefinition:type)}, not a member of it —\n * a reactive validator is not a user-invoked action, so it lives in the `triggers` map on\n * {@link (WorkflowMeta:type)} and never appears to action readers (transition/command dropdowns, the FSM graph).\n *\n * The two bindings are independent: `on` is the fire-set (which fields' edits run it), while the\n * `setError(field, msg)` call inside `clientHandler` chooses which field displays the error.\n * @public\n */\nexport const TriggerDefinition = z\n\t.object({\n\t\t/** Optional display label; the map key is the trigger's identity */\n\t\tlabel: z.string().optional(),\n\n\t\t/** Fieldnames whose edits fire this trigger (fires when any listed field changes) */\n\t\ton: z.array(z.string()),\n\n\t\t/** JS function body stored as a string; run client-side with `{ record, value, setError }`. Advisory. */\n\t\tclientHandler: z.string(),\n\t})\n\t.meta({\n\t\ttitle: 'TriggerDefinition',\n\t\tdescription: 'Reactive field-validation trigger — advisory client-side',\n\t})\n\n/**\n * Trigger definition type inferred from Zod schema\n * @public\n */\nexport type TriggerDefinition = z.infer<typeof TriggerDefinition>\n\n/**\n * Whether a workflow action may run from `currentState`.\n *\n * Single source of truth for the \"is this action available here\" rule, shared by\n * the frontend (`getAvailableTransitions`) and the server-side dispatch guard so\n * the two can never disagree. Empty or absent `allowedStates` means the action is\n * available in ALL states — a plain `allowedStates.includes(currentState)` would\n * wrongly block such actions everywhere.\n *\n * @public\n */\nexport function isActionAllowedInState(action: { allowedStates?: string[] | null }, currentState: string): boolean {\n\tconst allowedStates = action.allowedStates\n\tif (!allowedStates || allowedStates.length === 0) return true\n\treturn allowedStates.includes(currentState)\n}\n\n/**\n * DocBuilder graph layout — node positions for the workflow-state graph, keyed by state name.\n * Pure authoring view-state: persisted in the doctype JSON so an author's manual arrangement\n * survives reloads, but — exactly like {@link (WorkflowMeta:type)}'s `triggers` — it is client-only\n * and never mirrored into the runtime GraphQL SDL (see the WorkflowMeta type in the host SDLs, which\n * expose only `states`/`actions`). The shape mirrors VueFlow's node fields; `position` is the node's\n * canvas coordinate and `targetPosition`/`sourcePosition` are the handle sides.\n * @public\n */\nexport const WorkflowLayout = z.record(\n\tz.string(),\n\tz.object({\n\t\tposition: z.object({ x: z.number(), y: z.number() }).optional(),\n\t\ttargetPosition: z.enum(['left', 'top', 'right', 'bottom']).optional(),\n\t\tsourcePosition: z.enum(['left', 'top', 'right', 'bottom']).optional(),\n\t})\n)\n\n/**\n * Workflow layout type inferred from Zod schema\n * @public\n */\nexport type WorkflowLayout = z.infer<typeof WorkflowLayout>\n\n/**\n * Workflow metadata - states and actions for a doctype\n * @public\n */\nexport const WorkflowMeta = z\n\t.object({\n\t\t/** List of workflow states */\n\t\tstates: z.array(z.string()).optional(),\n\n\t\t/** Actions available in this workflow */\n\t\tactions: z.record(z.string(), ActionDefinition).optional(),\n\n\t\t/** Reactive field-validation triggers (advisory, client-side), keyed by trigger name */\n\t\ttriggers: z.record(z.string(), TriggerDefinition).optional(),\n\n\t\t/**\n\t\t * DocBuilder node positions keyed by state name — authoring view-state. Persisted here so a\n\t\t * doctype author's manual graph arrangement survives reloads; like `triggers`, it is client-only\n\t\t * and never enters the runtime GraphQL SDL. See {@link (WorkflowLayout:variable)}.\n\t\t */\n\t\tlayout: WorkflowLayout.optional(),\n\t})\n\t.meta({\n\t\ttitle: 'WorkflowMeta',\n\t\tdescription: 'Workflow metadata - states and actions for a doctype',\n\t})\n\n/**\n * Workflow metadata type inferred from Zod schema\n * @public\n */\nexport type WorkflowMeta = z.infer<typeof WorkflowMeta>\n\n/**\n * Doctype metadata - complete definition of a doctype\n * @public\n */\nexport const DoctypeMeta = z\n\t.object({\n\t\t/** Display name of the doctype */\n\t\tname: z.string().min(1),\n\n\t\t/** URL-friendly slug (kebab-case) */\n\t\tslug: z.string().min(1).optional(),\n\n\t\t/**\n\t\t * Field on this doctype used when displaying a reference to one of its records.\n\t\t * When a record elsewhere holds an inline foreign key to this doctype, the middleware\n\t\t * returns that field as `{ id, displayText }`, reading `displayText` from this field.\n\t\t */\n\t\tdisplayField: z.string().min(1).optional(),\n\n\t\t/**\n\t\t * URL path this doctype registers at, written literally — `/order` for a collection,\n\t\t * `/order/:id` for a record. Absent means the doctype has no page of its own, which is the\n\t\t * common case: a child table is reached inside its parent, never at a URL.\n\t\t *\n\t\t * A path rather than a segment because the record parameter has to be somewhere, and a host\n\t\t * that reads a bare segment has to know which kind of doctype it is holding to decide where\n\t\t * to put it. Writing it out means nothing downstream re-derives it.\n\t\t */\n\t\troute: z.string().startsWith('/').optional(),\n\n\t\t/** Field definitions (a link field is one carrying `doctype`) */\n\t\tfields: z.array(DoctypeFieldSchema),\n\n\t\t/** Relationship links to other doctypes */\n\t\tlinks: z.record(z.string(), LinkDeclaration).optional(),\n\n\t\t/** Workflow configuration */\n\t\tworkflow: WorkflowMeta.optional(),\n\n\t\t/** Parent doctype for inheritance */\n\t\tinherits: z.string().optional(),\n\t})\n\t.meta({\n\t\ttitle: 'DoctypeMeta',\n\t\tdescription: 'Doctype metadata - complete definition of a doctype',\n\t})\n\t.superRefine((doctype, ctx) => {\n\t\t// A record is identified by exactly one field here, and that is the design rather than a\n\t\t// limitation awaiting composite support. A doctype describes the **API surface** a client\n\t\t// interacts with, not the table behind it; how a composite database key maps onto a single\n\t\t// identity on that surface is the server's business, and the client neither sees nor\n\t\t// encodes the parts. So there is nothing for a doctype-level composite key to express.\n\t\t//\n\t\t// Declaring several is therefore malformed, and silently so: `getPrimaryKeyField` takes the\n\t\t// first match and the rest are ignored, leaving an adapter to key records on a column that\n\t\t// need not be unique — `stonecropRecord`'s row map then keeps whichever row comes last.\n\t\t// Refusing at the gate is what makes it say so.\n\t\t//\n\t\t// Counts the flattened set, because that is the set `getPrimaryKeyField` resolves over. The\n\t\t// two asked different questions while this scanned top level only: a doctype with one key\n\t\t// declared at each level passed the gate and then had one of them silently dropped.\n\t\t//\n\t\t// Zero keys stays legal and is not an omission: a surrogate-key doctype declares none and\n\t\t// resolves through `getRecordIdField`'s documented `id` fallback.\n\t\tconst declared = flattenFields(doctype.fields).filter(f => f.kind === 'field' && f.primaryKey)\n\t\tif (declared.length > 1) {\n\t\t\tctx.addIssue({\n\t\t\t\tcode: 'custom',\n\t\t\t\tpath: ['fields'],\n\t\t\t\tmessage: `Doctype declares ${declared.length} primaryKey fields (${declared\n\t\t\t\t\t.map(f => (f.kind === 'field' ? f.fieldname : ''))\n\t\t\t\t\t.join(\n\t\t\t\t\t\t', '\n\t\t\t\t\t)}); a record is identified by exactly one field. A composite database key is mapped to a single identity by the adapter, so a doctype never declares its parts`,\n\t\t\t})\n\t\t}\n\n\t\t// Through `getDisplayField` rather than a scan written here, because the adapter builds its\n\t\t// SELECT from that same call. The two hand-rolled versions disagreed in both directions at\n\t\t// once: this gate scanned top-level only, so it rejected a fieldset-nested field that would\n\t\t// have worked, while neither side excluded `computed` fields, so a nomination naming one\n\t\t// passed the gate and then failed as a missing column at query time.\n\t\tif (doctype.displayField && !getDisplayField(doctype.fields, doctype.displayField)) {\n\t\t\tconst named = flattenFields(doctype.fields).find(f => f.fieldname === doctype.displayField)\n\t\t\tctx.addIssue({\n\t\t\t\tcode: 'custom',\n\t\t\t\tpath: ['displayField'],\n\t\t\t\tmessage: named\n\t\t\t\t\t? `displayField \"${doctype.displayField}\" names a computed field, which has no column to read a display value from`\n\t\t\t\t\t: `displayField \"${doctype.displayField}\" is not declared on this doctype`,\n\t\t\t})\n\t\t}\n\t})\n\n/**\n * Doctype metadata type inferred from Zod schema\n * @public\n */\nexport type DoctypeMeta = z.infer<typeof DoctypeMeta>\n\n/**\n * The one string a doctype is addressed by.\n *\n * A doctype carries two names — `name` (`OrderItem`) and `slug` (`order-item`) — and every registry\n * must agree on which one keys it. Three implementations had drifted apart: the adapter's registry is\n * keyed by `name` and its `getMeta` also scans for a matching `slug`, so it accepts **either**; the\n * client's registry is keyed by a slug it derives itself and accepts **only** that; and\n * `Doctype.fromObject` dropped an authored `slug` on the floor and re-derived one regardless. The\n * adapter's accepted set was therefore a strict superset of the client's, and a link target written\n * as the Name booted the server, passed its reference check, served rows over GraphQL, and was\n * silently dropped by the client — an expanding child table rendering as one empty text input, with\n * nothing logged.\n *\n * Resolving through this in both runtimes is what makes the two answers the same answer. It is the\n * derivation only; a *lookup* still belongs to whichever registry owns the corpus, because the two\n * corpora legitimately differ (a client registers lazily, and a client-only host has no adapter at\n * all).\n *\n * An authored `slug` wins over the derived one because the authored doctype is the source of truth:\n * generation verifies a file and never overwrites it, so a doctype that states its own slug means it.\n * Deriving unconditionally is what `fromObject` did, and it made an authored `slug` a silent no-op on\n * one side of the wire while the other honoured it.\n *\n * @param doctype - anything carrying a doctype's `name` and optional authored `slug`\n * @returns the canonical slug\n * @public\n *\n * @example\n * ```typescript\n * getDoctypeSlug({ name: 'OrderItem' }) // 'order-item'\n * getDoctypeSlug({ name: 'Planner', slug: 'planner-board' }) // 'planner-board'\n * ```\n */\nexport function getDoctypeSlug(doctype: { name: string; slug?: string }): string {\n\t// `||` rather than `??`: an empty authored slug is not a usable registry key, and this is\n\t// reachable — `Doctype.fromObject` builds a doctype without going through the Zod gate, which is\n\t// where `slug: z.string().min(1)` would have refused it.\n\treturn doctype.slug || toSlug(doctype.name)\n}\n\n/**\n * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.\n *\n * @deprecated The `__display` suffix pattern is no longer used. Inline link fields are enriched\n * server-side by `@stonecrop/graphql-middleware` as `{ id, displayText }` objects on the link\n * field itself.\n * @public\n */\nexport const LINK_DISPLAY_SUFFIX = '__display'\n\n/**\n * Build the payload key for a link field's display text (e.g. `customerId__display`).\n *\n * @deprecated The `__display` suffix pattern is no longer used. Inline link fields are enriched\n * server-side by `@stonecrop/graphql-middleware` as `{ id, displayText }` objects on the link\n * field itself.\n * @public\n */\nexport function linkDisplayFieldname(fieldname: string): string {\n\treturn `${fieldname}${LINK_DISPLAY_SUFFIX}`\n}\n\n/**\n * Context for identifying what doctype/record we're working with.\n * Used by graphql-middleware and graphql-client to resolve schema metadata.\n * @public\n */\nexport interface DoctypeContext {\n\t/** Doctype name (e.g., 'Task', 'Customer') */\n\tdoctype: string\n\t/** Optional record ID for viewing/editing a specific record */\n\trecordId?: string\n\t/** Additional context properties */\n\t[key: string]: unknown\n}\n\n/**\n * Base interface for doctype metadata passed to DataClient methods.\n * Only requires properties needed for record fetching.\n * @public\n */\nexport interface DoctypeRef {\n\t/** Doctype name (e.g., 'Task', 'Customer') */\n\tname: string\n\t/** URL-friendly slug (e.g., 'task', 'customer') */\n\tslug?: string\n}\n\n/**\n * Options for fetching a single record\n * @public\n */\nexport interface GetRecordOptions {\n\t/**\n\t * Include nested link sub-selections.\n\t * - `true`: include all descendant links\n\t * - `string[]`: include only named links\n\t * - `false` / omitted: scalar fields only (default)\n\t */\n\tincludeNested?: boolean | string[]\n\n\t/**\n\t * Maximum depth for recursive sub-selections.\n\t * No default — unlimited when omitted.\n\t */\n\tmaxDepth?: number\n}\n\n/**\n * Options for fetching multiple records\n * @public\n */\nexport interface GetRecordsOptions {\n\t/** Filter expression (field-value pairs) */\n\tfilters?: Record<string, unknown>\n\t/** Order by expression (e.g. 'NAME_ASC') */\n\torderBy?: string\n\t/** Maximum number of records to return */\n\tlimit?: number\n\t/** Number of records to skip */\n\toffset?: number\n\t/**\n\t * Ask the backend for the total matching the filters as well as the page.\n\t *\n\t * Off by default because it costs a second query — a full scan on Postgres — and knowing\n\t * *whether* more exist (`hasMore`) is what a list view actually needs. Turn it on for a\n\t * \"showing 20 of 4,312\" style display.\n\t */\n\tincludeTotal?: boolean\n}\n\n/**\n * Result from getRecord - includes the record data\n * @public\n */\nexport interface GetRecordResult {\n\t/** The record data, or null if not found */\n\trecord: Record<string, unknown> | null\n}\n\n/**\n * Result from getRecords — a page of records, and enough to tell that it is one.\n *\n * A bare array used to be returned here, which claimed to be the whole collection. It is not:\n * a limit always applies, so a caller could not distinguish a complete list from a truncated\n * one. That is the entire reason this type exists.\n *\n * @public\n */\nexport interface GetRecordsResult {\n\t/** The records in this page */\n\tdata: Record<string, unknown>[]\n\t/** Whether the backend holds further records beyond this page */\n\thasMore: boolean\n\t/**\n\t * Total records matching the filters, ignoring limit/offset. Present only when the caller\n\t * asked for it via {@link GetRecordsOptions.includeTotal} — counting is a full scan on most\n\t * backends, so it is never computed speculatively.\n\t */\n\tcount?: number\n}\n\n/**\n * Interface for data clients that fetch doctype metadata and records.\n * Implemented by \\@stonecrop/graphql-client's StonecropClient.\n * Custom implementations can use any backend (REST, local storage, etc.).\n *\n * @typeParam T - Doctype reference type for record operations (defaults to DoctypeRef)\n * @typeParam M - Doctype metadata return type for getMeta (defaults to DoctypeMeta)\n * @public\n */\nexport interface DataClient<T extends DoctypeRef = DoctypeRef, M = DoctypeMeta> {\n\t/**\n\t * Fetch doctype metadata\n\t * @param context - Doctype context identifying the doctype\n\t * @returns Doctype metadata or null if not found\n\t */\n\tgetMeta(context: DoctypeContext): Promise<M | null>\n\n\t/**\n\t * Fetch a single record by ID\n\t *\n\t * When `includeNested` is set, builds a query with sub-selections for descendant\n\t * links and returns ancestor + merged descendants. When omitted, returns flat scalar data.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param recordId - Record ID to fetch\n\t * @param options - Query options\n\t * @returns Record data wrapped in GetRecordResult\n\t */\n\tgetRecord(doctype: T, recordId: string, options?: GetRecordOptions): Promise<GetRecordResult>\n\n\t/**\n\t * Fetch a page of records\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param options - Query options\n\t * @returns The page, plus whether more exist and (on request) the total\n\t */\n\tgetRecords(doctype: T, options?: GetRecordsOptions): Promise<GetRecordsResult>\n\n\t/**\n\t * Execute a doctype action (e.g., SUBMIT, APPROVE, save).\n\t * All state changes flow through this single mutation endpoint.\n\t *\n\t * @param doctype - Doctype reference (name and optional slug)\n\t * @param action - Action name to execute (e.g., 'SUBMIT', 'APPROVE', 'save')\n\t * @param args - Action arguments (typically record ID and/or form data)\n\t * @returns Action result: success, what the action's handler returned (`data`), any error, and the\n\t * record as {@link DataClient.getRecord} returns it after the action (`record`, null when the\n\t * action failed or targets no record)\n\t */\n\trunAction(\n\t\tdoctype: T,\n\t\taction: string,\n\t\targs?: unknown[]\n\t): Promise<{ success: boolean; data: unknown; error: string | null; record: Record<string, unknown> | null }>\n}\n","import { DoctypeFieldSchema } from './field'\nimport { DoctypeMeta } from './doctype'\n\n/**\n * Validation error with path information\n * @public\n */\nexport interface ValidationError {\n\t/** Path to the invalid property */\n\tpath: PropertyKey[]\n\n\t/** Error message */\n\tmessage: string\n}\n\n/**\n * Result of a validation operation\n * @public\n */\nexport interface ValidationResult {\n\t/** Whether validation passed */\n\tsuccess: boolean\n\n\t/** List of validation errors (empty if success) */\n\terrors: ValidationError[]\n}\n\n/**\n * Validate a field definition against the DoctypeField discriminated union\n * @param data - Data to validate\n * @returns Validation result\n * @public\n */\nexport function validateField(data: unknown): ValidationResult {\n\tconst result = DoctypeFieldSchema.safeParse(data)\n\n\tif (result.success) {\n\t\treturn { success: true, errors: [] }\n\t}\n\n\treturn {\n\t\tsuccess: false,\n\t\terrors: result.error.issues.map(issue => ({\n\t\t\tpath: issue.path,\n\t\t\tmessage: issue.message,\n\t\t})),\n\t}\n}\n\n/**\n * Validate a doctype definition\n * @param data - Data to validate\n * @returns Validation result\n * @public\n */\nexport function validateDoctype(data: unknown): ValidationResult {\n\tconst result = DoctypeMeta.safeParse(data)\n\n\tif (result.success) {\n\t\treturn { success: true, errors: [] }\n\t}\n\n\treturn {\n\t\tsuccess: false,\n\t\terrors: result.error.issues.map(issue => ({\n\t\t\tpath: issue.path,\n\t\t\tmessage: issue.message,\n\t\t})),\n\t}\n}\n\n/**\n * Parse and validate a field, throwing on failure\n * @param data - Data to parse\n * @returns Validated DoctypeField\n * @throws ZodError if validation fails\n * @public\n */\nexport function parseField(data: unknown): import('./field').DoctypeField {\n\treturn DoctypeFieldSchema.parse(data)\n}\n\n/**\n * Parse and validate a doctype, throwing on failure\n * @param data - Data to parse\n * @returns Validated DoctypeMeta\n * @throws ZodError if validation fails\n * @public\n */\nexport function parseDoctype(data: unknown): DoctypeMeta {\n\treturn DoctypeMeta.parse(data)\n}\n\n// Re-export types for convenience\nexport type { DoctypeField, ValueField, FieldsetField, TableField } from './field'\nexport type { DoctypeMeta } from './doctype'\n","/**\n * GraphQL Scalar Type Mappings\n *\n * Maps standard GraphQL scalars and well-known custom scalars to Stonecrop field types.\n * Source-agnostic — covers scalars commonly emitted by PostGraphile, Hasura, Apollo, etc.\n *\n * Users can extend these via the `customScalars` option in `GraphQLConversionOptions`.\n *\n * @packageDocumentation\n */\n\nimport type { FieldTemplate } from './types'\n\n/**\n * Mapping from standard GraphQL scalar types to Stonecrop field types.\n * These are defined by the GraphQL specification and are always available.\n *\n * @public\n */\nexport const GQL_SCALAR_MAP: Record<string, FieldTemplate> = {\n\tString: { component: 'ATextInput' },\n\tInt: { component: 'ANumericInput' },\n\tFloat: { component: 'ANumericInput' },\n\tBoolean: { component: 'ACheckbox' },\n\tID: { component: 'ATextInput' },\n}\n\n/**\n * Mapping from well-known custom GraphQL scalars to Stonecrop field types.\n * These cover scalars commonly used across GraphQL servers (PostGraphile, Hasura, etc.)\n * without baking in knowledge of any specific server.\n *\n * Entries here have lower precedence than `customScalars` from options, but higher\n * precedence than unknown/unmapped scalars.\n *\n * @public\n */\nexport const WELL_KNOWN_SCALARS: Record<string, FieldTemplate> = {\n\t// Arbitrary precision / large numbers — all numeric variants render with ANumericInput.\n\tBigFloat: { component: 'ANumericInput' },\n\tBigDecimal: { component: 'ANumericInput' },\n\tDecimal: { component: 'ANumericInput' },\n\tBigInt: { component: 'ANumericInput' },\n\tLong: { component: 'ANumericInput' },\n\n\t// Identifiers\n\tUUID: { component: 'ATextInput' },\n\n\t// Date / Time — no dedicated Time SFC exists; Time falls back to a plain text input.\n\tDateTime: { component: 'ADateTime' },\n\tDatetime: { component: 'ADateTime' },\n\tDate: { component: 'ADate' },\n\tTime: { component: 'ATextInput' },\n\tInterval: { component: 'ADuration' },\n\tDuration: { component: 'ADuration' },\n\n\t// Structured data\n\tJSON: { component: 'ACodeEditor' },\n\tJSONObject: { component: 'ACodeEditor' },\n\tJsonNode: { component: 'ACodeEditor' },\n}\n\n/**\n * Set of scalar type names that are internal to GraphQL servers and should be skipped\n * during field conversion (they don't represent meaningful data fields).\n *\n * @public\n */\nexport const INTERNAL_SCALARS = new Set(['Cursor'])\n\n/**\n * Build a merged scalar map from the built-in maps and user-provided custom scalars.\n * Precedence (highest to lowest): customScalars → GQL_SCALAR_MAP → WELL_KNOWN_SCALARS\n *\n * @param customScalars - User-provided scalar overrides\n * @returns Merged scalar map\n * @public\n */\nexport function buildScalarMap(customScalars?: Record<string, Partial<FieldTemplate>>): Record<string, FieldTemplate> {\n\tconst merged: Record<string, FieldTemplate> = { ...WELL_KNOWN_SCALARS }\n\n\t// Standard scalars override well-known\n\tfor (const [key, value] of Object.entries(GQL_SCALAR_MAP)) {\n\t\tmerged[key] = value\n\t}\n\n\t// Custom scalars override everything\n\tif (customScalars) {\n\t\tfor (const [key, value] of Object.entries(customScalars)) {\n\t\t\tmerged[key] = { component: value.component ?? 'ATextInput' }\n\t\t}\n\t}\n\n\treturn merged\n}\n","/**\n * Default heuristics for identifying entity types and fields in a GraphQL schema.\n *\n * These heuristics work across common GraphQL servers (PostGraphile, Hasura, Apollo, etc.)\n * by detecting widely-adopted conventions like the Relay connection pattern.\n *\n * All heuristics can be overridden via the `isEntityType`, `isEntityField`, and\n * `classifyField` options in `GraphQLConversionOptions`.\n *\n * @packageDocumentation\n */\n\nimport {\n\tisScalarType,\n\tisEnumType,\n\tisObjectType,\n\tisListType,\n\tisNonNullType,\n\tisNamedType,\n\ttype GraphQLObjectType,\n\ttype GraphQLField,\n\ttype GraphQLOutputType,\n\ttype GraphQLNamedType,\n} from 'graphql'\n\nimport type { FieldTemplate } from './types'\nimport type { GraphQLConversionFieldMeta, GraphQLConversionOptions } from './types'\nimport { buildScalarMap, INTERNAL_SCALARS } from './scalars'\nimport { toSlug, camelToLabel, toPascalCase } from '../naming'\n\n/**\n * Suffixes that identify synthetic/framework types generated by GraphQL servers.\n * Types ending with these suffixes are typically not entities.\n */\nconst SYNTHETIC_SUFFIXES = [\n\t'Connection',\n\t'Edge',\n\t'Input',\n\t'Patch',\n\t'Payload',\n\t'Condition',\n\t'Filter',\n\t'OrderBy',\n\t'Aggregate',\n\t'AggregateResult',\n\t'AggregateFilter',\n\t'DeleteResponse',\n\t'InsertResponse',\n\t'UpdateResponse',\n\t'MutationResponse',\n]\n\n/**\n * Root operation type names that are never entities.\n */\nconst ROOT_TYPE_NAMES = new Set(['Query', 'Mutation', 'Subscription'])\n\n/**\n * Default heuristic to determine if a GraphQL object type represents an entity.\n * An entity type becomes a Stonecrop doctype.\n *\n * This heuristic excludes:\n * - Introspection types (`__*`)\n * - Root operation types (`Query`, `Mutation`, `Subscription`)\n * - Types with synthetic suffixes (e.g., `*Connection`, `*Edge`, `*Input`)\n * - Types starting with `Node` interface marker (exact match only)\n *\n * @param typeName - The GraphQL type name\n * @param type - The GraphQL object type definition\n * @returns `true` if this type should become a Stonecrop doctype\n * @public\n */\nexport function defaultIsEntityType(typeName: string, type: GraphQLObjectType): boolean {\n\t// Exclude introspection types\n\tif (typeName.startsWith('__')) {\n\t\treturn false\n\t}\n\n\t// Exclude root operation types\n\tif (ROOT_TYPE_NAMES.has(typeName)) {\n\t\treturn false\n\t}\n\n\t// Exclude the Node interface marker type\n\tif (typeName === 'Node') {\n\t\treturn false\n\t}\n\n\t// Exclude types matching synthetic suffixes\n\tfor (const suffix of SYNTHETIC_SUFFIXES) {\n\t\tif (typeName.endsWith(suffix)) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t// Must have at least one field\n\tconst fields = type.getFields()\n\tif (Object.keys(fields).length === 0) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n/**\n * Fields to skip by default on entity types.\n * These are internal to GraphQL servers and don't represent semantic data.\n *\n * Relay's global object identifier is deliberately absent: which field carries it is a\n * declaration, not a name. See {@link relayNodeIdField}.\n */\nconst SKIP_FIELDS = new Set(['__typename', 'clientMutationId'])\n\n/**\n * The name Relay's Object Identification spec gives its marker interface.\n */\nconst RELAY_NODE_INTERFACE = 'Node'\n\n/**\n * The field carrying Relay's global object identifier on this type, or `undefined` for a type that\n * declares none.\n *\n * Read off the interface rather than matched against a list of names, because the name is a server\n * setting: PostGraphile exposes it as `nodeIdFieldName`, which is `id` under the un-overridden\n * Amber preset, `nodeId` under Stonecrop's, and whatever a foreign host chose under theirs. A\n * hardcoded name is a snapshot of one of those, and gets it wrong in both directions at once — it\n * emits an opaque identifier as a column (whose every read then fails on a column that does not\n * exist), and drops a real column that happens to share the name.\n *\n * The interface must be Relay's marker and not a domain interface that shares its name, so it has\n * to declare exactly one field, a non-null `ID`, and nothing else — anything carrying domain fields\n * is a different interface, and skipping against it would drop real columns.\n *\n * @internal\n */\nfunction relayNodeIdField(type: GraphQLObjectType): string | undefined {\n\tfor (const iface of type.getInterfaces()) {\n\t\tif (iface.name !== RELAY_NODE_INTERFACE) continue\n\n\t\tconst declared = Object.values(iface.getFields())\n\t\tif (declared.length !== 1) continue\n\n\t\tconst { namedType, required, isList } = unwrapType(declared[0].type)\n\t\tif (required && !isList && namedType.name === 'ID') return declared[0].name\n\t}\n\n\treturn undefined\n}\n\n/**\n * Default heuristic to filter fields on entity types.\n * Skips internal fields that don't represent meaningful data.\n *\n * @param fieldName - The GraphQL field name\n * @param _field - The GraphQL field definition (unused in default implementation)\n * @param parentType - The parent entity type, whose interfaces declare its Relay identifier\n * @returns `true` if this field should be included\n * @public\n */\nexport function defaultIsEntityField(\n\tfieldName: string,\n\t_field: GraphQLField<unknown, unknown>,\n\tparentType: GraphQLObjectType\n): boolean {\n\tif (SKIP_FIELDS.has(fieldName)) return false\n\treturn fieldName !== relayNodeIdField(parentType)\n}\n\n/**\n * Unwrap NonNull and List wrappers from a GraphQL type, tracking nullability.\n *\n * @param type - The GraphQL output type\n * @returns The unwrapped named type, whether it's required, and whether it's a list\n * @internal\n */\nfunction unwrapType(type: GraphQLOutputType): {\n\tnamedType: GraphQLNamedType\n\trequired: boolean\n\tisList: boolean\n} {\n\tlet required = false\n\tlet isList = false\n\tlet current: GraphQLOutputType = type\n\n\t// Unwrap outer NonNull\n\tif (isNonNullType(current)) {\n\t\trequired = true\n\t\tcurrent = current.ofType\n\t}\n\n\t// Unwrap List\n\tif (isListType(current)) {\n\t\tisList = true\n\t\tcurrent = current.ofType\n\n\t\t// Unwrap inner NonNull (e.g., [Type!])\n\t\tif (isNonNullType(current)) {\n\t\t\tcurrent = current.ofType\n\t\t}\n\t}\n\n\t// At this point, current should be a named type (scalar, enum, or object)\n\tif (!isNamedType(current)) {\n\t\tthrow new Error(`Expected a named GraphQL type, got: ${String(current)}`)\n\t}\n\treturn { namedType: current, required, isList }\n}\n\n/**\n * Check if a GraphQL object type looks like a Relay Connection type.\n * A connection type has an `edges` field returning a list of edge types,\n * where each edge has a `node` field.\n *\n * @param type - The GraphQL object type to check\n * @returns The node type name if this is a connection, or `undefined`\n * @internal\n */\nfunction getConnectionNodeType(type: GraphQLObjectType): string | undefined {\n\tconst fields = type.getFields()\n\n\t// Must have an 'edges' field\n\tconst edgesField = fields['edges']\n\tif (!edgesField) return undefined\n\n\t// edges must be a list\n\tconst { namedType: edgesType, isList: edgesIsList } = unwrapType(edgesField.type)\n\tif (!edgesIsList || !isObjectType(edgesType)) return undefined\n\n\t// Each edge must have a 'node' field\n\tconst edgeFields = edgesType.getFields()\n\tconst nodeField = edgeFields['node']\n\tif (!nodeField) return undefined\n\n\tconst { namedType: nodeType } = unwrapType(nodeField.type)\n\tif (!isObjectType(nodeType)) return undefined\n\n\treturn nodeType.name\n}\n\n/**\n * Classify a single GraphQL field into a Stonecrop field definition.\n *\n * Classification rules (in order):\n * 1. Scalar types → look up in merged scalar map\n * 2. Enum types → `Select` with enum values as options\n * 3. Object types that are entities → `Link` with slug as options\n * 4. Object types that are Connections → `Doctype` with node type slug as options\n * 5. List of entity type → `Doctype` with item type slug as options\n * 6. Anything else → `Data` with `_unmapped: true`\n *\n * @param fieldName - The GraphQL field name\n * @param field - The GraphQL field definition\n * @param entityTypes - Set of type names classified as entities\n * @param options - Conversion options (for custom scalars, unmapped meta, etc.)\n * @returns The Stonecrop field definition\n * @public\n */\nexport function classifyFieldType(\n\tfieldName: string,\n\tfield: GraphQLField<unknown, unknown>,\n\tentityTypes: Set<string>,\n\toptions: GraphQLConversionOptions = {}\n): GraphQLConversionFieldMeta {\n\tconst { namedType, required, isList } = unwrapType(field.type)\n\tconst scalarMap = buildScalarMap(options.customScalars)\n\n\tconst base: GraphQLConversionFieldMeta = {\n\t\tkind: 'field',\n\t\tfieldname: fieldName,\n\t\tlabel: camelToLabel(fieldName),\n\t\tcomponent: 'ATextInput',\n\t}\n\n\tif (required) {\n\t\tbase.required = true\n\t}\n\n\t// 1. Scalar types\n\tif (isScalarType(namedType)) {\n\t\t// Skip internal scalars (e.g., Cursor)\n\t\tif (INTERNAL_SCALARS.has(namedType.name)) {\n\t\t\tbase._unmapped = true\n\t\t\tif (options.includeUnmappedMeta) {\n\t\t\t\tbase._graphqlType = namedType.name\n\t\t\t}\n\t\t\treturn base\n\t\t}\n\n\t\t// Special case: ID fields that reference an entity type → Link\n\t\tif (namedType.name === 'ID') {\n\t\t\tconst candidateTypeName = toPascalCase(fieldName)\n\t\t\tif (entityTypes.has(candidateTypeName)) {\n\t\t\t\tbase.component = 'AFormLink'\n\t\t\t\tbase.doctype = toSlug(candidateTypeName)\n\t\t\t\treturn base\n\t\t\t}\n\t\t}\n\n\t\tconst template: FieldTemplate | undefined = scalarMap[namedType.name]\n\t\tif (template) {\n\t\t\tbase.component = template.component\n\t\t} else {\n\t\t\t// Unknown scalar — default to Data with unmapped marker\n\t\t\tbase._unmapped = true\n\t\t\tif (options.includeUnmappedMeta) {\n\t\t\t\tbase._graphqlType = namedType.name\n\t\t\t}\n\t\t}\n\t\treturn base\n\t}\n\n\t// 2. Enum types → Select\n\tif (isEnumType(namedType)) {\n\t\tbase.component = 'ADropdown'\n\t\tbase.options = namedType.getValues().map(v => v.name)\n\t\treturn base\n\t}\n\n\t// 3–5. Object types\n\tif (isObjectType(namedType)) {\n\t\t// 3. Direct reference to an entity type → Link\n\t\tif (!isList && entityTypes.has(namedType.name)) {\n\t\t\tbase.component = 'AFormLink'\n\t\t\tbase.doctype = toSlug(namedType.name)\n\t\t\treturn base\n\t\t}\n\n\t\t// 4. Connection type → link (child table)\n\t\tconst connectionNodeTypeName = getConnectionNodeType(namedType)\n\t\tif (connectionNodeTypeName && entityTypes.has(connectionNodeTypeName)) {\n\t\t\tbase.component = 'ATable'\n\t\t\tbase._isLink = true\n\t\t\tbase.doctype = toSlug(connectionNodeTypeName)\n\t\t\tbase.cardinality = 'noneOrMany'\n\t\t\treturn base\n\t\t}\n\n\t\t// 5. List of entity type → link\n\t\tif (isList && entityTypes.has(namedType.name)) {\n\t\t\tbase.component = 'ATable'\n\t\t\tbase._isLink = true\n\t\t\tbase.doctype = toSlug(namedType.name)\n\t\t\tbase.cardinality = 'noneOrMany'\n\t\t\treturn base\n\t\t}\n\n\t\t// Unknown object type — mark as unmapped\n\t\tbase._unmapped = true\n\t\tif (options.includeUnmappedMeta) {\n\t\t\tbase._graphqlType = namedType.name\n\t\t}\n\t\treturn base\n\t}\n\n\t// Fallback — shouldn't normally be reached\n\tbase._unmapped = true\n\tif (options.includeUnmappedMeta) {\n\t\tbase._graphqlType = namedType.name\n\t}\n\treturn base\n}\n","/* global define */\n\n(function (root, pluralize) {\n /* istanbul ignore else */\n if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {\n // Node.\n module.exports = pluralize();\n } else if (typeof define === 'function' && define.amd) {\n // AMD, registers as an anonymous module.\n define(function () {\n return pluralize();\n });\n } else {\n // Browser global.\n root.pluralize = pluralize();\n }\n})(this, function () {\n // Rule storage - pluralize and singularize need to be run sequentially,\n // while other rules can be optimized using an object for instant lookups.\n var pluralRules = [];\n var singularRules = [];\n var uncountables = {};\n var irregularPlurals = {};\n var irregularSingles = {};\n\n /**\n * Sanitize a pluralization rule to a usable regular expression.\n *\n * @param {(RegExp|string)} rule\n * @return {RegExp}\n */\n function sanitizeRule (rule) {\n if (typeof rule === 'string') {\n return new RegExp('^' + rule + '$', 'i');\n }\n\n return rule;\n }\n\n /**\n * Pass in a word token to produce a function that can replicate the case on\n * another word.\n *\n * @param {string} word\n * @param {string} token\n * @return {Function}\n */\n function restoreCase (word, token) {\n // Tokens are an exact match.\n if (word === token) return token;\n\n // Lower cased words. E.g. \"hello\".\n if (word === word.toLowerCase()) return token.toLowerCase();\n\n // Upper cased words. E.g. \"WHISKY\".\n if (word === word.toUpperCase()) return token.toUpperCase();\n\n // Title cased words. E.g. \"Title\".\n if (word[0] === word[0].toUpperCase()) {\n return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase();\n }\n\n // Lower cased words. E.g. \"test\".\n return token.toLowerCase();\n }\n\n /**\n * Interpolate a regexp string.\n *\n * @param {string} str\n * @param {Array} args\n * @return {string}\n */\n function interpolate (str, args) {\n return str.replace(/\\$(\\d{1,2})/g, function (match, index) {\n return args[index] || '';\n });\n }\n\n /**\n * Replace a word using a rule.\n *\n * @param {string} word\n * @param {Array} rule\n * @return {string}\n */\n function replace (word, rule) {\n return word.replace(rule[0], function (match, index) {\n var result = interpolate(rule[1], arguments);\n\n if (match === '') {\n return restoreCase(word[index - 1], result);\n }\n\n return restoreCase(match, result);\n });\n }\n\n /**\n * Sanitize a word by passing in the word and sanitization rules.\n *\n * @param {string} token\n * @param {string} word\n * @param {Array} rules\n * @return {string}\n */\n function sanitizeWord (token, word, rules) {\n // Empty string or doesn't need fixing.\n if (!token.length || uncountables.hasOwnProperty(token)) {\n return word;\n }\n\n var len = rules.length;\n\n // Iterate over the sanitization rules and use the first one to match.\n while (len--) {\n var rule = rules[len];\n\n if (rule[0].test(word)) return replace(word, rule);\n }\n\n return word;\n }\n\n /**\n * Replace a word with the updated word.\n *\n * @param {Object} replaceMap\n * @param {Object} keepMap\n * @param {Array} rules\n * @return {Function}\n */\n function replaceWord (replaceMap, keepMap, rules) {\n return function (word) {\n // Get the correct token and case restoration functions.\n var token = word.toLowerCase();\n\n // Check against the keep object map.\n if (keepMap.hasOwnProperty(token)) {\n return restoreCase(word, token);\n }\n\n // Check against the replacement map for a direct word replacement.\n if (replaceMap.hasOwnProperty(token)) {\n return restoreCase(word, replaceMap[token]);\n }\n\n // Run all the rules against the word.\n return sanitizeWord(token, word, rules);\n };\n }\n\n /**\n * Check if a word is part of the map.\n */\n function checkWord (replaceMap, keepMap, rules, bool) {\n return function (word) {\n var token = word.toLowerCase();\n\n if (keepMap.hasOwnProperty(token)) return true;\n if (replaceMap.hasOwnProperty(token)) return false;\n\n return sanitizeWord(token, token, rules) === token;\n };\n }\n\n /**\n * Pluralize or singularize a word based on the passed in count.\n *\n * @param {string} word The word to pluralize\n * @param {number} count How many of the word exist\n * @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks)\n * @return {string}\n */\n function pluralize (word, count, inclusive) {\n var pluralized = count === 1\n ? pluralize.singular(word) : pluralize.plural(word);\n\n return (inclusive ? count + ' ' : '') + pluralized;\n }\n\n /**\n * Pluralize a word.\n *\n * @type {Function}\n */\n pluralize.plural = replaceWord(\n irregularSingles, irregularPlurals, pluralRules\n );\n\n /**\n * Check if a word is plural.\n *\n * @type {Function}\n */\n pluralize.isPlural = checkWord(\n irregularSingles, irregularPlurals, pluralRules\n );\n\n /**\n * Singularize a word.\n *\n * @type {Function}\n */\n pluralize.singular = replaceWord(\n irregularPlurals, irregularSingles, singularRules\n );\n\n /**\n * Check if a word is singular.\n *\n * @type {Function}\n */\n pluralize.isSingular = checkWord(\n irregularPlurals, irregularSingles, singularRules\n );\n\n /**\n * Add a pluralization rule to the collection.\n *\n * @param {(string|RegExp)} rule\n * @param {string} replacement\n */\n pluralize.addPluralRule = function (rule, replacement) {\n pluralRules.push([sanitizeRule(rule), replacement]);\n };\n\n /**\n * Add a singularization rule to the collection.\n *\n * @param {(string|RegExp)} rule\n * @param {string} replacement\n */\n pluralize.addSingularRule = function (rule, replacement) {\n singularRules.push([sanitizeRule(rule), replacement]);\n };\n\n /**\n * Add an uncountable word rule.\n *\n * @param {(string|RegExp)} word\n */\n pluralize.addUncountableRule = function (word) {\n if (typeof word === 'string') {\n uncountables[word.toLowerCase()] = true;\n return;\n }\n\n // Set singular and plural references for the word.\n pluralize.addPluralRule(word, '$0');\n pluralize.addSingularRule(word, '$0');\n };\n\n /**\n * Add an irregular word definition.\n *\n * @param {string} single\n * @param {string} plural\n */\n pluralize.addIrregularRule = function (single, plural) {\n plural = plural.toLowerCase();\n single = single.toLowerCase();\n\n irregularSingles[single] = plural;\n irregularPlurals[plural] = single;\n };\n\n /**\n * Irregular rules.\n */\n [\n // Pronouns.\n ['I', 'we'],\n ['me', 'us'],\n ['he', 'they'],\n ['she', 'they'],\n ['them', 'them'],\n ['myself', 'ourselves'],\n ['yourself', 'yourselves'],\n ['itself', 'themselves'],\n ['herself', 'themselves'],\n ['himself', 'themselves'],\n ['themself', 'themselves'],\n ['is', 'are'],\n ['was', 'were'],\n ['has', 'have'],\n ['this', 'these'],\n ['that', 'those'],\n // Words ending in with a consonant and `o`.\n ['echo', 'echoes'],\n ['dingo', 'dingoes'],\n ['volcano', 'volcanoes'],\n ['tornado', 'tornadoes'],\n ['torpedo', 'torpedoes'],\n // Ends with `us`.\n ['genus', 'genera'],\n ['viscus', 'viscera'],\n // Ends with `ma`.\n ['stigma', 'stigmata'],\n ['stoma', 'stomata'],\n ['dogma', 'dogmata'],\n ['lemma', 'lemmata'],\n ['schema', 'schemata'],\n ['anathema', 'anathemata'],\n // Other irregular rules.\n ['ox', 'oxen'],\n ['axe', 'axes'],\n ['die', 'dice'],\n ['yes', 'yeses'],\n ['foot', 'feet'],\n ['eave', 'eaves'],\n ['goose', 'geese'],\n ['tooth', 'teeth'],\n ['quiz', 'quizzes'],\n ['human', 'humans'],\n ['proof', 'proofs'],\n ['carve', 'carves'],\n ['valve', 'valves'],\n ['looey', 'looies'],\n ['thief', 'thieves'],\n ['groove', 'grooves'],\n ['pickaxe', 'pickaxes'],\n ['passerby', 'passersby']\n ].forEach(function (rule) {\n return pluralize.addIrregularRule(rule[0], rule[1]);\n });\n\n /**\n * Pluralization rules.\n */\n [\n [/s?$/i, 's'],\n [/[^\\u0000-\\u007F]$/i, '$0'],\n [/([^aeiou]ese)$/i, '$1'],\n [/(ax|test)is$/i, '$1es'],\n [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'],\n [/(e[mn]u)s?$/i, '$1s'],\n [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'],\n [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'],\n [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'],\n [/(seraph|cherub)(?:im)?$/i, '$1im'],\n [/(her|at|gr)o$/i, '$1oes'],\n [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'],\n [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'],\n [/sis$/i, 'ses'],\n [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'],\n [/([^aeiouy]|qu)y$/i, '$1ies'],\n [/([^ch][ieo][ln])ey$/i, '$1ies'],\n [/(x|ch|ss|sh|zz)$/i, '$1es'],\n [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'],\n [/\\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'],\n [/(pe)(?:rson|ople)$/i, '$1ople'],\n [/(child)(?:ren)?$/i, '$1ren'],\n [/eaux$/i, '$0'],\n [/m[ae]n$/i, 'men'],\n ['thou', 'you']\n ].forEach(function (rule) {\n return pluralize.addPluralRule(rule[0], rule[1]);\n });\n\n /**\n * Singularization rules.\n */\n [\n [/s$/i, ''],\n [/(ss)$/i, '$1'],\n [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\\w]|^)li)ves$/i, '$1fe'],\n [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'],\n [/ies$/i, 'y'],\n [/\\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i, '$1ie'],\n [/\\b(mon|smil)ies$/i, '$1ey'],\n [/\\b((?:tit)?m|l)ice$/i, '$1ouse'],\n [/(seraph|cherub)im$/i, '$1'],\n [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, '$1'],\n [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, '$1sis'],\n [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'],\n [/(test)(?:is|es)$/i, '$1is'],\n [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'],\n [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'],\n [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'],\n [/(alumn|alg|vertebr)ae$/i, '$1a'],\n [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'],\n [/(matr|append)ices$/i, '$1ix'],\n [/(pe)(rson|ople)$/i, '$1rson'],\n [/(child)ren$/i, '$1'],\n [/(eau)x?$/i, '$1'],\n [/men$/i, 'man']\n ].forEach(function (rule) {\n return pluralize.addSingularRule(rule[0], rule[1]);\n });\n\n /**\n * Uncountable rules.\n */\n [\n // Singular words with no plurals.\n 'adulthood',\n 'advice',\n 'agenda',\n 'aid',\n 'aircraft',\n 'alcohol',\n 'ammo',\n 'analytics',\n 'anime',\n 'athletics',\n 'audio',\n 'bison',\n 'blood',\n 'bream',\n 'buffalo',\n 'butter',\n 'carp',\n 'cash',\n 'chassis',\n 'chess',\n 'clothing',\n 'cod',\n 'commerce',\n 'cooperation',\n 'corps',\n 'debris',\n 'diabetes',\n 'digestion',\n 'elk',\n 'energy',\n 'equipment',\n 'excretion',\n 'expertise',\n 'firmware',\n 'flounder',\n 'fun',\n 'gallows',\n 'garbage',\n 'graffiti',\n 'hardware',\n 'headquarters',\n 'health',\n 'herpes',\n 'highjinks',\n 'homework',\n 'housework',\n 'information',\n 'jeans',\n 'justice',\n 'kudos',\n 'labour',\n 'literature',\n 'machinery',\n 'mackerel',\n 'mail',\n 'media',\n 'mews',\n 'moose',\n 'music',\n 'mud',\n 'manga',\n 'news',\n 'only',\n 'personnel',\n 'pike',\n 'plankton',\n 'pliers',\n 'police',\n 'pollution',\n 'premises',\n 'rain',\n 'research',\n 'rice',\n 'salmon',\n 'scissors',\n 'series',\n 'sewage',\n 'shambles',\n 'shrimp',\n 'software',\n 'species',\n 'staff',\n 'swine',\n 'tennis',\n 'traffic',\n 'transportation',\n 'trout',\n 'tuna',\n 'wealth',\n 'welfare',\n 'whiting',\n 'wildebeest',\n 'wildlife',\n 'you',\n /pok[eé]mon$/i,\n // Regexes.\n /[^aeiou]ese$/i, // \"chinese\", \"japanese\"\n /deer$/i, // \"deer\", \"reindeer\"\n /fish$/i, // \"fish\", \"blowfish\", \"angelfish\"\n /measles$/i,\n /o[iu]s$/i, // \"carnivorous\"\n /pox$/i, // \"chickpox\", \"smallpox\"\n /sheep$/i\n ].forEach(pluralize.addUncountableRule);\n\n return pluralize;\n});\n","/**\n * Aggregate doctype derivation.\n *\n * A table gets two generated doctypes: the entity itself, whose `fields` carry every column and\n * which backs the record form, and an **aggregate** — the collection view over the same table.\n * The aggregate starts with identity alone, because the useful default for a collection is the\n * one column that lets a row be opened, not all forty. Widening it is curation, and curation\n * survives regeneration (see `mergeIntrospectedDoctype`).\n *\n * The two are peers: each is a complete doctype with its own `name` and `slug`, and nothing here\n * encodes a relationship between them. Deriving the aggregate's name from the entity's is a\n * generated encoding, not a readable one — no consumer recovers the pair by parsing a slug.\n *\n * @packageDocumentation\n */\n\nimport pluralize from 'pluralize'\n\nimport { toSlug } from '../naming'\nimport { getDoctypeSlug } from '../doctype'\nimport { flattenFields, getPrimaryKeyField } from '../field'\nimport type { ValueField } from '../field'\nimport type { ConvertedGraphQLDoctype } from './types'\n\n/**\n * The name an entity's aggregate doctype is generated under: the entity's name, pluralised.\n *\n * One definition, because the CLI writes the file under `toSlug` of this and any later caller\n * (a scaffolder, a docs generator) must land on the same name or it silently addresses a\n * different file.\n *\n * `pluralize` rather than appending `s`, because the irregulars are not rare in practice —\n * measured against a consumer's 41 hand-authored aggregate doctypes, this rule reproduces every\n * one of their names, slugs and filenames exactly, while `+ 's'` gets five wrong\n * (`Currencys`, `JournalEntrys`, …).\n *\n * The rule is not total: an already-plural name pluralises to itself. Callers must handle that —\n * see {@link buildAggregateDoctype}.\n *\n * @param doctypeName - the entity doctype's `name`\n * @returns the aggregate doctype's `name`\n * @public\n *\n * @example\n * ```typescript\n * aggregateDoctypeName('SalesOrder') // 'SalesOrders' -> slug 'sales-orders'\n * ```\n */\nexport function aggregateDoctypeName(doctypeName: string): string {\n\treturn pluralize.plural(doctypeName)\n}\n\n/**\n * Derive the aggregate doctype for a converted entity.\n *\n * Returns `undefined` when no identity column can be found — a natural-key table whose key the\n * converter refuses to guess and whose author has not declared one, or a foreign PostGraphile\n * endpoint that has left the Relay identifier occupying `id` (Stonecrop's own preset moves it to\n * `nodeId`). That is deliberate: an aggregate with an empty `fields` array is a valid doctype that\n * renders a table with no columns, which looks like a data problem rather than a generation one.\n * Emitting nothing and saying so is the loud failure.\n *\n * Identity resolves the same way `getRecordIdField` resolves it — the declared `primaryKey`, then\n * the conventional `id` — so an aggregate is always keyed on the column the client will later ask\n * for. `declaredIdentity` overrides both: SDL cannot express which `UNIQUE` column is the key, so\n * for a natural-key table the answer only exists in the authored file, and the caller that read it\n * passes the fieldname back.\n *\n * @param doctype - a converted entity doctype, as returned by `convertGraphQLSchema`\n * @param declaredIdentity - fieldname the authored doctype declares as its `primaryKey`, when the\n * caller has read one. Must name a field the converter emitted; the caller checks that, because\n * only it can say whether a missing one is a dropped column or a typo.\n * @returns the aggregate doctype, or `undefined` when no identity column exists\n * @public\n *\n * @example\n * ```typescript\n * const [order] = convertGraphQLSchema(sdl, { include: ['Order'] })\n * const aggregate = buildAggregateDoctype(order)\n * // { name: 'Orders', slug: 'orders', fields: [ the id field ] }\n * ```\n */\nexport function buildAggregateDoctype(\n\tdoctype: ConvertedGraphQLDoctype,\n\tdeclaredIdentity?: string\n): ConvertedGraphQLDoctype | undefined {\n\tconst identity = findIdentityField(doctype.fields, declaredIdentity)\n\tif (!identity) return undefined\n\n\tconst name = aggregateDoctypeName(doctype.name)\n\t// An already-plural name pluralises to itself, which would give the aggregate the entity's own\n\t// `name` *and* its filename. Both write paths are silent about it: the CLI writes the file twice\n\t// in one run, and the middleware's registry is a Map keyed by name, so the later read wins in\n\t// whatever order `readdirSync` returns. Refusing is the only loud option.\n\tif (name === doctype.name) return undefined\n\n\t// `primaryKey` is stamped rather than copied through: a declared identity is not marked on the\n\t// converter's own field, and an aggregate whose one column carries no marker resolves identity\n\t// through `getRecordIdField`'s `id` fallback — a column it does not have, so every listed row is\n\t// silently dropped. Rebuilt with `source` last so the key order matches an entity's identity\n\t// field and both files stay byte-stable.\n\t//\n\t// A copy, not a reference: the two doctypes are written to separate files and an edit to one\n\t// must not reach the other.\n\tconst { source, ...rest } = identity\n\treturn {\n\t\tname,\n\t\tslug: toSlug(name),\n\t\tfields: [{ ...rest, primaryKey: true, ...(source === undefined ? {} : { source }) }],\n\t}\n}\n\n/**\n * The field an aggregate is keyed on: an identity the author declared, else the primary key the\n * converter derived, else the conventional `id`.\n *\n * The author wins because the authored doctype is the source of truth — generation verifies it and\n * never overwrites it (see `mergeIntrospectedDoctype`), and the divergence is already reported as\n * identity drift.\n *\n * Calls `getPrimaryKeyField` for the derived half rather than restating it: a restatement drifted\n * exactly as one does, staying top-level while the helper learned to descend into fieldsets.\n *\n * @internal\n */\nfunction findIdentityField(fields: readonly ValueField[], declared?: string): ValueField | undefined {\n\tif (declared !== undefined) return fields.find(field => field.fieldname === declared)\n\treturn getPrimaryKeyField(fields) ?? fields.find(field => field.fieldname === 'id')\n}\n\n/**\n * The doctypes in a run that get a URL of their own.\n *\n * A child table has no page: its rows exist inside a parent and are edited there, so a route for\n * it is an address nothing can link to. The declaration that says so is the parent's `links` entry\n * with a to-many cardinality — which the server derives from the foreign keys it treats as owning,\n * so this reads what the schema states rather than guessing from a name.\n *\n * The rule is *listed by something, referenced by nothing*. A single reference wins over any number\n * of listings, and the asymmetry is deliberate: a doctype that is both a parent's rows and another\n * doctype's link target — a recipe task, say, embedded in its recipe and pointed at by four other\n * records — needs somewhere for those links to navigate to. Denying it leaves the arrow on an\n * `AFormLink` dead, which fails silently; granting it leaves a URL nobody visits, which does not.\n *\n * Scoped to one run, so a partial generation sees a partial graph and grants more routes than a\n * whole one would. That is the safe direction, and the extra routes are deletable — an authored\n * file's keys survive regeneration untouched.\n *\n * @internal\n */\nfunction routableDoctypes(entities: readonly ConvertedGraphQLDoctype[]): Set<string> {\n\tconst listed = new Set<string>()\n\tconst referenced = new Set<string>()\n\n\tfor (const entity of entities) {\n\t\tfor (const link of Object.values(entity.links ?? {})) {\n\t\t\tif (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne') listed.add(link.target)\n\t\t\telse referenced.add(link.target)\n\t\t}\n\t\t// `flattenFields` rather than a top-level scan: a link inside a fieldset is still a reference,\n\t\t// and the two ways to answer this question have already drifted apart once.\n\t\tfor (const field of flattenFields(entity.fields)) {\n\t\t\tif ('doctype' in field && typeof field.doctype === 'string') referenced.add(field.doctype)\n\t\t}\n\t}\n\n\treturn new Set(\n\t\tentities\n\t\t\t.filter(entity => {\n\t\t\t\tconst slug = getDoctypeSlug(entity)\n\t\t\t\treturn referenced.has(slug) || !listed.has(slug)\n\t\t\t})\n\t\t\t.map(entity => entity.name)\n\t)\n}\n\n/**\n * One file the generator will write, and what that file is verified against.\n *\n * `basis` exists because the two are not always the same document. An aggregate is written from\n * its own one-field generation but verified against the **entity**, since its purpose is to carry\n * fewer columns than the table — checking it against itself reports every curated column as one\n * the table had dropped.\n *\n * @public\n */\nexport interface GenerationPlanEntry {\n\t/** The doctype to write. */\n\tgenerated: ConvertedGraphQLDoctype\n\t/** The doctype whose fields an existing file on disk is verified against. */\n\tbasis: ConvertedGraphQLDoctype\n\t/** Whether the file is a curated subset of `basis` — passed through to `MergeOptions.subset`. */\n\tsubset: boolean\n}\n\n/** Options for {@link planGeneration}. @public */\nexport interface GenerationPlanOptions {\n\t/** Emit only the entity doctypes, skipping their aggregates. Defaults to `false`. */\n\tnoAggregates?: boolean\n\t/** Called with an advisory message for each entity that yields no aggregate. */\n\tonWarning?: (message: string) => void\n\t/**\n\t * Identity the authored doctype on disk declares, keyed by doctype `name`.\n\t *\n\t * SDL cannot say which `UNIQUE` column is a table's key, so for a natural-key table the converter\n\t * derives nothing and the answer exists only in the file. Without this the aggregate is\n\t * unreachable: generation says \"declare a primaryKey and re-run\", and re-running after declaring\n\t * one changes nothing, because planning never reads the file.\n\t *\n\t * Passed in rather than read here so this stays a pure function of its inputs; the CLI owns the\n\t * IO. The plan is then a function of the schema *and* what is already on disk.\n\t */\n\tidentity?: Record<string, string>\n}\n\n/**\n * Expand converted entities into the set of doctype files to write.\n *\n * Each table yields two: the entity, whose fields carry every column and which backs the record\n * form, and its aggregate — the collection view. They are written as peers, one file each, with\n * no key relating them.\n *\n * Separate from the CLI because the pairing of a file to its verification basis is the part that\n * is easy to get wrong and impossible to notice: getting it wrong does not throw, it just reports\n * drift that is not there, forever.\n *\n * @param entities - `convertGraphQLSchema` output\n * @param options - see {@link GenerationPlanOptions}\n * @returns one entry per file to write\n * @public\n */\nexport function planGeneration(\n\tentities: readonly ConvertedGraphQLDoctype[],\n\toptions: GenerationPlanOptions = {}\n): GenerationPlanEntry[] {\n\tconst entityNames = new Set(entities.map(entity => entity.name))\n\tconst claimed = new Set<string>()\n\tconst routable = routableDoctypes(entities)\n\n\treturn entities.flatMap(entity => {\n\t\t// Written out in full rather than as a segment the host assembles: the record parameter has\n\t\t// to live somewhere, and a host given `/order` cannot know whether this doctype is the\n\t\t// collection or the record without asking a second question. The pair shares the entity's\n\t\t// slug, so no URL ever carries a plural.\n\t\tconst segment = `/${getDoctypeSlug(entity)}`\n\t\tconst routed = routable.has(entity.name) ? { ...entity, route: `${segment}/:id` } : entity\n\n\t\t// `basis` is the same object as `generated` for an entity — it is verified against itself.\n\t\tconst self: GenerationPlanEntry = { generated: routed, basis: routed, subset: false }\n\t\tif (options.noAggregates) return [self]\n\n\t\t// Name collisions are checked here rather than in the builder because only this function\n\t\t// holds the whole set. Reported before the identity check so each refusal names its own\n\t\t// cause — the two are repaired differently.\n\t\tconst name = aggregateDoctypeName(entity.name)\n\t\tif (name === entity.name) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name} is already plural, so its aggregate would take the same name and the same ` +\n\t\t\t\t\t`file. No aggregate was generated. Rename the doctype to its singular form, or author ` +\n\t\t\t\t\t`${entity.slug}.json's collection view by hand.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\t\tif (entityNames.has(name) || claimed.has(name)) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name}'s aggregate would be named ${name}, which is already taken by another ` +\n\t\t\t\t\t`doctype in this run. No aggregate was generated — one of the two needs an explicit ` +\n\t\t\t\t\t`name via the doctypeNames option.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\n\t\t// Checked here rather than in the builder because only the caller knows whether a name that\n\t\t// matches nothing is a dropped column or a typo — and an aggregate built around a field the\n\t\t// table has no column for renders a collection whose only column is absent from every row.\n\t\tconst declared = options.identity?.[entity.name]\n\t\tif (declared !== undefined && !entity.fields.some(field => field.fieldname === declared)) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name} declares its primaryKey on '${declared}', which the schema has no column for. ` +\n\t\t\t\t\t`No aggregate was generated — correct the declaration in ${entity.slug}.json, or restore the ` +\n\t\t\t\t\t`column to the table.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\n\t\tconst aggregate = buildAggregateDoctype(entity, declared)\n\t\tif (!aggregate) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${entity.name} has no derivable identity column, so no aggregate doctype was generated. ` +\n\t\t\t\t\t`Declare a primaryKey on ${entity.slug}.json and re-run.`\n\t\t\t)\n\t\t\treturn [self]\n\t\t}\n\t\tclaimed.add(name)\n\n\t\t// The basis is the entity itself: an aggregate is verified against the table it curates from,\n\t\t// not against its own one-field generation. Drift lines take their name from the authored file\n\t\t// being checked, so they already name the file the reader has to edit.\n\t\tconst listed = routable.has(entity.name) ? { ...aggregate, route: segment } : aggregate\n\t\treturn [self, { generated: listed, basis: entity, subset: true }]\n\t})\n}\n","/**\n * Reading an authored doctype — the JSON as it sits on disk, before any parsing.\n *\n * A separate reader from `@stonecrop/schema`'s `flattenFields`/`getPrimaryKeyField` because the two\n * operate on different *shapes*, not different rules: those take parsed `DoctypeField`s and branch\n * on the `kind` discriminant the Zod parser synthesizes, which authored JSON does not carry.\n * `getPrimaryKeyField` on a raw file therefore returns `undefined` — indistinguishable from \"no key\n * declared\", which is the exact condition its callers are testing.\n *\n * Every question about authored JSON is answered here once, so the rule cannot drift between the\n * merge and the generation plan.\n *\n * @internal\n */\n\n/**\n * A doctype as it exists on disk: a plain object that may carry keys this package does not model\n * (`handler` on an action, `filterFunction` on a field, whatever an app has added). Typing it\n * loosely is what lets the merge round-trip those keys untouched instead of dropping them.\n *\n * @public\n */\nexport type AuthoredDoctype = Record<string, unknown>\n\n/** @internal */\nexport function isAuthoredRecord(value: unknown): value is AuthoredDoctype {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Flatten authored fields, descending into fieldsets.\n *\n * A fieldset is a layout grouping, not a scope: a field inside one is still a field of the doctype,\n * with a column of its own and a key it may declare.\n *\n * @internal\n */\nexport function flattenAuthored(fields: readonly AuthoredDoctype[]): AuthoredDoctype[] {\n\tconst out: AuthoredDoctype[] = []\n\tfor (const field of fields) {\n\t\tif (Array.isArray(field.schema)) {\n\t\t\tout.push(...flattenAuthored(field.schema.filter(isAuthoredRecord)))\n\t\t} else {\n\t\t\tout.push(field)\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * The fieldname an authored doctype declares as its identity, or `undefined` when it declares none.\n *\n * Descends into fieldsets, because a nested `primaryKey` is a real declaration — ignoring one is\n * what `getPrimaryKeyField` was fixed for.\n *\n * @internal\n */\nexport function authoredPrimaryKey(doctype: AuthoredDoctype): string | undefined {\n\tif (!Array.isArray(doctype.fields)) return undefined\n\tconst declared = flattenAuthored(doctype.fields.filter(isAuthoredRecord)).find(f => f.primaryKey === true)\n\treturn typeof declared?.fieldname === 'string' ? declared.fieldname : undefined\n}\n","/**\n * Merge introspected schema facts into an already-authored doctype.\n *\n * The authored doctype is the source of truth. Generation **verifies** it and stamps provenance;\n * it does not overwrite. That polarity is deliberate and load-bearing — a doctype legitimately\n * declares a `primaryKey` the schema cannot express. A natural business key is very often a\n * `UNIQUE` constraint rather than the table's `PRIMARY KEY`, and where a table carries several\n * uniques no rule can pick between them. Overwriting identity from the schema would silently\n * re-key such a doctype on every regeneration and break the handlers that key on the old value.\n *\n * So divergence is **reported, never applied** — a human decides. The only mutation this performs\n * is adding `source: 'introspected'` to fields confirmed to exist in the GraphQL schema.\n *\n * @packageDocumentation\n */\n\nimport { INTROSPECTED_IDENTITY_PROPS } from '../field'\nimport { authoredPrimaryKey, flattenAuthored, isAuthoredRecord } from './authored'\nimport type { AuthoredDoctype } from './authored'\nimport type { ConvertedGraphQLDoctype } from './types'\n\nexport type { AuthoredDoctype }\n\n/**\n * What generation found that the authored doctype does not agree with. Every bucket is advisory —\n * nothing here is applied automatically.\n *\n * @public\n */\nexport interface DoctypeDrift {\n\t/** The authored doctype's name. */\n\tdoctype: string\n\t/**\n\t * `clean` — the authored primary key is the one generation would derive.\n\t * `partial` — the doctype declares an identity generation cannot derive, so identity was left alone.\n\t */\n\tmode: 'clean' | 'partial'\n\t/** Why the mode is `partial`, when it is. */\n\treason?: string\n\t/** Fieldnames confirmed against the schema and stamped. */\n\ttagged: string[]\n\t/** Authored fields with no matching schema field — app components, fieldsets, or stale entries. */\n\torphan: string[]\n\t/** Schema fields absent from the doctype. Usually deliberate curation, occasionally an oversight. */\n\tomitted: string[]\n\t/** `fieldname: authored=… schema=…` where the chosen component differs from the scalar mapping. */\n\tcomponentDrift: string[]\n\t/** `fieldname: authored=… schema=…` where nullability disagrees. */\n\trequiredDrift: string[]\n\t/** Identity properties that differ. These are the ones a human must adjudicate. */\n\tidentityDrift: string[]\n}\n\n/**\n * How to verify the authored doctype against the schema.\n *\n * @public\n */\nexport interface MergeOptions {\n\t/**\n\t * The authored doctype is a curated **subset** of the schema's columns rather than a model of\n\t * all of them — an aggregate being the case this exists for.\n\t *\n\t * This changes what counts as drift in both directions, so `generated` must be passed the\n\t * *entity's* full field set, not the subset's. A column the author added to an aggregate is\n\t * then confirmed against the real table (so a genuinely dropped column still reports as an\n\t * orphan), while the columns deliberately left out stop reporting as omissions. Without it an\n\t * aggregate reports phantom drift on every run, which both spams `--check` and buries the one\n\t * finding that matters.\n\t */\n\tsubset?: boolean\n}\n\n/** Outcome of a merge: the doctype to write, plus what generation disagreed with. @public */\nexport interface MergeResult {\n\t/** The authored doctype with `source` markers added and nothing else changed. */\n\tdoctype: AuthoredDoctype\n\t/** Advisory report. Never applied. */\n\tdrift: DoctypeDrift\n}\n\nfunction describe(value: unknown): string {\n\treturn value === undefined ? '—' : JSON.stringify(value)\n}\n\n/**\n * Verify an authored doctype against freshly generated output and stamp provenance.\n *\n * @param authored - the doctype as it exists on disk; every key not named below is preserved verbatim\n * @param generated - `convertGraphQLSchema` output for the corresponding GraphQL type. For a\n * `subset` merge this is the **entity**, whose fields are the set the subset is curated from\n * @param options - see {@link MergeOptions}\n * @returns the doctype to write, plus a drift report\n *\n * @example\n * ```ts\n * const [generated] = convertGraphQLSchema(introspection, { include: ['Uom'] })\n * const { doctype, drift } = mergeIntrospectedDoctype(JSON.parse(onDisk), generated)\n * if (drift.identityDrift.length) console.warn(drift.identityDrift.join('\\n'))\n * ```\n *\n * @public\n */\nexport function mergeIntrospectedDoctype(\n\tauthored: AuthoredDoctype,\n\tgenerated: ConvertedGraphQLDoctype,\n\toptions: MergeOptions = {}\n): MergeResult {\n\tconst authoredFields = Array.isArray(authored.fields) ? authored.fields.filter(isAuthoredRecord) : []\n\tconst generatedByName = new Map(generated.fields.map(f => [f.fieldname, f]))\n\t// Expanding links live in `links`, not `fields`, so a field naming one is modelled, not orphaned.\n\tconst generatedLinkNames = new Set(Object.keys(generated.links ?? {}))\n\n\tconst drift: DoctypeDrift = {\n\t\tdoctype: typeof authored.name === 'string' ? authored.name : '(unnamed)',\n\t\tmode: 'clean',\n\t\ttagged: [],\n\t\torphan: [],\n\t\tomitted: [],\n\t\tcomponentDrift: [],\n\t\trequiredDrift: [],\n\t\tidentityDrift: [],\n\t}\n\n\tconst tag = (field: AuthoredDoctype): AuthoredDoctype => {\n\t\t// Containers have no column of their own; recurse and leave the container itself alone.\n\t\tif (Array.isArray(field.schema)) {\n\t\t\treturn { ...field, schema: field.schema.filter(isAuthoredRecord).map(tag) }\n\t\t}\n\n\t\tconst name = typeof field.fieldname === 'string' ? field.fieldname : ''\n\t\tconst match = generatedByName.get(name)\n\n\t\tif (!match) {\n\t\t\t// A computed field declares up front that it has no backing column, so it is not a\n\t\t\t// discrepancy. Everything else is worth surfacing — it may be an app component, or a\n\t\t\t// column that has since been dropped.\n\t\t\tif (field.computed !== true && !generatedLinkNames.has(name)) drift.orphan.push(name)\n\t\t\treturn field\n\t\t}\n\n\t\tdrift.tagged.push(name)\n\n\t\tif (match.component !== field.component) {\n\t\t\tdrift.componentDrift.push(`${name}: authored=${describe(field.component)} schema=${describe(match.component)}`)\n\t\t}\n\t\tif (Boolean(match.required) !== Boolean(field.required)) {\n\t\t\tdrift.requiredDrift.push(`${name}: authored=${Boolean(field.required)} schema=${Boolean(match.required)}`)\n\t\t}\n\t\tfor (const prop of INTROSPECTED_IDENTITY_PROPS) {\n\t\t\tif (prop === 'fieldname' || prop === 'required') continue\n\t\t\tconst authoredValue = field[prop]\n\t\t\tconst schemaValue = match[prop]\n\t\t\t// Absent on both sides is agreement, not drift — most fields set none of these.\n\t\t\tif (authoredValue === undefined && schemaValue === undefined) continue\n\t\t\tif (JSON.stringify(authoredValue) !== JSON.stringify(schemaValue)) {\n\t\t\t\tdrift.identityDrift.push(`${name}.${prop}: authored=${describe(authoredValue)} schema=${describe(schemaValue)}`)\n\t\t\t}\n\t\t}\n\n\t\treturn { ...field, source: 'introspected' }\n\t}\n\n\tconst merged: AuthoredDoctype = { ...authored, fields: authoredFields.map(tag) }\n\n\t// A curated subset omits columns by definition, so the bucket that reports omissions has\n\t// nothing true to say about one.\n\tif (!options.subset) {\n\t\tconst authoredNames = new Set(flattenAuthored(authoredFields).map(f => f.fieldname))\n\t\tdrift.omitted = generated.fields.map(f => f.fieldname).filter(n => !authoredNames.has(n))\n\t}\n\n\t// Classify identity last, once every field has been compared.\n\tconst authoredPk = authoredPrimaryKey(authored)\n\tconst generatedPk = generated.fields.find(f => f.primaryKey === true)\n\tif (authoredPk && generatedPk && authoredPk !== generatedPk.fieldname) {\n\t\tdrift.mode = 'partial'\n\t\tdrift.reason = `authored primary key '${authoredPk}' is not the derivable '${generatedPk.fieldname}' — left as authored`\n\t} else if (authoredPk && !generatedPk) {\n\t\tdrift.mode = 'partial'\n\t\tdrift.reason = `authored primary key '${authoredPk}' is not derivable from the schema — left as authored`\n\t} else if (!authoredPk && generatedPk) {\n\t\tdrift.mode = 'partial'\n\t\tdrift.reason = `schema suggests '${generatedPk.fieldname}' as primary key but the doctype declares none — not applied`\n\t}\n\n\treturn { doctype: merged, drift }\n}\n\n/**\n * Render a drift report as human-readable lines. Empty when generation agrees with the doctype.\n *\n * @param drift - a report from {@link mergeIntrospectedDoctype}\n * @returns one line per finding, ready to print\n *\n * @public\n */\nexport function formatDoctypeDrift(drift: DoctypeDrift): string[] {\n\tconst lines: string[] = []\n\tif (drift.reason) lines.push(` ${drift.doctype}: ${drift.reason}`)\n\tconst bucket = (label: string, entries: string[]) => {\n\t\tif (entries.length) lines.push(` ${drift.doctype}: ${label} ${entries.join('; ')}`)\n\t}\n\tbucket('identity drift', drift.identityDrift)\n\tbucket('component drift', drift.componentDrift)\n\tbucket('required drift', drift.requiredDrift)\n\tbucket('authored fields with no schema field:', drift.orphan)\n\tbucket('schema fields not modelled:', drift.omitted)\n\treturn lines\n}\n","/**\n * GraphQL Introspection to Stonecrop Schema Converter\n *\n * Converts a standard GraphQL introspection result (or SDL string) into\n * Stonecrop doctype schemas. Source-agnostic — works with any GraphQL server.\n *\n * @packageDocumentation\n */\n\nimport { buildClientSchema, buildSchema, isObjectType, type GraphQLSchema } from 'graphql'\n\nimport type { LinkDeclaration } from '../doctype'\nimport { toSlug } from '../naming'\nimport type { IntrospectionSource, GraphQLConversionOptions, ConvertedGraphQLDoctype } from './types'\nimport type { ValueField } from '../field'\nimport { defaultIsEntityType, defaultIsEntityField, classifyFieldType } from './heuristics'\n\n/**\n * Convert a GraphQL schema to Stonecrop doctype schemas.\n *\n * Accepts either an `IntrospectionQuery` result object or an SDL string.\n * Entity types are identified using heuristics (or a custom `isEntityType` function)\n * and converted to `DoctypeMeta`-compatible JSON objects.\n *\n * @param source - GraphQL introspection result or SDL string\n * @param options - Conversion options for controlling output format and behavior\n * @returns Array of converted Stonecrop doctype definitions\n *\n * @example\n * ```typescript\n * // From introspection result (fetched from any GraphQL server)\n * const introspection = await fetchIntrospection('http://localhost:5000/graphql')\n * const doctypes = convertGraphQLSchema(introspection)\n *\n * // From SDL string\n * const sdl = fs.readFileSync('schema.graphql', 'utf-8')\n * const doctypes = convertGraphQLSchema(sdl)\n *\n * // With PostGraphile custom scalars\n * const doctypes = convertGraphQLSchema(introspection, {\n * customScalars: {\n * BigFloat: { component: 'ANumericInput' }\n * }\n * })\n * ```\n *\n * @public\n */\nexport function convertGraphQLSchema(\n\tsource: IntrospectionSource,\n\toptions: GraphQLConversionOptions = {}\n): ConvertedGraphQLDoctype[] {\n\tconst schema = buildGraphQLSchema(source)\n\tconst typeMap = schema.getTypeMap()\n\n\t// Determine the root operation type names to exclude\n\tconst rootTypeNames = new Set<string>()\n\tconst queryType = schema.getQueryType()\n\tconst mutationType = schema.getMutationType()\n\tconst subscriptionType = schema.getSubscriptionType()\n\tif (queryType) rootTypeNames.add(queryType.name)\n\tif (mutationType) rootTypeNames.add(mutationType.name)\n\tif (subscriptionType) rootTypeNames.add(subscriptionType.name)\n\n\t// Use custom or default entity type detector\n\tconst isEntityType = options.isEntityType ?? defaultIsEntityType\n\n\t// Phase 1: Identify all entity types\n\tconst entityTypes = new Set<string>()\n\tfor (const [typeName, type] of Object.entries(typeMap)) {\n\t\tif (!isObjectType(type)) continue\n\n\t\t// Always skip root operation types (even if custom isEntityType doesn't)\n\t\tif (rootTypeNames.has(typeName)) continue\n\n\t\tif (isEntityType(typeName, type)) {\n\t\t\tentityTypes.add(typeName)\n\t\t}\n\t}\n\n\t// Phase 2: Apply include/exclude filters\n\tlet filteredEntityTypes = entityTypes\n\n\tif (options.include) {\n\t\tconst includeSet = new Set(options.include)\n\t\tfilteredEntityTypes = new Set([...entityTypes].filter(t => includeSet.has(t)))\n\t}\n\n\tif (options.exclude) {\n\t\tconst excludeSet = new Set(options.exclude)\n\t\tfilteredEntityTypes = new Set([...filteredEntityTypes].filter(t => !excludeSet.has(t)))\n\t}\n\n\t// Phase 3: Convert each entity type to a doctype\n\tconst isEntityField = options.isEntityField ?? defaultIsEntityField\n\n\tconst doctypes: ConvertedGraphQLDoctype[] = []\n\n\tfor (const typeName of filteredEntityTypes) {\n\t\tconst type = typeMap[typeName]\n\t\tif (!isObjectType(type)) continue\n\n\t\tconst fields = type.getFields()\n\n\t\t// A type carrying BOTH `id` and `rowId` is PostGraphile Amber with its default inflection:\n\t\t// the Relay global identifier has taken `id`, displacing the real column to `rowId`. Neither\n\t\t// name can be emitted as-is — `id` is an opaque node id, and `rowId` does not name a column.\n\t\t// Refuse to guess: drop the Relay field and tell the caller to fix it at the inflector, where\n\t\t// it belongs. Normalizing here would bake a database fact into the doctype.\n\t\tconst isUnnormalizedPostGraphile = 'id' in fields && 'rowId' in fields\n\t\tif (isUnnormalizedPostGraphile) {\n\t\t\toptions.onWarning?.(\n\t\t\t\t`${typeName}: schema exposes both 'id' (Relay identifier) and 'rowId' (the real column). ` +\n\t\t\t\t\t`Skipping 'id' and emitting 'rowId' verbatim — no primary key can be derived. ` +\n\t\t\t\t\t`Override the '_attributeName' and 'nodeIdFieldName' inflectors so the column keeps its own name.`\n\t\t\t)\n\t\t}\n\n\t\tconst entityFields = Object.entries(fields).filter(\n\t\t\t([fieldName, field]) =>\n\t\t\t\tisEntityField(fieldName, field, type) && !(isUnnormalizedPostGraphile && fieldName === 'id')\n\t\t)\n\n\t\t// oxlint-disable-next-line oxc/no-map-spread -- ...custom spread required; Object.assign cannot preserve the metadata-carrying inferred union type from classifyField\n\t\tconst allClassifiedFields = entityFields.map(([fieldName, field]) => {\n\t\t\t// Check for full custom classification first\n\t\t\tif (options.classifyField) {\n\t\t\t\tconst custom = options.classifyField(fieldName, field, type)\n\t\t\t\tif (custom !== null && custom !== undefined) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: 'field' as const,\n\t\t\t\t\t\tfieldname: fieldName,\n\t\t\t\t\t\tlabel: custom.label ?? fieldName,\n\t\t\t\t\t\tcomponent: custom.component ?? 'ATextInput',\n\t\t\t\t\t\t...custom,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Default classification\n\t\t\treturn classifyFieldType(fieldName, field, entityTypes, options)\n\t\t})\n\n\t\t// Derive the primary key, but only for the one case SDL actually settles: a non-null `id`\n\t\t// that is a plain scalar. A natural key is typically a UNIQUE constraint indistinguishable\n\t\t// from any other column here, and a table may carry several — so anything else is left for\n\t\t// the author to declare. Emitting a guess would be worse than emitting nothing, because the\n\t\t// middleware builds its identity predicate from this and the client keys records by it.\n\t\tconst primaryKeyFieldname = allClassifiedFields.find(\n\t\t\tfield => field.fieldname === 'id' && field.required && !field.doctype && !field._isLink\n\t\t)?.fieldname\n\n\t\t// Separate scalar fields from link fields\n\t\tconst links: Record<string, LinkDeclaration> = {}\n\t\tconst convertedFields = allClassifiedFields\n\t\t\t.filter(field => {\n\t\t\t\tif (field._isLink && field.doctype && field.cardinality) {\n\t\t\t\t\tlinks[field.fieldname] = {\n\t\t\t\t\t\ttarget: field.doctype,\n\t\t\t\t\t\tcardinality: field.cardinality,\n\t\t\t\t\t}\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t\t// Clean up internal metadata unless requested, and stamp identity + provenance.\n\t\t\t// Stamped last so every classification path (default, classifyField) carries the marker —\n\t\t\t// the docbuilder's identity lock keys off it, and no classifier may unset it.\n\t\t\t.map(field => {\n\t\t\t\tconst identity = field.fieldname === primaryKeyFieldname ? { primaryKey: true as const } : {}\n\t\t\t\tif (!options.includeUnmappedMeta) {\n\t\t\t\t\tconst { _graphqlType, _unmapped, _isLink, ...clean } = field\n\t\t\t\t\treturn Object.assign(clean, identity, { source: 'introspected' as const })\n\t\t\t\t}\n\t\t\t\tconst { _isLink, ...rest } = field\n\t\t\t\treturn Object.assign(rest, identity, { source: 'introspected' as const })\n\t\t\t})\n\n\t\tconst doctypeName = options.doctypeNames?.[typeName] ?? typeName\n\t\tconst doctype: ConvertedGraphQLDoctype = {\n\t\t\tname: doctypeName,\n\t\t\tslug: toSlug(doctypeName),\n\t\t\tfields: convertedFields as ValueField[],\n\t\t}\n\n\t\tif (Object.keys(links).length > 0) {\n\t\t\tdoctype.links = links\n\t\t}\n\n\t\tif (options.includeUnmappedMeta) {\n\t\t\tdoctype._graphqlTypeName = typeName\n\t\t}\n\n\t\tdoctypes.push(doctype)\n\t}\n\n\treturn doctypes\n}\n\n/**\n * Build a GraphQLSchema from either an introspection result or SDL string.\n *\n * @param source - IntrospectionQuery object or SDL string\n * @returns A complete GraphQLSchema\n * @internal\n */\nfunction buildGraphQLSchema(source: IntrospectionSource): GraphQLSchema {\n\tif (typeof source === 'string') {\n\t\t// SDL string\n\t\treturn buildSchema(source)\n\t}\n\n\t// IntrospectionQuery result\n\treturn buildClientSchema(source)\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Re-exports\n// ═══════════════════════════════════════════════════════════════\n\n// Main converter (this file)\nexport { convertGraphQLSchema as default }\n\n// Types\nexport type {\n\tIntrospectionSource,\n\tGraphQLConversionOptions,\n\tGraphQLConversionFieldMeta,\n\tConvertedGraphQLDoctype,\n} from './types'\n\n// Scalar maps\nexport { GQL_SCALAR_MAP, WELL_KNOWN_SCALARS, INTERNAL_SCALARS, buildScalarMap } from './scalars'\n\n// Heuristics\nexport { defaultIsEntityType, defaultIsEntityField, classifyFieldType } from './heuristics'\n\n// Aggregate — the collection-view doctype derived from an entity, emitted as its own file\nexport { aggregateDoctypeName, buildAggregateDoctype, planGeneration } from './aggregate'\nexport type { GenerationPlanEntry, GenerationPlanOptions } from './aggregate'\n\n// Merge — verifies an authored doctype against the schema and stamps provenance\nexport { mergeIntrospectedDoctype, formatDoctypeDrift } from './merge'\nexport type { AuthoredDoctype, DoctypeDrift, MergeOptions, MergeResult } from './merge'\n\n// Naming utilities\nexport { toSlug, toPascalCase, pascalToSnake, snakeToCamel, camelToSnake, snakeToLabel, camelToLabel } from '../naming'\n"],"x_google_ignoreList":[7],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { A as snakeToCamel, B as getPrimaryKeyField, C as WorkflowMeta, D as camelToLabel, E as linkDisplayFieldname, F as FieldsetFieldSchema, G as stripFieldKind, H as getRecordIdentity, I as INTROSPECTED_IDENTITY_PROPS, K as TableViewConfig, L as TableFieldSchema, M as toPascalCase, N as toSlug, O as camelToSnake, P as DoctypeFieldSchema, R as ValueFieldSchema, S as WorkflowLayout, T as isActionAllowedInState, U as inferFieldKind, V as getRecordIdField, W as normalizeFieldKind, _ as validateDoctype, a as aggregateDoctypeName, b as LINK_DISPLAY_SUFFIX, c as classifyFieldType, d as GQL_SCALAR_MAP, f as INTERNAL_SCALARS, g as parseField, h as parseDoctype, j as snakeToLabel, k as pascalToSnake, l as defaultIsEntityField, m as buildScalarMap, n as formatDoctypeDrift, o as buildAggregateDoctype, p as WELL_KNOWN_SCALARS, r as mergeIntrospectedDoctype, s as planGeneration, t as convertGraphQLSchema, u as defaultIsEntityType, v as validateField, w as getDoctypeSlug, x as TriggerDefinition, y as ActionDefinition, z as getDisplayField } from "./converter-2mU9FiFz.js";
2
- import { a as componentCategory, i as COMPONENT_LINK_EXPANSION, n as CANONICAL_COMPONENTS, o as componentLinkExpansion, r as COMPONENT_CATEGORY, s as resolveLinkRenderMode, t as unwrapInlineLinks } from "./record-BQOOi83C.js";
2
+ import { a as componentCategory, i as COMPONENT_LINK_EXPANSION, n as CANONICAL_COMPONENTS, o as componentLinkExpansion, r as COMPONENT_CATEGORY, s as resolveLinkRenderMode, t as unwrapInlineLinks } from "./record-DBFAh8ag.js";
3
3
  import { t as flattenFields } from "./flatten-Bx2cfvw3.js";
4
4
  //#region src/badge.ts
5
5
  var BADGE_VARIANTS = /* @__PURE__ */ new Set([
@@ -14,7 +14,7 @@ var COMPONENT_CATEGORY = {
14
14
  ADatePicker: "date",
15
15
  ADateSelection: "date",
16
16
  ADateTime: "datetime",
17
- ADuration: "text",
17
+ ADuration: "duration",
18
18
  ADateRange: "date",
19
19
  ADropdown: "select",
20
20
  ASegmentedControl: "select",
@@ -131,4 +131,4 @@ function unwrapWith(inline, fieldsets, record) {
131
131
  //#endregion
132
132
  export { componentCategory as a, COMPONENT_LINK_EXPANSION as i, CANONICAL_COMPONENTS as n, componentLinkExpansion as o, COMPONENT_CATEGORY as r, resolveLinkRenderMode as s, unwrapInlineLinks as t };
133
133
 
134
- //# sourceMappingURL=record-BQOOi83C.js.map
134
+ //# sourceMappingURL=record-DBFAh8ag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-DBFAh8ag.js","names":[],"sources":["../src/component-meta.ts","../src/record.ts"],"sourcesContent":["/**\n * Semantic category for a rendering component.\n *\n * `component` is the primary field axis, so the runtime consumers that need to know what a field\n * *means* (atable cell formatting / filter widgets, record-default init) derive it from here. This\n * is the single source of \"what kind of value does this component render\", keyed by the canonical\n * registered component names — each consumer maps the category to its own concern (filter widget,\n * default value, …).\n *\n * @public\n */\nexport type ComponentCategory =\n\t| 'text'\n\t| 'number'\n\t| 'boolean'\n\t| 'date'\n\t| 'datetime'\n\t| 'duration'\n\t| 'select'\n\t| 'code'\n\t| 'link'\n\t| 'attach'\n\t| 'quantity'\n\t| 'currency'\n\n/**\n * Canonical component → semantic category. Only the components Stonecrop ships with appear here;\n * custom/unknown component names have no category and consumers fall back to their default.\n * @public\n */\nexport const COMPONENT_CATEGORY: Record<string, ComponentCategory> = {\n\tATextInput: 'text',\n\tATextboxInput: 'text',\n\tANumericInput: 'number',\n\tACheckbox: 'boolean',\n\tADate: 'date',\n\tADatePicker: 'date',\n\tADateSelection: 'date',\n\tADateTime: 'datetime',\n\tADuration: 'duration',\n\tADateRange: 'date',\n\tADropdown: 'select',\n\tASegmentedControl: 'select',\n\tACodeEditor: 'code',\n\tAFormLink: 'link',\n\tAFileAttach: 'attach',\n\tAQuantityInput: 'quantity',\n\tACurrencyInput: 'currency',\n}\n\n/**\n * Resolve a component's semantic category, or `undefined` for an unknown (custom) component —\n * callers treat that as \"no opinion\" and use their own default.\n * @public\n */\nexport function componentCategory(component?: string): ComponentCategory | undefined {\n\treturn component ? COMPONENT_CATEGORY[component] : undefined\n}\n\n/**\n * Whether a link component expands its target doctype, or renders the link inline.\n *\n * This is the *only* axis the component decides. It deliberately does not choose between an\n * embedded record and an embedded table: `cardinality` states whether the value is a scalar or\n * an array, which is a fact about the data rather than a rendering preference, so a component\n * must not be able to override it (an `AForm` over a `noneOrMany` link would be handed an array\n * it cannot render). Component names encode both axes — `AFormLink`/`ATableLink` are the inline\n * pair, `AForm`/`ATable` the expanding pair — but only the inline/expand half is authoritative.\n *\n * @public\n */\nexport type LinkExpansion = 'inline' | 'expand'\n\n/**\n * Canonical link component → expansion. Only components Stonecrop ships with appear here; an\n * unmapped (custom) component has none, and callers treat that as `expand` — the behaviour that\n * predates this map, so a custom component can never silently collapse a link to a picker.\n * @public\n */\nexport const COMPONENT_LINK_EXPANSION: Record<string, LinkExpansion> = {\n\tAFormLink: 'inline',\n\tAForm: 'expand',\n\tATable: 'expand',\n}\n\n/**\n * Resolve a component's link expansion, or `undefined` for an absent/unmapped component.\n * @public\n */\nexport function componentLinkExpansion(component?: string): LinkExpansion | undefined {\n\treturn component ? COMPONENT_LINK_EXPANSION[component] : undefined\n}\n\n/**\n * How a link field renders.\n *\n * - `inline` — a scalar id-picker; the target is *not* expanded (the field keeps its own value\n * and carries a `doctype` prop for async display-text resolution and navigation).\n * - `record` — the target doctype is resolved and embedded as a nested form.\n * - `table` — the target doctype is resolved and embedded as a child table.\n *\n * @public\n */\nexport type LinkRenderMode = 'inline' | 'record' | 'table'\n\n/**\n * Decide how a *declared* link (one with a `LinkDeclaration`) renders.\n *\n * Two independent axes: the **component** picks inline vs expand, and when expanding the\n * **cardinality** picks record vs table (many → table). The declaration's component wins over the\n * field's, matching the precedence the resolver already uses for the rendered component.\n *\n * This is the single definition of \"does this link expand\" — it is consumed by both the client\n * resolver (which builds the nested schema) and the server column builder (which must still\n * SELECT an `inline` link's FK column). Call it; never re-derive the rule at the call site, or\n * the two will drift and the client will render a table for a column the server never selected.\n *\n * @param link - the link declaration (only `component` and `cardinality` are consulted)\n * @param fieldComponent - the linked field's own `component`, used when the declaration names none\n * @public\n */\nexport function resolveLinkRenderMode(\n\tlink: { component?: string; cardinality?: string },\n\tfieldComponent?: string\n): LinkRenderMode {\n\tif (componentLinkExpansion(link.component ?? fieldComponent) === 'inline') return 'inline'\n\treturn link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne' ? 'table' : 'record'\n}\n\n/**\n * Every component Stonecrop ships with that can render a value field, sorted by name.\n *\n * The union of the two maps above is the definition, not a copy of it: a shipped component either\n * categorises a value ({@link COMPONENT_CATEGORY}) or is one of the link containers that has no\n * value of its own ({@link COMPONENT_LINK_EXPANSION}'s `AForm`/`ATable`). `AFieldset` is absent by\n * the same rule — it is a `kind: 'fieldset'` container, so it is never a value field's component.\n *\n * `component` is an **open** axis: any string is valid, and naming a custom component is how an app\n * renders a field Stonecrop ships no widget for. This list is therefore the set to *suggest* to an\n * author, and to check first-party data against — never a set to validate arbitrary input against.\n *\n * @public\n */\nexport const CANONICAL_COMPONENTS: readonly string[] = [\n\t...new Set([...Object.keys(COMPONENT_CATEGORY), ...Object.keys(COMPONENT_LINK_EXPANSION)]),\n].toSorted()\n","import { componentLinkExpansion } from './component-meta'\nimport type { DoctypeField } from './field'\nimport { flattenFields } from './flatten'\n\n/**\n * Reduce a record's *inline* link values to the ids that get persisted.\n *\n * The adapter returns an inline link as `{ id, displayText }`, so that is what a record holds\n * everywhere it is read — the store, a list row, a form field. A column takes the id alone, so\n * this is the single definition of the shape a record leaves in, and it belongs at the boundary\n * a record crosses on its way to the server, never on the way into the store.\n *\n * Doing it on the way in destroys the text the adapter looked up: nothing else holds it, so the\n * field that resolved a moment ago renders its raw id, and the same record then renders\n * differently depending on whether anything had edited the form yet. That is the bug this exists\n * to prevent, and its damage is a wrong render, not a throw.\n *\n * Only *inline* links may be reduced. An inline link's value is indistinguishable by inspection\n * from an expanded one (`{ id, ...the whole target record }`), so `component` — which states\n * which of the two a field is — is what tells them apart, via {@link componentLinkExpansion}.\n * Reducing an expanded link would send the id in place of the record.\n *\n * Fieldsets are descended into in both shapes a record appears in: flat, as the store and the\n * server hold it, and nested under the fieldset's own key, as a form emits it.\n *\n * @param fields - the doctype's top-level fields\n * @param record - the record to reduce; not mutated\n * @returns a shallow copy with every inline link reduced to its id\n * @public\n */\nexport function unwrapInlineLinks(fields: readonly DoctypeField[], record: Record<string, any>): Record<string, any> {\n\tconst inline = new Set(\n\t\tflattenFields(fields)\n\t\t\t.filter(field => field.kind === 'field' && Boolean(field.doctype))\n\t\t\t.filter(field => componentLinkExpansion(field.component) === 'inline')\n\t\t\t.map(field => field.fieldname)\n\t)\n\tif (inline.size === 0) return record\n\n\tconst fieldsets = new Set(fields.filter(field => field.kind === 'fieldset').map(field => field.fieldname))\n\treturn unwrapWith(inline, fieldsets, record)\n}\n\nfunction unwrapWith(\n\tinline: ReadonlySet<string>,\n\tfieldsets: ReadonlySet<string>,\n\trecord: Record<string, any>\n): Record<string, any> {\n\tconst result: Record<string, any> = { ...record }\n\tfor (const [key, value] of Object.entries(result)) {\n\t\tif (value === null || typeof value !== 'object' || Array.isArray(value)) continue\n\t\tif (inline.has(key)) {\n\t\t\t// `'id' in value` rather than a truthiness test: an inline link that was never resolved\n\t\t\t// is still a bare scalar, and reducing it a second time would be a no-op at best.\n\t\t\tif ('id' in value) result[key] = value.id\n\t\t} else if (fieldsets.has(key)) {\n\t\t\tresult[key] = unwrapWith(inline, fieldsets, value)\n\t\t}\n\t}\n\treturn result\n}\n"],"mappings":";;;;;;;AA8BA,IAAa,qBAAwD;CACpE,YAAY;CACZ,eAAe;CACf,eAAe;CACf,WAAW;CACX,OAAO;CACP,aAAa;CACb,gBAAgB;CAChB,WAAW;CACX,WAAW;CACX,YAAY;CACZ,WAAW;CACX,mBAAmB;CACnB,aAAa;CACb,WAAW;CACX,aAAa;CACb,gBAAgB;CAChB,gBAAgB;AACjB;;;;;;AAOA,SAAgB,kBAAkB,WAAmD;CACpF,OAAO,YAAY,mBAAmB,aAAa,KAAA;AACpD;;;;;;;AAsBA,IAAa,2BAA0D;CACtE,WAAW;CACX,OAAO;CACP,QAAQ;AACT;;;;;AAMA,SAAgB,uBAAuB,WAA+C;CACrF,OAAO,YAAY,yBAAyB,aAAa,KAAA;AAC1D;;;;;;;;;;;;;;;;;AA8BA,SAAgB,sBACf,MACA,gBACiB;CACjB,IAAI,uBAAuB,KAAK,aAAa,cAAc,MAAM,UAAU,OAAO;CAClF,OAAO,KAAK,gBAAgB,gBAAgB,KAAK,gBAAgB,eAAe,UAAU;AAC3F;;;;;;;;;;;;;;;AAgBA,IAAa,uBAA0C,CACtD,mBAAG,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,kBAAkB,GAAG,GAAG,OAAO,KAAK,wBAAwB,CAAC,CAAC,CAC1F,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnHX,SAAgB,kBAAkB,QAAiC,QAAkD;CACpH,MAAM,SAAS,IAAI,IAClB,cAAc,MAAM,CAAC,CACnB,QAAO,UAAS,MAAM,SAAS,WAAW,QAAQ,MAAM,OAAO,CAAC,CAAC,CACjE,QAAO,UAAS,uBAAuB,MAAM,SAAS,MAAM,QAAQ,CAAC,CACrE,KAAI,UAAS,MAAM,SAAS,CAC/B;CACA,IAAI,OAAO,SAAS,GAAG,OAAO;CAG9B,OAAO,WAAW,QAAQ,IADJ,IAAI,OAAO,QAAO,UAAS,MAAM,SAAS,UAAU,CAAC,CAAC,KAAI,UAAS,MAAM,SAAS,CAC9E,GAAW,MAAM;AAC5C;AAEA,SAAS,WACR,QACA,WACA,QACsB;CACtB,MAAM,SAA8B,EAAE,GAAG,OAAO;CAChD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EAClD,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;EACzE,IAAI,OAAO,IAAI,GAAG,GAGb;OAAA,QAAQ,OAAO,OAAO,OAAO,MAAM;EAAA,OACjC,IAAI,UAAU,IAAI,GAAG,GAC3B,OAAO,OAAO,WAAW,QAAQ,WAAW,KAAK;CAEnD;CACA,OAAO;AACR"}
package/dist/record.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as unwrapInlineLinks } from "./record-BQOOi83C.js";
1
+ import { t as unwrapInlineLinks } from "./record-DBFAh8ag.js";
2
2
  export { unwrapInlineLinks };
package/dist/schema.d.ts CHANGED
@@ -419,7 +419,7 @@ export declare const COMPONENT_LINK_EXPANSION: Record<string, LinkExpansion>;
419
419
  *
420
420
  * @public
421
421
  */
422
- export declare type ComponentCategory = 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'select' | 'code' | 'link' | 'attach' | 'quantity' | 'currency';
422
+ export declare type ComponentCategory = 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'duration' | 'select' | 'code' | 'link' | 'attach' | 'quantity' | 'currency';
423
423
 
424
424
  /**
425
425
  * Resolve a component's semantic category, or `undefined` for an unknown (custom) component —
@@ -536,12 +536,15 @@ export declare interface DataClient<T extends DoctypeRef = DoctypeRef, M = Docty
536
536
  * @param doctype - Doctype reference (name and optional slug)
537
537
  * @param action - Action name to execute (e.g., 'SUBMIT', 'APPROVE', 'save')
538
538
  * @param args - Action arguments (typically record ID and/or form data)
539
- * @returns Action result with success status, response data, and any error
539
+ * @returns Action result: success, what the action's handler returned (`data`), any error, and the
540
+ * record as {@link DataClient.getRecord} returns it after the action (`record`, null when the
541
+ * action failed or targets no record)
540
542
  */
541
543
  runAction(doctype: T, action: string, args?: unknown[]): Promise<{
542
544
  success: boolean;
543
545
  data: unknown;
544
546
  error: string | null;
547
+ record: Record<string, unknown> | null;
545
548
  }>;
546
549
  }
547
550
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/schema",
3
- "version": "0.35.0",
3
+ "version": "0.38.0",
4
4
  "description": "Stonecrop schema definitions and validation tooling",
5
5
  "keywords": [
6
6
  "doctype",
@@ -1 +0,0 @@
1
- {"version":3,"file":"record-BQOOi83C.js","names":[],"sources":["../src/component-meta.ts","../src/record.ts"],"sourcesContent":["/**\n * Semantic category for a rendering component.\n *\n * `component` is the primary field axis, so the runtime consumers that need to know what a field\n * *means* (atable cell formatting / filter widgets, record-default init) derive it from here. This\n * is the single source of \"what kind of value does this component render\", keyed by the canonical\n * registered component names — each consumer maps the category to its own concern (filter widget,\n * default value, …).\n *\n * @public\n */\nexport type ComponentCategory =\n\t| 'text'\n\t| 'number'\n\t| 'boolean'\n\t| 'date'\n\t| 'datetime'\n\t| 'select'\n\t| 'code'\n\t| 'link'\n\t| 'attach'\n\t| 'quantity'\n\t| 'currency'\n\n/**\n * Canonical component → semantic category. Only the components Stonecrop ships with appear here;\n * custom/unknown component names have no category and consumers fall back to their default.\n * @public\n */\nexport const COMPONENT_CATEGORY: Record<string, ComponentCategory> = {\n\tATextInput: 'text',\n\tATextboxInput: 'text',\n\tANumericInput: 'number',\n\tACheckbox: 'boolean',\n\tADate: 'date',\n\tADatePicker: 'date',\n\tADateSelection: 'date',\n\tADateTime: 'datetime',\n\tADuration: 'text',\n\tADateRange: 'date',\n\tADropdown: 'select',\n\tASegmentedControl: 'select',\n\tACodeEditor: 'code',\n\tAFormLink: 'link',\n\tAFileAttach: 'attach',\n\tAQuantityInput: 'quantity',\n\tACurrencyInput: 'currency',\n}\n\n/**\n * Resolve a component's semantic category, or `undefined` for an unknown (custom) component —\n * callers treat that as \"no opinion\" and use their own default.\n * @public\n */\nexport function componentCategory(component?: string): ComponentCategory | undefined {\n\treturn component ? COMPONENT_CATEGORY[component] : undefined\n}\n\n/**\n * Whether a link component expands its target doctype, or renders the link inline.\n *\n * This is the *only* axis the component decides. It deliberately does not choose between an\n * embedded record and an embedded table: `cardinality` states whether the value is a scalar or\n * an array, which is a fact about the data rather than a rendering preference, so a component\n * must not be able to override it (an `AForm` over a `noneOrMany` link would be handed an array\n * it cannot render). Component names encode both axes — `AFormLink`/`ATableLink` are the inline\n * pair, `AForm`/`ATable` the expanding pair — but only the inline/expand half is authoritative.\n *\n * @public\n */\nexport type LinkExpansion = 'inline' | 'expand'\n\n/**\n * Canonical link component → expansion. Only components Stonecrop ships with appear here; an\n * unmapped (custom) component has none, and callers treat that as `expand` — the behaviour that\n * predates this map, so a custom component can never silently collapse a link to a picker.\n * @public\n */\nexport const COMPONENT_LINK_EXPANSION: Record<string, LinkExpansion> = {\n\tAFormLink: 'inline',\n\tAForm: 'expand',\n\tATable: 'expand',\n}\n\n/**\n * Resolve a component's link expansion, or `undefined` for an absent/unmapped component.\n * @public\n */\nexport function componentLinkExpansion(component?: string): LinkExpansion | undefined {\n\treturn component ? COMPONENT_LINK_EXPANSION[component] : undefined\n}\n\n/**\n * How a link field renders.\n *\n * - `inline` — a scalar id-picker; the target is *not* expanded (the field keeps its own value\n * and carries a `doctype` prop for async display-text resolution and navigation).\n * - `record` — the target doctype is resolved and embedded as a nested form.\n * - `table` — the target doctype is resolved and embedded as a child table.\n *\n * @public\n */\nexport type LinkRenderMode = 'inline' | 'record' | 'table'\n\n/**\n * Decide how a *declared* link (one with a `LinkDeclaration`) renders.\n *\n * Two independent axes: the **component** picks inline vs expand, and when expanding the\n * **cardinality** picks record vs table (many → table). The declaration's component wins over the\n * field's, matching the precedence the resolver already uses for the rendered component.\n *\n * This is the single definition of \"does this link expand\" — it is consumed by both the client\n * resolver (which builds the nested schema) and the server column builder (which must still\n * SELECT an `inline` link's FK column). Call it; never re-derive the rule at the call site, or\n * the two will drift and the client will render a table for a column the server never selected.\n *\n * @param link - the link declaration (only `component` and `cardinality` are consulted)\n * @param fieldComponent - the linked field's own `component`, used when the declaration names none\n * @public\n */\nexport function resolveLinkRenderMode(\n\tlink: { component?: string; cardinality?: string },\n\tfieldComponent?: string\n): LinkRenderMode {\n\tif (componentLinkExpansion(link.component ?? fieldComponent) === 'inline') return 'inline'\n\treturn link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne' ? 'table' : 'record'\n}\n\n/**\n * Every component Stonecrop ships with that can render a value field, sorted by name.\n *\n * The union of the two maps above is the definition, not a copy of it: a shipped component either\n * categorises a value ({@link COMPONENT_CATEGORY}) or is one of the link containers that has no\n * value of its own ({@link COMPONENT_LINK_EXPANSION}'s `AForm`/`ATable`). `AFieldset` is absent by\n * the same rule — it is a `kind: 'fieldset'` container, so it is never a value field's component.\n *\n * `component` is an **open** axis: any string is valid, and naming a custom component is how an app\n * renders a field Stonecrop ships no widget for. This list is therefore the set to *suggest* to an\n * author, and to check first-party data against — never a set to validate arbitrary input against.\n *\n * @public\n */\nexport const CANONICAL_COMPONENTS: readonly string[] = [\n\t...new Set([...Object.keys(COMPONENT_CATEGORY), ...Object.keys(COMPONENT_LINK_EXPANSION)]),\n].toSorted()\n","import { componentLinkExpansion } from './component-meta'\nimport type { DoctypeField } from './field'\nimport { flattenFields } from './flatten'\n\n/**\n * Reduce a record's *inline* link values to the ids that get persisted.\n *\n * The adapter returns an inline link as `{ id, displayText }`, so that is what a record holds\n * everywhere it is read — the store, a list row, a form field. A column takes the id alone, so\n * this is the single definition of the shape a record leaves in, and it belongs at the boundary\n * a record crosses on its way to the server, never on the way into the store.\n *\n * Doing it on the way in destroys the text the adapter looked up: nothing else holds it, so the\n * field that resolved a moment ago renders its raw id, and the same record then renders\n * differently depending on whether anything had edited the form yet. That is the bug this exists\n * to prevent, and its damage is a wrong render, not a throw.\n *\n * Only *inline* links may be reduced. An inline link's value is indistinguishable by inspection\n * from an expanded one (`{ id, ...the whole target record }`), so `component` — which states\n * which of the two a field is — is what tells them apart, via {@link componentLinkExpansion}.\n * Reducing an expanded link would send the id in place of the record.\n *\n * Fieldsets are descended into in both shapes a record appears in: flat, as the store and the\n * server hold it, and nested under the fieldset's own key, as a form emits it.\n *\n * @param fields - the doctype's top-level fields\n * @param record - the record to reduce; not mutated\n * @returns a shallow copy with every inline link reduced to its id\n * @public\n */\nexport function unwrapInlineLinks(fields: readonly DoctypeField[], record: Record<string, any>): Record<string, any> {\n\tconst inline = new Set(\n\t\tflattenFields(fields)\n\t\t\t.filter(field => field.kind === 'field' && Boolean(field.doctype))\n\t\t\t.filter(field => componentLinkExpansion(field.component) === 'inline')\n\t\t\t.map(field => field.fieldname)\n\t)\n\tif (inline.size === 0) return record\n\n\tconst fieldsets = new Set(fields.filter(field => field.kind === 'fieldset').map(field => field.fieldname))\n\treturn unwrapWith(inline, fieldsets, record)\n}\n\nfunction unwrapWith(\n\tinline: ReadonlySet<string>,\n\tfieldsets: ReadonlySet<string>,\n\trecord: Record<string, any>\n): Record<string, any> {\n\tconst result: Record<string, any> = { ...record }\n\tfor (const [key, value] of Object.entries(result)) {\n\t\tif (value === null || typeof value !== 'object' || Array.isArray(value)) continue\n\t\tif (inline.has(key)) {\n\t\t\t// `'id' in value` rather than a truthiness test: an inline link that was never resolved\n\t\t\t// is still a bare scalar, and reducing it a second time would be a no-op at best.\n\t\t\tif ('id' in value) result[key] = value.id\n\t\t} else if (fieldsets.has(key)) {\n\t\t\tresult[key] = unwrapWith(inline, fieldsets, value)\n\t\t}\n\t}\n\treturn result\n}\n"],"mappings":";;;;;;;AA6BA,IAAa,qBAAwD;CACpE,YAAY;CACZ,eAAe;CACf,eAAe;CACf,WAAW;CACX,OAAO;CACP,aAAa;CACb,gBAAgB;CAChB,WAAW;CACX,WAAW;CACX,YAAY;CACZ,WAAW;CACX,mBAAmB;CACnB,aAAa;CACb,WAAW;CACX,aAAa;CACb,gBAAgB;CAChB,gBAAgB;AACjB;;;;;;AAOA,SAAgB,kBAAkB,WAAmD;CACpF,OAAO,YAAY,mBAAmB,aAAa,KAAA;AACpD;;;;;;;AAsBA,IAAa,2BAA0D;CACtE,WAAW;CACX,OAAO;CACP,QAAQ;AACT;;;;;AAMA,SAAgB,uBAAuB,WAA+C;CACrF,OAAO,YAAY,yBAAyB,aAAa,KAAA;AAC1D;;;;;;;;;;;;;;;;;AA8BA,SAAgB,sBACf,MACA,gBACiB;CACjB,IAAI,uBAAuB,KAAK,aAAa,cAAc,MAAM,UAAU,OAAO;CAClF,OAAO,KAAK,gBAAgB,gBAAgB,KAAK,gBAAgB,eAAe,UAAU;AAC3F;;;;;;;;;;;;;;;AAgBA,IAAa,uBAA0C,CACtD,mBAAG,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,kBAAkB,GAAG,GAAG,OAAO,KAAK,wBAAwB,CAAC,CAAC,CAC1F,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClHX,SAAgB,kBAAkB,QAAiC,QAAkD;CACpH,MAAM,SAAS,IAAI,IAClB,cAAc,MAAM,CAAC,CACnB,QAAO,UAAS,MAAM,SAAS,WAAW,QAAQ,MAAM,OAAO,CAAC,CAAC,CACjE,QAAO,UAAS,uBAAuB,MAAM,SAAS,MAAM,QAAQ,CAAC,CACrE,KAAI,UAAS,MAAM,SAAS,CAC/B;CACA,IAAI,OAAO,SAAS,GAAG,OAAO;CAG9B,OAAO,WAAW,QAAQ,IADJ,IAAI,OAAO,QAAO,UAAS,MAAM,SAAS,UAAU,CAAC,CAAC,KAAI,UAAS,MAAM,SAAS,CAC9E,GAAW,MAAM;AAC5C;AAEA,SAAS,WACR,QACA,WACA,QACsB;CACtB,MAAM,SAA8B,EAAE,GAAG,OAAO;CAChD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EAClD,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;EACzE,IAAI,OAAO,IAAI,GAAG,GAGb;OAAA,QAAQ,OAAO,OAAO,OAAO,MAAM;EAAA,OACjC,IAAI,UAAU,IAAI,GAAG,GAC3B,OAAO,OAAO,WAAW,QAAQ,WAAW,KAAK;CAEnD;CACA,OAAO;AACR"}