@stonecrop/schema 0.25.0 → 0.27.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.
Files changed (38) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +85 -59
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +68 -61
  5. package/dist/index.js.map +1 -1
  6. package/dist/schema.d.ts +256 -13
  7. package/dist/src/cli.js +53 -6
  8. package/dist/src/component-meta.d.ts.map +1 -1
  9. package/dist/src/component-meta.js +1 -0
  10. package/dist/src/converter/aggregate.d.ts +127 -0
  11. package/dist/src/converter/aggregate.d.ts.map +1 -0
  12. package/dist/src/converter/aggregate.js +235 -0
  13. package/dist/src/converter/authored.d.ts +43 -0
  14. package/dist/src/converter/authored.d.ts.map +1 -0
  15. package/dist/src/converter/authored.js +52 -0
  16. package/dist/src/converter/heuristics.d.ts +2 -2
  17. package/dist/src/converter/heuristics.d.ts.map +1 -1
  18. package/dist/src/converter/heuristics.js +43 -4
  19. package/dist/src/converter/index.d.ts +3 -1
  20. package/dist/src/converter/index.d.ts.map +1 -1
  21. package/dist/src/converter/index.js +2 -0
  22. package/dist/src/converter/merge.d.ts +25 -10
  23. package/dist/src/converter/merge.d.ts.map +1 -1
  24. package/dist/src/converter/merge.js +17 -26
  25. package/dist/src/doctype.d.ts +38 -0
  26. package/dist/src/doctype.d.ts.map +1 -1
  27. package/dist/src/doctype.js +55 -1
  28. package/dist/src/field.d.ts +59 -8
  29. package/dist/src/field.d.ts.map +1 -1
  30. package/dist/src/field.js +84 -14
  31. package/dist/src/index.d.ts +3 -3
  32. package/dist/src/index.d.ts.map +1 -1
  33. package/dist/src/index.js +3 -3
  34. package/dist/validation-BjRDR6sh.js +1000 -0
  35. package/dist/validation-BjRDR6sh.js.map +1 -0
  36. package/package.json +3 -1
  37. package/dist/validation-CQtfIFHQ.js +0 -583
  38. package/dist/validation-CQtfIFHQ.js.map +0 -1
