@stonecrop/stonecrop 0.13.8 → 0.13.9

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.
@@ -1,8 +1,6 @@
1
- import type { SchemaTypes } from '@stonecrop/aform';
2
- import type { LinkDeclaration } from '@stonecrop/schema';
1
+ import type { DoctypeField, LinkDeclaration } from '@stonecrop/schema';
3
2
  import { Component } from 'vue';
4
- import type { ImmutableDoctype } from './types';
5
- import type { DoctypeConfig } from './types/doctype';
3
+ import type { DoctypeConfig, ImmutableDoctype } from './types/doctype';
6
4
  /**
7
5
  * Doctype runtime class with Immutable.js collections for HST change tracking.
8
6
  * @public
@@ -100,21 +98,21 @@ export default class Doctype {
100
98
  */
101
99
  static fromObject(config: DoctypeConfig): Doctype;
102
100
  /**
103
- * Returns the schema as a plain array for use with components that expect
104
- * plain JavaScript arrays (e.g., AForm, ATable).
101
+ * Returns the raw authoring schema as a plain array.
102
+ * For the resolved schema suitable for AForm, use `registry.resolveSchema(doctype)`.
105
103
  *
106
- * @returns Array of schema fields
104
+ * @returns Array of raw DoctypeField authoring definitions
107
105
  *
108
106
  * @example
109
107
  * ```ts
110
- * const schemaArray = doctype.getSchemaArray()
111
- * // Use with AForm
112
- * <AForm :schema="schemaArray" v-model:data="formData" />
108
+ * const fields = doctype.getSchemaArray()
109
+ * // Pass to resolveSchema for AForm-ready output:
110
+ * const resolved = registry.resolveSchema(doctype)
113
111
  * ```
114
112
  *
115
113
  * @public
116
114
  */
117
- getSchemaArray(): SchemaTypes[];
115
+ getSchemaArray(): DoctypeField[];
118
116
  /**
119
117
  * Returns the actions as a plain object for use with components that expect
120
118
  * plain JavaScript objects.
@@ -1 +1 @@
1
- {"version":3,"file":"doctype.d.ts","sourceRoot":"","sources":["../../src/doctype.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,mBAAmB,CAAA;AAEtE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAE/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,OAAO;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IAExB;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAE3C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAA;IAE/C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAA;IAE7C;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAA;IAE9B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAEhD;;;;;;;;OAQG;gBAEF,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,EACtC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,EACpC,SAAS,CAAC,EAAE,SAAS,EACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;IAUxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO;IAOjD;;;;;;;;;;;;;;OAcG;IACH,cAAc,IAAI,WAAW,EAAE;IAK/B;;;;;;;OAOG;IACH,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAK5C;;;;;;;;;;;;;;OAcG;IACH,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAuC3F;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAC7B;QACA,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,EAAE,MAAM,CAAA;QACf,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;QACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;QACxB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAC7B,GACD,SAAS;IASZ;;;;;;;;;;;;;;;OAeG;IACH,IAAI,IAAI,WAKP;CACD"}
1
+ {"version":3,"file":"doctype.d.ts","sourceRoot":"","sources":["../../src/doctype.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAgB,MAAM,mBAAmB,CAAA;AAEpF,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEtE;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,OAAO;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IAExB;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAE3C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAA;IAE/C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAA;IAE7C;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAA;IAE9B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAEhD;;;;;;;;OAQG;gBAEF,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,EACtC,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,EACpC,SAAS,CAAC,EAAE,SAAS,EACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;IAUxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqCG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO;IAOjD;;;;;;;;;;;;;;OAcG;IACH,cAAc,IAAI,YAAY,EAAE;IAKhC;;;;;;;OAOG;IACH,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAK5C;;;;;;;;;;;;;;OAcG;IACH,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAuC3F;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAC7B;QACA,KAAK,EAAE,MAAM,CAAA;QACb,OAAO,EAAE,MAAM,CAAA;QACf,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;QACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;QACxB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAC7B,GACD,SAAS;IASZ;;;;;;;;;;;;;;;OAeG;IACH,IAAI,IAAI,WAKP;CACD"}
@@ -109,16 +109,16 @@ export default class Doctype {
109
109
  return new Doctype(config.name, schema, config.workflow, actions, undefined, config.links);
110
110
  }
111
111
  /**
112
- * Returns the schema as a plain array for use with components that expect
113
- * plain JavaScript arrays (e.g., AForm, ATable).
112
+ * Returns the raw authoring schema as a plain array.
113
+ * For the resolved schema suitable for AForm, use `registry.resolveSchema(doctype)`.
114
114
  *
115
- * @returns Array of schema fields
115
+ * @returns Array of raw DoctypeField authoring definitions
116
116
  *
117
117
  * @example
118
118
  * ```ts
119
- * const schemaArray = doctype.getSchemaArray()
120
- * // Use with AForm
121
- * <AForm :schema="schemaArray" v-model:data="formData" />
119
+ * const fields = doctype.getSchemaArray()
120
+ * // Pass to resolveSchema for AForm-ready output:
121
+ * const resolved = registry.resolveSchema(doctype)
122
122
  * ```
123
123
  *
124
124
  * @public
@@ -1,4 +1,4 @@
1
- import type { SchemaTypes } from '@stonecrop/aform';
1
+ import type { ResolvedField } from '@stonecrop/aform';
2
2
  import type { LinkDeclaration } from '@stonecrop/schema';
3
3
  import { Router } from 'vue-router';
4
4
  import Doctype from './doctype';
@@ -65,70 +65,50 @@ export default class Registry {
65
65
  */
