@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.
- package/dist/src/doctype.d.ts +9 -11
- package/dist/src/doctype.d.ts.map +1 -1
- package/dist/src/doctype.js +6 -6
- package/dist/src/registry.d.ts +23 -43
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +124 -167
- package/dist/src/schema-validator.d.ts +3 -3
- package/dist/src/schema-validator.d.ts.map +1 -1
- package/dist/src/schema-validator.js +13 -29
- package/dist/src/stonecrop.d.ts +1 -1
- package/dist/src/stonecrop.d.ts.map +1 -1
- package/dist/src/stonecrop.js +0 -1
- package/dist/src/stores/operation-log.d.ts +1 -1
- package/dist/src/types/composable.d.ts +2 -2
- package/dist/src/types/composable.d.ts.map +1 -1
- package/dist/src/types/doctype.d.ts +6 -25
- package/dist/src/types/doctype.d.ts.map +1 -1
- package/dist/stonecrop.d.ts +41 -81
- package/dist/stonecrop.js +458 -508
- package/dist/stonecrop.js.map +1 -1
- package/package.json +4 -4
- package/src/composables/stonecrop.ts +2 -2
- package/src/doctype.ts +10 -12
- package/src/registry.ts +137 -217
- package/src/schema-validator.ts +20 -41
- package/src/stonecrop.ts +0 -1
- package/src/types/composable.ts +2 -2
- package/src/types/doctype.ts +6 -27
package/dist/src/doctype.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
104
|
-
*
|
|
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
|
|
104
|
+
* @returns Array of raw DoctypeField authoring definitions
|
|
107
105
|
*
|
|
108
106
|
* @example
|
|
109
107
|
* ```ts
|
|
110
|
-
* const
|
|
111
|
-
* //
|
|
112
|
-
*
|
|
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():
|
|
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,
|
|
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"}
|
package/dist/src/doctype.js
CHANGED
|
@@ -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
|
|
113
|
-
*
|
|
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
|
|
115
|
+
* @returns Array of raw DoctypeField authoring definitions
|
|
116
116
|
*
|
|
117
117
|
* @example
|
|
118
118
|
* ```ts
|
|
119
|
-
* const
|
|
120
|
-
* //
|
|
121
|
-
*
|
|
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
|
package/dist/src/registry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
68
|
+
* Resolve a Doctype's authoring schema into a rendered schema array suitable for AForm.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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
|
-
*
|
|
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
|
|
82
|
+
* @returns A resolved schema array ready for AForm
|
|
87
83
|
*
|
|
88
84
|
* @public
|
|
89
85
|
*/
|
|
90
|
-
resolveSchema(doctype: Doctype, visited?: Set<string>):
|
|
86
|
+
resolveSchema(doctype: Doctype, visited?: Set<string>): ResolvedField[];
|
|
91
87
|
/**
|
|
92
|
-
* Recursively resolve a
|
|
93
|
-
*
|
|
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
|
|
99
|
-
*
|
|
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
|
-
*
|
|
118
|
-
* initializes
|
|
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:
|
|
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,
|
|
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"}
|
package/dist/src/registry.js
CHANGED
|
@@ -88,245 +88,202 @@ export default class Registry {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Resolve
|
|
91
|
+
* Resolve a Doctype's authoring schema into a rendered schema array suitable for AForm.
|
|
92
92
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
130
|
+
return result;
|
|
197
131
|
}
|
|
198
132
|
/**
|
|
199
|
-
* Recursively resolve a
|
|
200
|
-
*
|
|
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 (
|
|
140
|
+
if (field.kind === 'field' && field.fieldtype === 'Link') {
|
|
207
141
|
const link = links.get(field.fieldname);
|
|
208
142
|
if (!link) {
|
|
209
|
-
|
|
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
|
-
|
|
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(
|
|
168
|
+
resolved.push(this.buildTableConfig(field, childSchema, link.component));
|
|
221
169
|
}
|
|
222
170
|
else {
|
|
223
|
-
|
|
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 (
|
|
232
|
-
|
|
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
|
-
|
|
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
|
|
242
|
-
*
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
252
|
-
|
|
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
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
290
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
record[field.fieldname] = [];
|
|
300
|
-
return;
|
|
249
|
+
else if (field.kind === 'link') {
|
|
250
|
+
record[field.fieldname] = this.initializeRecord(field.schema);
|
|
301
251
|
}
|
|
302
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
record[field.fieldname] =
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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 {
|
|
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<
|
|
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<
|
|
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,
|
|
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"}
|