@@ -249,6 +249,7 @@ export declare const DoctypeMeta: z.ZodObject<{
249
249
  name: z.ZodString;
250
250
  slug: z.ZodOptional<z.ZodString>;
251
251
  displayField: z.ZodOptional<z.ZodString>;
252
+ route: z.ZodOptional<z.ZodString>;
252
253
  fields: z.ZodArray<z.ZodType<import("./field").DoctypeField, unknown, z.core.$ZodTypeInternals<import("./field").DoctypeField, unknown>>>;
253
254
  links: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
254
255
  target: z.ZodString;
@@ -314,6 +315,43 @@ export declare const DoctypeMeta: z.ZodObject<{
314
315
  * @public
315
316
  */
316
317
  export type DoctypeMeta = z.infer<typeof DoctypeMeta>;
318
+ /**
319
+ * The one string a doctype is addressed by.
320
+ *
321
+ * A doctype carries two names — `name` (`OrderItem`) and `slug` (`order-item`) — and every registry
322
+ * must agree on which one keys it. Three implementations had drifted apart: the adapter's registry is
323
+ * keyed by `name` and its `getMeta` also scans for a matching `slug`, so it accepts **either**; the
324
+ * client's registry is keyed by a slug it derives itself and accepts **only** that; and
325
+ * `Doctype.fromObject` dropped an authored `slug` on the floor and re-derived one regardless. The
326
+ * adapter's accepted set was therefore a strict superset of the client's, and a link target written
327
+ * as the Name booted the server, passed its reference check, served rows over GraphQL, and was
328
+ * silently dropped by the client — an expanding child table rendering as one empty text input, with
329
+ * nothing logged.
330
+ *
331
+ * Resolving through this in both runtimes is what makes the two answers the same answer. It is the
332
+ * derivation only; a *lookup* still belongs to whichever registry owns the corpus, because the two
333
+ * corpora legitimately differ (a client registers lazily, and a client-only host has no adapter at
334
+ * all).
335
+ *
336
+ * An authored `slug` wins over the derived one because the authored doctype is the source of truth:
337
+ * generation verifies a file and never overwrites it, so a doctype that states its own slug means it.
338
+ * Deriving unconditionally is what `fromObject` did, and it made an authored `slug` a silent no-op on
339
+ * one side of the wire while the other honoured it.
340
+ *
341
+ * @param doctype - anything carrying a doctype's `name` and optional authored `slug`
342
+ * @returns the canonical slug
343
+ * @public
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * getDoctypeSlug({ name: 'OrderItem' }) // 'order-item'
348
+ * getDoctypeSlug({ name: 'Planner', slug: 'planner-board' }) // 'planner-board'
349
+ * ```
350
+ */
351
+ export declare function getDoctypeSlug(doctype: {
352
+ name: string;
353
+ slug?: string;
354
+ }): string;
317
355
  /**
318
356
  * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.
319
357
  *
@@ -1 +1 @@
1
- {"version":3,"file":"doctype.d.ts","sourceRoot":"","sources":["../../src/doctype.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctype.d.ts","sourceRoot":"","sources":["../../src/doctype.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { DoctypeFieldSchema, flattenFields, getDisplayField } from './field';
3
+ import { toSlug } from './naming';
3
4
  /**
4
5
  * Cardinality for relationship links.
5
6
  * @public
@@ -214,6 +215,16 @@ export const DoctypeMeta = z
214
215
  * returns that field as `{ id, displayText }`, reading `displayText` from this field.
215
216
  */
216
217
  displayField: z.string().min(1).optional(),
218
+ /**
219
+ * URL path this doctype registers at, written literally — `/order` for a collection,
220
+ * `/order/:id` for a record. Absent means the doctype has no page of its own, which is the
221
+ * common case: a child table is reached inside its parent, never at a URL.
222
+ *
223
+ * A path rather than a segment because the record parameter has to be somewhere, and a host
224
+ * that reads a bare segment has to know which kind of doctype it is holding to decide where
225
+ * to put it. Writing it out means nothing downstream re-derives it.
226
+ */
227
+ route: z.string().startsWith('/').optional(),
217
228
  /** Field definitions (a link field is one carrying `doctype`) */
218
229
  fields: z.array(DoctypeFieldSchema),
219
230
  /** Relationship links to other doctypes */
@@ -239,9 +250,13 @@ export const DoctypeMeta = z
239
250
  // need not be unique — `stonecropRecord`'s row map then keeps whichever row comes last.
240
251
  // Refusing at the gate is what makes it say so.
241
252
  //
253
+ // Counts the flattened set, because that is the set `getPrimaryKeyField` resolves over. The
254
+ // two asked different questions while this scanned top level only: a doctype with one key
255
+ // declared at each level passed the gate and then had one of them silently dropped.
256
+ //
242
257
  // Zero keys stays legal and is not an omission: a surrogate-key doctype declares none and
243
258
  // resolves through `getRecordIdField`'s documented `id` fallback.
244
- const declared = doctype.fields.filter(f => f.kind === 'field' && f.primaryKey);
259
+ const declared = flattenFields(doctype.fields).filter(f => f.kind === 'field' && f.primaryKey);
245
260
  if (declared.length > 1) {
246
261
  ctx.addIssue({
247
262
  code: 'custom',
@@ -267,6 +282,45 @@ export const DoctypeMeta = z
267
282
  });
268
283
  }
269
284
  });