66
66
  addDoctype(doctype: Doctype): void;
67
67
  /**
68
- * Resolve nested Doctype fields in a schema by embedding child schemas inline.
68
+ * Resolve a Doctype's authoring schema into a rendered schema array suitable for AForm.
69
69
  *
70
- * Accepts a Doctype and extracts `fields` and `links` internally.
71
- * Fields array contains both scalar fields and link fields (with fieldtype: 'Link').
72
- * Render order is determined by the order of fields in the fields array.
70
+ * Transforms `DoctypeField[]` (authoring space) `ResolvedField[]` (rendering space):
71
+ * - `kind: 'field'` (not Link) → `ResolvedScalar`
72
+ * - `kind: 'field'` (Link, no declaration) `ResolvedScalar` with `component: 'AFormLink'`
73
+ * - `kind: 'field'` (Link, `noneOrMany`/`atLeastOne`) → `ResolvedTable`
74
+ * - `kind: 'field'` (Link, `one`/`atMostOne`) → `ResolvedLink`
75
+ * - `kind: 'fieldset'` → `ResolvedFieldset` (children resolved recursively)
76
+ * - `kind: 'table'` → `ResolvedTable` (columns as `ColumnSchema[]`)
73
77
  *
74
- * For each link field:
75
- * - Looks up the corresponding link declaration in `links` by fieldname
76
- * - `cardinality: 'noneOrMany'` or `'atLeastOne'`: auto-derives `columns` from the target's schema,
77
- * sets `component` to `link.component ?? 'ATable'`, `config: { view: 'list' }`.
78
- * - `cardinality: 'one'` or `'atMostOne'`: embeds the target schema as the entry's
79
- * `schema` property, sets `component` to `link.component ?? 'AForm'`.
80
- *
81
- * Recurses for deeply nested doctypes. Circular references are protected against.
82
- * Returns a new array — does not mutate the original.
78
+ * Circular references are protected against via the `visited` set.
83
79
  *
84
80
  * @param doctype - The doctype to resolve
85
81
  * @param visited - Internal — set of already-visited doctype slugs for cycle detection
86
- * @returns A new schema array with nested links resolved
82
+ * @returns A resolved schema array ready for AForm
87
83
  *
88
84
  * @public
89
85
  */