285
+ /**
286
+ * The one string a doctype is addressed by.
287
+ *
288
+ * A doctype carries two names — `name` (`OrderItem`) and `slug` (`order-item`) — and every registry
289
+ * must agree on which one keys it. Three implementations had drifted apart: the adapter's registry is
290
+ * keyed by `name` and its `getMeta` also scans for a matching `slug`, so it accepts **either**; the
291
+ * client's registry is keyed by a slug it derives itself and accepts **only** that; and
292
+ * `Doctype.fromObject` dropped an authored `slug` on the floor and re-derived one regardless. The
293
+ * adapter's accepted set was therefore a strict superset of the client's, and a link target written
294
+ * as the Name booted the server, passed its reference check, served rows over GraphQL, and was
295
+ * silently dropped by the client — an expanding child table rendering as one empty text input, with
296
+ * nothing logged.
297
+ *
298
+ * Resolving through this in both runtimes is what makes the two answers the same answer. It is the
299
+ * derivation only; a *lookup* still belongs to whichever registry owns the corpus, because the two
300
+ * corpora legitimately differ (a client registers lazily, and a client-only host has no adapter at
301
+ * all).
302
+ *
303
+ * An authored `slug` wins over the derived one because the authored doctype is the source of truth:
304
+ * generation verifies a file and never overwrites it, so a doctype that states its own slug means it.
305
+ * Deriving unconditionally is what `fromObject` did, and it made an authored `slug` a silent no-op on
306
+ * one side of the wire while the other honoured it.
307
+ *
308
+ * @param doctype - anything carrying a doctype's `name` and optional authored `slug`
309
+ * @returns the canonical slug
310
+ * @public
311
+ *
312
+ * @example
313
+ * ```typescript
314
+ * getDoctypeSlug({ name: 'OrderItem' }) // 'order-item'
315
+ * getDoctypeSlug({ name: 'Planner', slug: 'planner-board' }) // 'planner-board'
316
+ * ```
317
+ */
318
+ export function getDoctypeSlug(doctype) {
319
+ // `||` rather than `??`: an empty authored slug is not a usable registry key, and this is
320
+ // reachable — `Doctype.fromObject` builds a doctype without going through the Zod gate, which is
321
+ // where `slug: z.string().min(1)` would have refused it.
322
+ return doctype.slug || toSlug(doctype.name);
323
+ }
270
324
  /**
271
325
  * Suffix appended to a link fieldname for its pre-resolved display text in record payloads.
272
326
  *
@@ -73,6 +73,8 @@ export interface ValueField {
73
73
  label?: string;
74
74
  /** CSS width (e.g. `"40ch"`, `"200px"`) */
75
75
  width?: string;
76
+ /** CSS height (e.g. `"100%"`, `"40vh"`) — used by full-viewport fields such as Planner */
77
+ height?: string;
76
78
  /** Text alignment */
77
79
  align?: 'left' | 'center' | 'right' | 'start' | 'end';
78
80
  /** Whether the field is editable in table cell context */