90
- resolveSchema(doctype: Doctype, visited?: Set<string>): SchemaTypes[];
86
+ resolveSchema(doctype: Doctype, visited?: Set<string>): ResolvedField[];
91
87
  /**
92
- * Recursively resolve a flat fields array using the provided link context.
93
- * Used by resolveSchema to handle fieldset children.
88
+ * Recursively resolve a `DoctypeField[]` using the provided link context.
89
+ * Called by `resolveSchema` and recursively for fieldset children.
94
90
  * @internal
95
91
  */
96
92
  private resolveFields;
97
93
  /**
98
- * Build an ATable configuration from a field and child schema.
99
- * Data-model properties from the source field are preserved via the spread `field` argument.
94
+ * Build a `ResolvedTable` from a resolved Link field with many cardinality.
95
+ * Extracts scalar column definitions from the child schema.
100
96
  * @internal
101
97
  */
102
98
  private buildTableConfig;
103
99
  /**
104
- * Initialize a new record with default values based on a schema.
105
- *
106
- * @remarks
107
- * Creates a plain object with keys from the schema's fieldnames and default values
108
- * derived from each field's `fieldtype`:
109
- * - Data, Text → `''`
110
- * - Check → `false`
111
- * - Int, Float, Decimal, Currency, Quantity → `0`
112
- * - JSON → `{}`
113
- * - Doctype with `cardinality: 'noneOrMany'` or `'atLeastOne'` → `[]`
114
- * - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
115
- * - All others → `null`
100
+ * Initialize a new record with default values based on a resolved schema.
101
+ * Narrows by `kind` discriminator for precise branch selection.
116
102
  *
117
- * For Doctype fields with a resolved `schema` array (cardinality: 'one'), recursively
118
- * initializes the nested record.
103
+ * - `kind: 'table'` or `kind: 'link'` `[]` or `{}`
104
+ * - `kind: 'fieldset'` → recursively initializes children as `{}`
105
+ * - `kind: 'field'` → derives default from `fieldtype`; falls back to `null`
119
106
  *
120
- * @param schema - The schema array to derive defaults from
107
+ * @param schema - The resolved schema array to derive defaults from
121
108
  * @returns A plain object with default values for each field
122
- *
123
- * @example
124
- * ```ts
125
- * const defaults = registry.initializeRecord(addressSchema)
126
- * // { street: '', city: '', state: '', zip_code: '' }
127
- * ```
128
- *
129
109
  * @public
130
110
  */