@@ -157,6 +159,29 @@ export interface TableField {
157
159
  * @public
158
160
  */
159
161
  export type DoctypeField = ValueField | FieldsetField | TableField;
162
+ /**
163
+ * Which of the three field shapes an entry has, read from the entry's own structure.
164
+ *
165
+ * The single definition of that question. It had three copies before this — the parser's
166
+ * `injectKind`, {@link stripFieldKind}'s agreement check, and the docbuilder's own
167
+ * `isValueField` in another package — each free to drift, and drift here re-types a field rather
168
+ * than throwing: a value field read as a fieldset loses its column, a fieldset read as a value
169
+ * field loses every child.
170
+ *
171
+ * Deliberately **shape-only**: a declared `kind` is ignored. Two callers depend on that. The
172
+ * stripper compares this against the declaration to decide whether removing it is lossless, which
173
+ * it cannot do if this honours it. The docbuilder reads raw JSON off disk and classifies entries to
174
+ * decide which to render as editable rows — and `kind` is Stonecrop's own discriminant, not
175
+ * something a doctype author writes, so a tool reading a file has no business consulting it.
176
+ *
177
+ * `injectKind` is the one place a declaration still wins, and only to leave an already-parsed
178
+ * object untouched on its way back through.
179
+ *
180
+ * @param field - a field entry, authored or parsed
181
+ * @returns the kind its shape implies
182
+ * @public
183
+ */
184
+ export declare function inferFieldKind(field: unknown): DoctypeField['kind'];
160
185
  /**
161
186
  * Recursively injects the `kind` discriminant into a raw field object and, for fieldsets,
162
187
  * into each of its nested `schema` children — mirroring exactly what Zod's `preprocess`
@@ -173,6 +198,27 @@ export type DoctypeField = ValueField | FieldsetField | TableField;
173
198
  * @public
174
199
  */
175
200
  export declare function normalizeFieldKind(field: unknown): unknown;
201
+ /**
202
+ * Remove the `kind` discriminant from a field, recursing into a fieldset's children.
203
+ *
204
+ * The outbound half of the boundary {@link normalizeFieldKind} owns inbound. `kind` is a
205
+ * discriminated-union tag the parser synthesizes, not something an author writes, so nothing that
206
+ * *writes* a doctype should put it on disk — the generator and the docbuilder's save both call
207
+ * this. Without it the two round-trip asymmetrically: every save adds a key the file never had.
208
+ *
209
+ * Strips only when `injectKind` would restore exactly what was removed. A fieldset carrying no
210
+ * `schema` re-infers as a plain field, so its `kind` is kept rather than silently re-typing the
211
+ * document; `DoctypeMeta` requires `schema` on a fieldset, so that shape is already invalid and
212
+ * belongs to the load gate, not here.
213
+ *
214
+ * Table `columns` are {@link ColumnSchema} entries rather than `DoctypeField`s and never carry an
215
+ * injected `kind`, so they are passed through untouched — the same asymmetry `injectKind` has.
216
+ *
217
+ * @param field - a field object, as held in memory after parsing
218
+ * @returns the field without `kind`, safe to serialize
219
+ * @public
220
+ */
221
+ export declare function stripFieldKind(field: unknown): unknown;
176
222
  /**
177
223
  * The field properties a `source: 'introspected'` marker freezes — the ones the database owns.
178
224
  *
@@ -194,15 +240,19 @@ export declare const INTROSPECTED_IDENTITY_PROPS: readonly ["fieldname", "primar
194
240
  * route/store key from it. Call this; never re-derive the rule at the call site, or the two will
195
241
  * drift and the client will key records by a column the server never queried.
196
242
  *
197
- * Two deliberate limits, both matching the shape `primaryKey` actually has:
198
- * - Only **top-level** fields are scanned. `primaryKey` is a `ValueField` flag and a fieldset's
199
- * children are not identity columns, so a nested match would be an authoring error, not a PK.
200
- * - The **first** match wins. Identity is single-valued by design — a doctype describes the API
201
- * surface, and mapping a composite database key onto one identity there is the adapter's job —
202
- * so a doctype declaring several is malformed rather than composite. `DoctypeMeta` rejects that
203
- * at the load gate; this stays total for callers holding fields that never went through it.
243
+ * Two deliberate rules, both matching the shape `primaryKey` actually has:
244
+ * - Fieldset children are **included**, via {@link flattenFields}. A fieldset is layout, not
245
+ * scope: its children are fields of the doctype with columns of their own, which is why the
246
+ * adapter's SELECT already descends and why `getDisplayField` does too. Scanning top level only
247
+ * did not *refuse* a nested declaration — it ignored one, so an author marked identity and
248
+ * nothing honoured it and nothing said so.
249
+ * - The **first** match in document order wins. Identity is single-valued by design — a doctype
250
+ * describes the API surface, and mapping a composite database key onto one identity there is the
251
+ * adapter's job — so a doctype declaring several is malformed rather than composite.
252
+ * `DoctypeMeta` rejects that at the load gate; this stays total for callers holding fields that
253
+ * never went through it.
204
254
  *
205
- * @param fields - the doctype's top-level fields
255
+ * @param fields - the doctype's fields; fieldset children are descended into
206
256
  * @returns the primary-key field, or `undefined` for a PK-less doctype
207
257
  * @public
208
258
  */
@@ -298,6 +348,7 @@ export declare const ValueFieldSchema: z.ZodObject<{
298
348
  doctype: z.ZodOptional<z.ZodString>;
299
349
  label: z.ZodOptional<z.ZodString>;
300
350
  width: z.ZodOptional<z.ZodString>;
351
+ height: z.ZodOptional<z.ZodString>;
301
352
  align: z.ZodOptional<z.ZodEnum<{
302
353
  left: "left";
303
354
  right: "right";
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/field.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/field.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,wFAQtB,CAAA;AAEH;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD;;;GAGG;AACH,eAAO,MAAM,eAAe;;iBAQzB,CAAA;AAEH;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAM7D;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IAC1B,+DAA+D;IAC/D,IAAI,EAAE,OAAO,CAAA;IACb,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;kFAC8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAA;IACrD,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;wFAGoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0CAA0C;IAC1C,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB;kCAC8B;IAC9B,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,+BAA+B;IAC/B,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,4FAA4F;IAC5F,WAAW,CAAC,EAAE,WAAW,GAAG,KAAK,GAAG,YAAY,GAAG,YAAY,CAAA;IAC/D;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,cAAc,CAAA;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,8DAA8D;IAC9D,IAAI,EAAE,UAAU,CAAA;IAChB,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAA;IACjB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,eAAe,CAAA;IACtB,uEAAuE;IACvE,MAAM,EAAE,YAAY,EAAE,CAAA;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IAC1B,yDAAyD;IACzD,IAAI,EAAE,OAAO,CAAA;IACb,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oFAAoF;IACpF,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,uFAAuF;IACvF,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,uDAAuD;IACvD,IAAI,CAAC,EAAE,eAAe,CAAA;CACtB;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,UAAU,CAAA;AAMlE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAKnE;AAiCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS1D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAYtD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,uFAO9B,CAAA;AAEV;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,UAAU,GAAG,SAAS,CAE1F;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,CAAC,UAAU,GAAG,UAAU,CAAC,EAAE,CAU1F;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE,SAAS,YAAY,EAAE,EAC/B,YAAY,EAAE,MAAM,GAAG,SAAS,GAC9B,UAAU,GAAG,SAAS,CAKxB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAExE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,SAAS,YAAY,EAAE,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,GAAG,SAAS,CAUpB;AA8ED;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA2B,CAAA;AAExD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBAA8B,CAAA;AAE9D;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA2B,CAAA;AAExD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,mFAA6B,CAAA"}
package/dist/src/field.js CHANGED
@@ -40,6 +40,37 @@ export const FieldValidation = z
40
40
  // ---------------------------------------------------------------------------
41
41
  // Zod runtime validation schemas
42
42
  // ---------------------------------------------------------------------------
43
+ /**
44
+ * Which of the three field shapes an entry has, read from the entry's own structure.
45
+ *
46
+ * The single definition of that question. It had three copies before this — the parser's
47
+ * `injectKind`, {@link stripFieldKind}'s agreement check, and the docbuilder's own
48
+ * `isValueField` in another package — each free to drift, and drift here re-types a field rather
49
+ * than throwing: a value field read as a fieldset loses its column, a fieldset read as a value
50
+ * field loses every child.
51
+ *
52
+ * Deliberately **shape-only**: a declared `kind` is ignored. Two callers depend on that. The
53
+ * stripper compares this against the declaration to decide whether removing it is lossless, which
54
+ * it cannot do if this honours it. The docbuilder reads raw JSON off disk and classifies entries to
55
+ * decide which to render as editable rows — and `kind` is Stonecrop's own discriminant, not
56
+ * something a doctype author writes, so a tool reading a file has no business consulting it.
57
+ *
58
+ * `injectKind` is the one place a declaration still wins, and only to leave an already-parsed
59
+ * object untouched on its way back through.
60
+ *
61
+ * @param field - a field entry, authored or parsed
62
+ * @returns the kind its shape implies
63
+ * @public
64
+ */
65
+ export function inferFieldKind(field) {
66
+ if (typeof field !== 'object' || field === null || Array.isArray(field))
67
+ return 'field';
68
+ if ('schema' in field)
69
+ return 'fieldset';
70
+ if ('columns' in field)
71
+ return 'table';
72
+ return 'field';
73
+ }
43
74
  /**
44
75
  * Infers the `kind` discriminant from the structural properties of a raw field
45
76
  * object, then injects it if absent. This allows authored JSON to omit `kind`
@@ -63,13 +94,14 @@ function injectKind(data) {
63
94
  return data;
64
95
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- safe: non-null, non-array object verified by guards above
65
96
  const obj = data;
97
+ // An explicit `kind` is left exactly as it was found: this is the one place a declaration still
98
+ // beats the shape, and only so an already-parsed object survives a second pass unchanged.
99
+ // Returning `data` itself rather than rebuilding it also preserves identity and key order.
100
+ // Nothing outside this function shares that precedence — a reader classifying a file on disk
101
+ // wants `inferFieldKind`, because `kind` is ours and no author writes it.
66
102
  if ('kind' in obj)
67
103
  return data;
68
- if ('schema' in obj)
69
- return { kind: 'fieldset', ...obj };
70
- if ('columns' in obj)
71
- return { kind: 'table', ...obj };
72
- return { kind: 'field', ...obj };
104
+ return { kind: inferFieldKind(obj), ...obj };
73
105
  }
74
106
  /**
75
107
  * Recursively injects the `kind` discriminant into a raw field object and, for fieldsets,
@@ -97,6 +129,39 @@ export function normalizeFieldKind(field) {
97
129
  }
98
130
  return injected;
99
131
  }
132
+ /**
133
+ * Remove the `kind` discriminant from a field, recursing into a fieldset's children.
134
+ *
135
+ * The outbound half of the boundary {@link normalizeFieldKind} owns inbound. `kind` is a
136
+ * discriminated-union tag the parser synthesizes, not something an author writes, so nothing that
137
+ * *writes* a doctype should put it on disk — the generator and the docbuilder's save both call
138
+ * this. Without it the two round-trip asymmetrically: every save adds a key the file never had.
139
+ *
140
+ * Strips only when `injectKind` would restore exactly what was removed. A fieldset carrying no
141
+ * `schema` re-infers as a plain field, so its `kind` is kept rather than silently re-typing the
142
+ * document; `DoctypeMeta` requires `schema` on a fieldset, so that shape is already invalid and
143
+ * belongs to the load gate, not here.
144
+ *
145
+ * Table `columns` are {@link ColumnSchema} entries rather than `DoctypeField`s and never carry an
146
+ * injected `kind`, so they are passed through untouched — the same asymmetry `injectKind` has.
147
+ *
148
+ * @param field - a field object, as held in memory after parsing
149
+ * @returns the field without `kind`, safe to serialize
150
+ * @public
151
+ */
152
+ export function stripFieldKind(field) {
153
+ if (typeof field !== 'object' || field === null || Array.isArray(field))
154
+ return field;
155
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- safe: non-null, non-array object verified by the guard above
156
+ const obj = field;
157
+ if (obj.kind !== undefined && obj.kind !== inferFieldKind(obj))
158
+ return field;
159
+ const { kind: _kind, ...rest } = obj;
160
+ if (Array.isArray(rest.schema)) {
161
+ return { ...rest, schema: rest.schema.map(stripFieldKind) };
162
+ }
163
+ return rest;
164
+ }
100
165
  /**
101
166
  * The field properties a `source: 'introspected'` marker freezes — the ones the database owns.
102
167
  *
@@ -125,20 +190,24 @@ export const INTROSPECTED_IDENTITY_PROPS = [
125
190
  * route/store key from it. Call this; never re-derive the rule at the call site, or the two will
126
191
  * drift and the client will key records by a column the server never queried.
127
192
  *
128
- * Two deliberate limits, both matching the shape `primaryKey` actually has:
129
- * - Only **top-level** fields are scanned. `primaryKey` is a `ValueField` flag and a fieldset's
130
- * children are not identity columns, so a nested match would be an authoring error, not a PK.
131
- * - The **first** match wins. Identity is single-valued by design — a doctype describes the API
132
- * surface, and mapping a composite database key onto one identity there is the adapter's job —
133
- * so a doctype declaring several is malformed rather than composite. `DoctypeMeta` rejects that
134
- * at the load gate; this stays total for callers holding fields that never went through it.
193
+ * Two deliberate rules, both matching the shape `primaryKey` actually has:
194
+ * - Fieldset children are **included**, via {@link flattenFields}. A fieldset is layout, not
195
+ * scope: its children are fields of the doctype with columns of their own, which is why the
196
+ * adapter's SELECT already descends and why `getDisplayField` does too. Scanning top level only
197
+ * did not *refuse* a nested declaration — it ignored one, so an author marked identity and
198
+ * nothing honoured it and nothing said so.
199
+ * - The **first** match in document order wins. Identity is single-valued by design — a doctype
200
+ * describes the API surface, and mapping a composite database key onto one identity there is the
201
+ * adapter's job — so a doctype declaring several is malformed rather than composite.
202
+ * `DoctypeMeta` rejects that at the load gate; this stays total for callers holding fields that
203
+ * never went through it.
135
204
  *
136
- * @param fields - the doctype's top-level fields
205
+ * @param fields - the doctype's fields; fieldset children are descended into
137
206
  * @returns the primary-key field, or `undefined` for a PK-less doctype
138
207
  * @public
139
208
  */
140
209
  export function getPrimaryKeyField(fields) {
141
- return fields.find((f) => f.kind === 'field' && Boolean(f.primaryKey));
210
+ return flattenFields(fields).find((f) => f.kind === 'field' && Boolean(f.primaryKey));
142
211
  }
143
212
  /**
144
213
  * Recursively flatten Fieldset containers into a flat array of non-container fields.
@@ -257,6 +326,7 @@ function createDoctypeFieldSchemas() {
257
326
  doctype: z.string().min(1).optional(),
258
327
  label: z.string().optional(),
259
328
  width: z.string().optional(),
329
+ height: z.string().optional(),
260
330
  align: z.enum(['left', 'center', 'right', 'start', 'end']).optional(),
261
331
  edit: z.boolean().optional(),
262
332
  mask: z.string().optional(),
@@ -2,11 +2,11 @@ export type { InteractionMode } from './mode';
2
2
  export { TableViewConfig } from './table';
3
3
  export { CANONICAL_COMPONENTS, COMPONENT_CATEGORY, COMPONENT_LINK_EXPANSION, componentCategory, componentLinkExpansion, resolveLinkRenderMode, type ComponentCategory, type LinkExpansion, type LinkRenderMode, } from './component-meta';
4
4
  export type { DoctypeField, FieldOptions, FieldValidation, FieldsetField, TableField, ValueField } from './field';
5
- export { DoctypeFieldSchema, FieldsetFieldSchema, flattenFields, getDisplayField, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
6
- export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, isActionAllowedInState, LINK_DISPLAY_SUFFIX, linkDisplayFieldname, } from './doctype';
5
+ export { DoctypeFieldSchema, FieldsetFieldSchema, flattenFields, getDisplayField, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, inferFieldKind, stripFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
6
+ export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, getDoctypeSlug, isActionAllowedInState, LINK_DISPLAY_SUFFIX, linkDisplayFieldname, } from './doctype';
7
7
  export type { Cardinality, CustomFetch, DataClient, DoctypeContext, DoctypeMeta, DoctypeRef, FetchStrategy, GetRecordOptions, GetRecordResult, GetRecordsOptions, GetRecordsResult, LazyFetch, LinkDeclaration, SerializedFunction, SyncFetch, } from './doctype';
8
8
  export { parseDoctype, parseField, validateDoctype, validateField, type ValidationError, type ValidationResult, } from './validation';
9
- export { buildScalarMap, classifyFieldType, convertGraphQLSchema, defaultIsEntityField, defaultIsEntityType, formatDoctypeDrift, GQL_SCALAR_MAP, INTERNAL_SCALARS, mergeIntrospectedDoctype, WELL_KNOWN_SCALARS, type AuthoredDoctype, type ConvertedGraphQLDoctype, type DoctypeDrift, type GraphQLConversionFieldMeta, type GraphQLConversionOptions, type IntrospectionSource, type MergeResult, } from './converter';
9
+ export { aggregateDoctypeName, buildAggregateDoctype, buildScalarMap, classifyFieldType, convertGraphQLSchema, defaultIsEntityField, defaultIsEntityType, formatDoctypeDrift, GQL_SCALAR_MAP, INTERNAL_SCALARS, mergeIntrospectedDoctype, planGeneration, WELL_KNOWN_SCALARS, type AuthoredDoctype, type ConvertedGraphQLDoctype, type DoctypeDrift, type GraphQLConversionFieldMeta, type GenerationPlanEntry, type GenerationPlanOptions, type GraphQLConversionOptions, type IntrospectionSource, type MergeOptions, type MergeResult, } from './converter';
10
10
  export { toSlug, toPascalCase, pascalToSnake, snakeToCamel, camelToSnake, snakeToLabel, camelToLabel } from './naming';
11
11
  export type { ColumnSchema } from './column-schema';
12
12
  export type { BadgeDescriptor, BadgePresentation, BadgeSpec, BadgeSpecObject, BadgeVariant, SelectOptions, } from './badge';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAG7C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAGzC,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,GACnB,MAAM,kBAAkB,CAAA;AAGzB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACjH,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,2BAA2B,EAC3B,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,SAAS,CAAA;AAKhB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,WAAW,CAAA;AAClB,YAAY,EACX,WAAW,EACX,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,SAAS,GACT,MAAM,WAAW,CAAA;AAGlB,OAAO,EACN,YAAY,EACZ,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACrB,MAAM,cAAc,CAAA;AAGrB,OAAO,EACN,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,WAAW,GAChB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGtH,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAGnD,YAAY,EACX,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,YAAY,EACZ,aAAa,GACb,MAAM,SAAS,CAAA;AAChB,OAAO,EACN,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,aAAa,GACb,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAG7C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAGzC,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,GACnB,MAAM,kBAAkB,CAAA;AAGzB,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACjH,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,2BAA2B,EAC3B,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GAChB,MAAM,SAAS,CAAA;AAKhB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,WAAW,CAAA;AAClB,YAAY,EACX,WAAW,EACX,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,SAAS,GACT,MAAM,WAAW,CAAA;AAGlB,OAAO,EACN,YAAY,EACZ,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACrB,MAAM,cAAc,CAAA;AAGrB,OAAO,EACN,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,WAAW,GAChB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAGtH,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAGnD,YAAY,EACX,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,YAAY,EACZ,aAAa,GACb,MAAM,SAAS,CAAA;AAChB,OAAO,EACN,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,aAAa,GACb,MAAM,SAAS,CAAA"}
package/dist/src/index.js CHANGED
@@ -2,15 +2,15 @@
2
2
  export { TableViewConfig } from './table';
3
3
  // Component → semantic category — the single source of "what kind of value does this component render"
4
4
  export { CANONICAL_COMPONENTS, COMPONENT_CATEGORY, COMPONENT_LINK_EXPANSION, componentCategory, componentLinkExpansion, resolveLinkRenderMode, } from './component-meta';
5
- export { DoctypeFieldSchema, FieldsetFieldSchema, flattenFields, getDisplayField, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
5
+ export { DoctypeFieldSchema, FieldsetFieldSchema, flattenFields, getDisplayField, getPrimaryKeyField, getRecordIdentity, getRecordIdField, INTROSPECTED_IDENTITY_PROPS, normalizeFieldKind, inferFieldKind, stripFieldKind, TableFieldSchema, ValueFieldSchema, } from './field';
6
6
  // Doctype schema
7
7
  // ActionDefinition and WorkflowMeta are exported as values (Zod schemas) so consumers can use
8
8
  // .safeParse(), .shape, etc. at runtime. TypeScript types are inferred from the same exports.
9
- export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, isActionAllowedInState, LINK_DISPLAY_SUFFIX, linkDisplayFieldname, } from './doctype';
9
+ export { ActionDefinition, TriggerDefinition, WorkflowLayout, WorkflowMeta, getDoctypeSlug, isActionAllowedInState, LINK_DISPLAY_SUFFIX, linkDisplayFieldname, } from './doctype';
10
10
  // Validation helpers
11
11
  export { parseDoctype, parseField, validateDoctype, validateField, } from './validation';
12
12
  // GraphQL to Doctype conversion
13
- export { buildScalarMap, classifyFieldType, convertGraphQLSchema, defaultIsEntityField, defaultIsEntityType, formatDoctypeDrift, GQL_SCALAR_MAP, INTERNAL_SCALARS, mergeIntrospectedDoctype, WELL_KNOWN_SCALARS, } from './converter';
13
+ export { aggregateDoctypeName, buildAggregateDoctype, buildScalarMap, classifyFieldType, convertGraphQLSchema, defaultIsEntityField, defaultIsEntityType, formatDoctypeDrift, GQL_SCALAR_MAP, INTERNAL_SCALARS, mergeIntrospectedDoctype, planGeneration, WELL_KNOWN_SCALARS, } from './converter';
14
14
  // Naming utilities
15
15
  export { toSlug, toPascalCase, pascalToSnake, snakeToCamel, camelToSnake, snakeToLabel, camelToLabel } from './naming';
16
16
  export { hasBadgeOptions, isBadgeDescriptor, isSelectChoiceMap, isSelectOptions, lookupBadge, selectChoices, } from './badge';