131
- initializeRecord(schema: SchemaTypes[]): Record<string, any>;
111
+ initializeRecord(schema: ResolvedField[]): Record<string, any>;
132
112
  /**
133
113
  * Get a registered doctype by slug
134
114
  * @param slug - The doctype slug to look up
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAa,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,OAAO,MAAM,WAAW,CAAA;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC5B;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAA;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAa;IAElC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAK;IAE/C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc,CAAqE;IAE3F;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB,CAAgB;IAE3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IAExB;;;;OAIG;gBACS,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAUjG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEpE;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO;IAuB3B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE;IAkHrE;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAkDrB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAoD5D;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAI7C;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAWvF;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBtG;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAgC5B"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAiE,MAAM,kBAAkB,CAAA;AACpH,OAAO,KAAK,EAA8B,eAAe,EAA+B,MAAM,mBAAmB,CAAA;AACjH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,OAAO,MAAM,WAAW,CAAA;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC5B;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAA;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAa;IAElC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAK;IAE/C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc,CAAqE;IAE3F;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB,CAAgB;IAE3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IAExB;;;;OAIG;gBACS,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAUjG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEpE;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO;IAuB3B;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,aAAa,EAAE;IA4BvE;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAiFrB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IA6C9D;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAI7C;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAWvF;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBtG;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAgC5B"}
@@ -88,245 +88,202 @@ export default class Registry {
88
88
  }
89
89
  }
90
90
  /**
91
- * Resolve nested Doctype fields in a schema by embedding child schemas inline.
91
+ * Resolve a Doctype's authoring schema into a rendered schema array suitable for AForm.
92
92
  *
93
- * Accepts a Doctype and extracts `fields` and `links` internally.
94
- * Fields array contains both scalar fields and link fields (with fieldtype: 'Link').
95
- * Render order is determined by the order of fields in the fields array.
93
+ * Transforms `DoctypeField[]` (authoring space) `ResolvedField[]` (rendering space):
94
+ * - `kind: 'field'` (not Link) → `ResolvedScalar`
95
+ * - `kind: 'field'` (Link, no declaration) `ResolvedScalar` with `component: 'AFormLink'`
96
+ * - `kind: 'field'` (Link, `noneOrMany`/`atLeastOne`) → `ResolvedTable`
97
+ * - `kind: 'field'` (Link, `one`/`atMostOne`) → `ResolvedLink`
98
+ * - `kind: 'fieldset'` → `ResolvedFieldset` (children resolved recursively)
99
+ * - `kind: 'table'` → `ResolvedTable` (columns as `ColumnSchema[]`)
96
100
  *
97
- * For each link field:
98
- * - Looks up the corresponding link declaration in `links` by fieldname
99
- * - `cardinality: 'noneOrMany'` or `'atLeastOne'`: auto-derives `columns` from the target's schema,
100
- * sets `component` to `link.component ?? 'ATable'`, `config: { view: 'list' }`.
101
- * - `cardinality: 'one'` or `'atMostOne'`: embeds the target schema as the entry's
102
- * `schema` property, sets `component` to `link.component ?? 'AForm'`.
103
- *
104
- * Recurses for deeply nested doctypes. Circular references are protected against.
105
- * Returns a new array — does not mutate the original.
101
+ * Circular references are protected against via the `visited` set.
106
102
  *
107
103
  * @param doctype - The doctype to resolve
108
104
  * @param visited - Internal — set of already-visited doctype slugs for cycle detection
109
- * @returns A new schema array with nested links resolved
105
+ * @returns A resolved schema array ready for AForm
110
106
  *
111
107
  * @public
112
108
  */
113
109
  resolveSchema(doctype, visited) {
114
110
  const seen = visited ?? new Set();
115
111
  const slug = doctype.slug;
116
- // Prevent circular resolution
112
+ // Prevent circular resolution — return all ValueField entries as scalars (link not expanded)
117
113
  if (seen.has(slug)) {
118
- return doctype.schema ? (Array.isArray(doctype.schema) ? doctype.schema : Array.from(doctype.schema)) : [];
114
+ const fallback = doctype.schema ? doctype.schema.toArray() : [];
115
+ return fallback
116
+ .filter((f) => f.kind === 'field')
117
+ .map(({ cardinality: _c, ...rest }) => rest);
119
118
  }
120
119
  seen.add(slug);
121
- // Convert schema to array
122
- const schemaArray = doctype.schema
123
- ? Array.isArray(doctype.schema)
124
- ? doctype.schema
125
- : Array.from(doctype.schema)
126
- : [];
127
- // Build a map of link declarations by fieldname for quick lookup
128
- // Use the link's fieldname property if set, otherwise use the key
120
+ const schemaArray = doctype.schema ? doctype.schema.toArray() : [];
121
+ // Map link declarations by fieldname (link.fieldname ?? key)
129
122
  const linksByFieldname = new Map();
130
123
  if (doctype.links) {
131
124
  for (const [key, link] of Object.entries(doctype.links)) {
132
- const linkFieldname = link.fieldname ?? key;
133
- linksByFieldname.set(linkFieldname, link);
134
- }
135
- }
136
- // Process fields in order: scalar fields copied as-is, link fields resolved
137
- const resolvedFields = [];
138
- for (const field of schemaArray) {
139
- // Check if this field is a link field (fieldtype: 'Link')
140
- if ('fieldtype' in field && field.fieldtype === 'Link') {
141
- const link = linksByFieldname.get(field.fieldname);
142
- if (!link) {
143
- // oxlint-disable typescript/no-unsafe-type-assertion -- SchemaTypes union narrowed to FieldMeta by fieldtype === 'Link' check; options may not exist on all members
144
- const linkDoctype = typeof field.options === 'string' ? field.options : undefined;
145
- // oxlint-enable typescript/no-unsafe-type-assertion
146
- if (linkDoctype === undefined) {
147
- console.warn(`[Stonecrop] Link field "${field.fieldname}" has no \`options\` or corresponding \`links\` declaration. ` +
148
- `AFormLink will be created without a \`doctype\` prop, so navigation will not work. ` +
149
- `Add \`"options": "<doctype-slug>"\` to the field definition.`);
150
- }
151
- // Strip any raw `doctype` from the JSON; only `options` is the authoritative source.
152
- const { doctype: _rawDoctype, ...fieldRest } = field;
153
- resolvedFields.push({
154
- ...fieldRest,
155
- component: fieldRest.component || 'AFormLink',
156
- ...(linkDoctype !== undefined ? { doctype: linkDoctype } : {}),
157
- });
158
- continue;
159
- }
160
- const targetDoctype = this.registry[link.target];
161
- if (!targetDoctype) {
162
- // Target not found - copy as-is
163
- resolvedFields.push({ ...field });
164
- continue;
165
- }
166
- const childSchema = this.resolveSchema(targetDoctype, seen);
167
- // Extract properties consumed by resolution; preserve everything else
168
- // TODO: options and cardinality are untyped runtime properties on link fields; add them to
169
- // FormSchema (or a dedicated link field type) to remove this cast
170
- const { fieldtype: _ft, options: _opt, cardinality: _card, ...fieldRest } = field;
171
- if (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne') {
172
- // Many relationship — build table config
173
- resolvedFields.push(this.buildTableConfig({ ...fieldRest, label: fieldRest.label || field.fieldname }, childSchema, link.component));
174
- }
175
- else {
176
- // One relationship — embed form schema
177
- resolvedFields.push({
178
- ...fieldRest,
179
- label: fieldRest.label || field.fieldname,
180
- component: link.component || fieldRest.component || 'AForm',
181
- schema: childSchema,
182
- });
183
- }
184
- }
185
- else if ('schema' in field && Array.isArray(field.schema)) {
186
- // Fieldset — recursively resolve nested fields
187
- const resolvedChildren = this.resolveFields(field.schema, linksByFieldname, seen);
188
- resolvedFields.push({ ...field, schema: resolvedChildren });
189
- }
190
- else {
191
- // Scalar field — copy as-is
192
- resolvedFields.push({ ...field });
125
+ linksByFieldname.set(link.fieldname ?? key, link);
193
126
  }
194
127
  }
128
+ const result = this.resolveFields(schemaArray, linksByFieldname, seen);
195
129
  seen.delete(slug);
196
- return resolvedFields;
130
+ return result;
197
131
  }
198
132
  /**
199
- * Recursively resolve a flat fields array using the provided link context.
200
- * Used by resolveSchema to handle fieldset children.
133
+ * Recursively resolve a `DoctypeField[]` using the provided link context.
134
+ * Called by `resolveSchema` and recursively for fieldset children.
201
135
  * @internal
202
136
  */
203
137
  resolveFields(fields, links, visited) {
204
138
  const resolved = [];
205
139
  for (const field of fields) {
206
- if ('fieldtype' in field && field.fieldtype === 'Link') {
140
+ if (field.kind === 'field' && field.fieldtype === 'Link') {
207
141
  const link = links.get(field.fieldname);
208
142
  if (!link) {
209
- resolved.push({ ...field });
143
+ // Unresolved link — warn and produce a scalar with component: 'AFormLink'
144
+ const linkDoctype = typeof field.options === 'string' ? field.options : undefined;
145
+ if (linkDoctype === undefined) {
146
+ console.warn(`[Stonecrop] Link field "${field.fieldname}" has no \`options\` or corresponding \`links\` declaration. ` +
147
+ `AFormLink will be created without a \`doctype\` prop, so navigation will not work. ` +
148
+ `Add \`"options": "<doctype-slug>"\` to the field definition.`);
149
+ }
150
+ const { cardinality: _c, ...rest } = field;
151
+ resolved.push({
152
+ ...rest,
153
+ component: rest.component || 'AFormLink',
154
+ ...(linkDoctype !== undefined ? { doctype: linkDoctype } : {}),
155
+ });
210
156
  continue;
211
157
  }
212
158
  const targetDoctype = this.registry[link.target];
213
159
  if (!targetDoctype) {
214
- resolved.push({ ...field });
160
+ // Target not registered — copy as scalar
161
+ const { cardinality: _c, ...rest } = field;
162
+ resolved.push({ ...rest });
215
163
  continue;
216
164
  }
217
165
  const childSchema = this.resolveSchema(targetDoctype, new Set(visited));
218
- const { fieldtype: _ft, options: _opt, cardinality: _card, ...fieldRest } = field;
166
+ const { fieldtype: _ft, options: _opt, cardinality: _card, kind: _kind, ...fieldRest } = field;
219
167
  if (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne') {
220
- resolved.push(this.buildTableConfig({ ...fieldRest, label: fieldRest.label || field.fieldname }, childSchema, link.component));
168
+ resolved.push(this.buildTableConfig(field, childSchema, link.component));
221
169
  }
222
170
  else {
223
- resolved.push({
171
+ const linkEntry = {
224
172
  ...fieldRest,
173
+ kind: 'link',
225
174
  label: fieldRest.label || field.fieldname,
226
175
  component: link.component || fieldRest.component || 'AForm',
227
176
  schema: childSchema,
228
- });
177
+ };
178
+ resolved.push(linkEntry);
229
179
  }
230
180
  }
231
- else if ('schema' in field && Array.isArray(field.schema)) {
232
- resolved.push({ ...field, schema: this.resolveFields(field.schema, links, visited) });
181
+ else if (field.kind === 'fieldset') {
182
+ const resolvedChildren = this.resolveFields(field.schema, links, visited);
183
+ const { schema: _s, ...fieldRest } = field;
184
+ const fieldsetEntry = {
185
+ ...fieldRest,
186
+ schema: resolvedChildren,
187
+ };
188
+ resolved.push(fieldsetEntry);
189
+ }
190
+ else if (field.kind === 'table') {
191
+ // Inline table — columns become ColumnSchema[], add default config
192
+ const { columns, config, ...fieldRest } = field;
193
+ const tableEntry = {
194
+ ...fieldRest,
195
+ kind: 'table',
196
+ component: fieldRest.component || 'ATable',
197
+ schema: columns,
198
+ config: config ?? { view: 'list' },
199
+ };
200
+ resolved.push(tableEntry);
233
201
  }
234
202
  else {
235
- resolved.push({ ...field });
203
+ // Scalar field (kind: 'field', not a Link) — strip cardinality
204
+ const { cardinality: _c, ...rest } = field;
205
+ resolved.push({ ...rest });
236
206
  }
237
207
  }
238
208
  return resolved;
239
209
  }
240
210
  /**
241
- * Build an ATable configuration from a field and child schema.
242
- * Data-model properties from the source field are preserved via the spread `field` argument.
211
+ * Build a `ResolvedTable` from a resolved Link field with many cardinality.
212
+ * Extracts scalar column definitions from the child schema.
243
213
  * @internal
244
214
  */
245
215
  buildTableConfig(field, childSchema, component) {
246
- const resolved = {
247
- ...field,
248
- fieldname: field.fieldname,
249
- component: component || field.component || 'ATable',
216
+ // Only scalar fields become columns; strip kind and cardinality (runtime column spec)
217
+ const columns = childSchema
218
+ .filter((f) => f.kind === 'field')
219
+ .map(({ kind: _k, ...col }) => col);
220
+ const config = field.config ?? { view: 'list' };
221
+ const { fieldtype: _ft, options: _opt, cardinality: _card, ...fieldRest } = field;
222
+ return {
223
+ ...fieldRest,
250
224
  kind: 'table',
251
- schema: childSchema,
252
- config: field.config,
225
+ label: fieldRest.label || field.fieldname,
226
+ component: component || fieldRest.component || 'ATable',
227
+ schema: columns,
228
+ config,
253
229
  };
254
- if (!resolved.config) {
255
- resolved.config = { view: 'list' };
256
- }
257
- return resolved;
258
230
  }
259
231
  /**
260
- * Initialize a new record with default values based on a schema.
232
+ * Initialize a new record with default values based on a resolved schema.
233
+ * Narrows by `kind` discriminator for precise branch selection.
261
234
  *
262
- * @remarks
263
- * Creates a plain object with keys from the schema's fieldnames and default values
264
- * derived from each field's `fieldtype`:
265
- * - Data, Text → `''`
266
- * - Check → `false`
267
- * - Int, Float, Decimal, Currency, Quantity → `0`
268
- * - JSON → `{}`
269
- * - Doctype with `cardinality: 'noneOrMany'` or `'atLeastOne'` → `[]`
270
- * - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
271
- * - All others → `null`
235
+ * - `kind: 'table'` or `kind: 'link'` → `[]` or `{}`
236
+ * - `kind: 'fieldset'` recursively initializes children as `{}`
237
+ * - `kind: 'field'` → derives default from `fieldtype`; falls back to `null`
272
238
  *
273
- * For Doctype fields with a resolved `schema` array (cardinality: 'one'), recursively
274
- * initializes the nested record.
275
- *
276
- * @param schema - The schema array to derive defaults from
239
+ * @param schema - The resolved schema array to derive defaults from
277
240
  * @returns A plain object with default values for each field
278
- *
279
- * @example
280
- * ```ts
281
- * const defaults = registry.initializeRecord(addressSchema)
282
- * // { street: '', city: '', state: '', zip_code: '' }
283
- * ```
284
- *
285
241
  * @public
286
242
  */
287
243
  initializeRecord(schema) {
288
244
  const record = {};
289
- schema.forEach(field => {
290
- const fieldtype = 'fieldtype' in field ? field.fieldtype : 'Data';
291
- const cardinality = 'cardinality' in field ? field.cardinality : undefined;
292
- // 1:many — cardinality signals an array
293
- if (cardinality === 'noneOrMany' || cardinality === 'atLeastOne') {
245
+ for (const field of schema) {
246
+ if (field.kind === 'table') {
294
247
  record[field.fieldname] = [];
295
- return;
296
248
  }
297
- // Resolved 1:many table entry — kind discriminant set by buildTableConfig
298
- if ('kind' in field && field.kind === 'table') {
299
- record[field.fieldname] = [];
300
- return;
249
+ else if (field.kind === 'link') {
250
+ record[field.fieldname] = this.initializeRecord(field.schema);
301
251
  }
302
- // Resolved 1:1 link entry — has schema property (e.g., FieldsetSchema with nested schema)
303
- if ('schema' in field && Array.isArray(field.schema)) {
252
+ else if (field.kind === 'fieldset') {
304
253
  record[field.fieldname] = this.initializeRecord(field.schema);
305
- return;
306
254
  }
307
- switch (fieldtype) {
308
- case 'Data':
309
- case 'Text':
310
- case 'Code':
311
- record[field.fieldname] = '';
312
- break;
313
- case 'Check':
314
- record[field.fieldname] = false;
315
- break;
316
- case 'Int':
317
- case 'Float':
318
- case 'Decimal':
319
- case 'Currency':
320
- case 'Quantity':
321
- record[field.fieldname] = 0;
322
- break;
323
- case 'JSON':
324
- record[field.fieldname] = {};
325
- break;
326
- default:
327
- record[field.fieldname] = null;
255
+ else {
256
+ // kind: 'field' — derive from fieldtype
257
+ const fieldDefault = field.default;
258
+ if (fieldDefault !== undefined) {
259
+ record[field.fieldname] = fieldDefault;
260
+ }
261
+ else {
262
+ switch (field.fieldtype) {
263
+ case 'Data':
264
+ case 'Text':
265
+ case 'Code':
266
+ record[field.fieldname] = '';
267
+ break;
268
+ case 'Check':
269
+ record[field.fieldname] = false;
270
+ break;
271
+ case 'Int':
272
+ case 'Float':
273
+ case 'Decimal':
274
+ case 'Currency':
275
+ case 'Quantity':
276
+ record[field.fieldname] = 0;
277
+ break;
278
+ case 'JSON':
279
+ record[field.fieldname] = {};
280
+ break;
281
+ default:
282
+ record[field.fieldname] = null;
283
+ }
284
+ }
328
285
  }
329
- });
286
+ }
330
287
  return record;
331
288
  }
332
289
  /**
@@ -3,7 +3,7 @@
3
3
  * Validates Stonecrop schemas for integrity and consistency
4
4
  * @packageDocumentation
5
5
  */
6
- import type { SchemaTypes } from '@stonecrop/aform';
6
+ import type { DoctypeField } from '@stonecrop/schema';
7
7
  import type { LinkDeclaration } from '@stonecrop/schema';
8
8
  import type { List, Map as ImmutableMap } from 'immutable';
9
9
  import type { AnyStateNodeConfig } from 'xstate';
@@ -29,7 +29,7 @@ export declare class SchemaValidator {
29
29
  * @param links - Optional links object
30
30
  * @returns Validation result
31
31
  */
32
- validate(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>, links?: Record<string, LinkDeclaration>): ValidationResult;
32
+ validate(doctype: string, schema: List<DoctypeField> | DoctypeField[] | undefined, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>, links?: Record<string, LinkDeclaration>): ValidationResult;
33
33
  /**
34
34
  * Validates that required schema properties are present
35
35
  * @internal
@@ -74,5 +74,5 @@ export declare function createValidator(registry: Registry, options?: Partial<Va
74
74
  * @returns Validation result
75
75
  * @public
76
76
  */
77
- export declare function validateSchema(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, registry: Registry, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>): ValidationResult;
77
+ export declare function validateSchema(doctype: string, schema: List<DoctypeField> | DoctypeField[] | undefined, registry: Registry, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>): ValidationResult;
78
78
  //# sourceMappingURL=schema-validator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/schema-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAA;AAGhD,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,OAAO,KAAK,EAAmB,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEnG;;;GAGG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,OAAO,CAA4B;IAE3C;;;OAGG;gBACS,OAAO,GAAE,gBAAqB;IAW1C;;;;;;;;OAQG;IACH,QAAQ,CACP,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,GAAG,SAAS,EACrD,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAChE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GACrC,gBAAgB;IA+CnB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IA2ClC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAgE1B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA0GhC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoFxB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;CAwClC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,eAAe,CAKxG;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,GAAG,SAAS,EACrD,QAAQ,EAAE,QAAQ,EAClB,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAC9D,gBAAgB,CAGlB"}
1
+ {"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/schema-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAA;AAGhD,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,OAAO,KAAK,EAAmB,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEnG;;;GAGG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,OAAO,CAA4B;IAE3C;;;OAGG;gBACS,OAAO,GAAE,gBAAqB;IAW1C;;;;;;;;OAQG;IACH,QAAQ,CACP,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,YAAY,EAAE,GAAG,SAAS,EACvD,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAChE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GACrC,gBAAgB;IA+CnB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAoClC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAmD1B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAyGhC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoFxB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;CAwClC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,eAAe,CAKxG;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,YAAY,EAAE,GAAG,SAAS,EACvD,QAAQ,EAAE,QAAQ,EAClB,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAC9D,gBAAgB,CAGlB"}