@stonecrop/stonecrop 0.13.7 → 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/stonecrop",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"pinia-shared-state": "^1.0.1",
|
|
35
35
|
"pinia-xstate": "^3.0.0",
|
|
36
36
|
"xstate": "^5.25.0",
|
|
37
|
-
"@stonecrop/schema": "0.13.
|
|
37
|
+
"@stonecrop/schema": "0.13.9"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"pinia": "^3.0.4",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"vitest": "^4.1.5",
|
|
57
57
|
"vue": "^3.5.33",
|
|
58
58
|
"vue-router": "^5.0.6",
|
|
59
|
-
"@stonecrop/aform": "0.13.
|
|
60
|
-
"@stonecrop/atable": "0.13.
|
|
59
|
+
"@stonecrop/aform": "0.13.9",
|
|
60
|
+
"@stonecrop/atable": "0.13.9",
|
|
61
61
|
"stonecrop-rig": "0.7.0"
|
|
62
62
|
},
|
|
63
63
|
"description": "Schema-driven framework with XState workflows and HST state management",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ResolvedField } from '@stonecrop/aform'
|
|
2
2
|
import { storeToRefs } from 'pinia'
|
|
3
3
|
import { inject, onMounted, Ref, ref, watch, provide, computed } from 'vue'
|
|
4
4
|
|
|
@@ -61,7 +61,7 @@ export function useStonecrop(options?: {
|
|
|
61
61
|
const routerRecordId = ref<string | undefined>()
|
|
62
62
|
|
|
63
63
|
// Resolved schema with nested Doctype fields expanded
|
|
64
|
-
const resolvedSchema = ref<
|
|
64
|
+
const resolvedSchema = ref<ResolvedField[]>([])
|
|
65
65
|
|
|
66
66
|
// Loading state for lazy-loaded doctypes
|
|
67
67
|
const isLoading = ref(false)
|
package/src/doctype.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { LinkDeclaration, WorkflowMeta } from '@stonecrop/schema'
|
|
1
|
+
import type { DoctypeField, LinkDeclaration, WorkflowMeta } from '@stonecrop/schema'
|
|
3
2
|
import { List, Map } from 'immutable'
|
|
4
3
|
import { Component } from 'vue'
|
|
5
4
|
|
|
6
|
-
import type { ImmutableDoctype } from './types'
|
|
7
|
-
import type { DoctypeConfig } from './types/doctype'
|
|
5
|
+
import type { DoctypeConfig, ImmutableDoctype } from './types/doctype'
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Doctype runtime class with Immutable.js collections for HST change tracking.
|
|
@@ -126,28 +124,28 @@ export default class Doctype {
|
|
|
126
124
|
* @public
|
|
127
125
|
*/
|
|
128
126
|
static fromObject(config: DoctypeConfig): Doctype {
|
|
129
|
-
const schema = config.fields ? List(config.fields) : List<
|
|
127
|
+
const schema = config.fields ? List(config.fields) : List<DoctypeField>()
|
|
130
128
|
const actions = config.actions ? Map(config.actions) : Map<string, string[]>()
|
|
131
129
|
|
|
132
130
|
return new Doctype(config.name, schema, config.workflow, actions, undefined, config.links)
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
/**
|
|
136
|
-
* Returns the schema as a plain array
|
|
137
|
-
*
|
|
134
|
+
* Returns the raw authoring schema as a plain array.
|
|
135
|
+
* For the resolved schema suitable for AForm, use `registry.resolveSchema(doctype)`.
|
|
138
136
|
*
|
|
139
|
-
* @returns Array of
|
|
137
|
+
* @returns Array of raw DoctypeField authoring definitions
|
|
140
138
|
*
|
|
141
139
|
* @example
|
|
142
140
|
* ```ts
|
|
143
|
-
* const
|
|
144
|
-
* //
|
|
145
|
-
*
|
|
141
|
+
* const fields = doctype.getSchemaArray()
|
|
142
|
+
* // Pass to resolveSchema for AForm-ready output:
|
|
143
|
+
* const resolved = registry.resolveSchema(doctype)
|
|
146
144
|
* ```
|
|
147
145
|
*
|
|
148
146
|
* @public
|
|
149
147
|
*/
|
|
150
|
-
getSchemaArray():
|
|
148
|
+
getSchemaArray(): DoctypeField[] {
|
|
151
149
|
if (!this.schema) return []
|
|
152
150
|
return this.schema.toArray()
|
|
153
151
|
}
|
package/src/registry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { ResolvedField, ResolvedLink, ResolvedScalar, ResolvedTable, ResolvedFieldset } from '@stonecrop/aform'
|
|
2
|
+
import type { ColumnSchema, DoctypeField, LinkDeclaration, TableViewConfig, ValueField } from '@stonecrop/schema'
|
|
3
3
|
import { Router } from 'vue-router'
|
|
4
4
|
|
|
5
5
|
import Doctype from './doctype'
|
|
@@ -106,67 +106,70 @@ export default class Registry {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
* Resolve
|
|
109
|
+
* Resolve a Doctype's authoring schema into a rendered schema array suitable for AForm.
|
|
110
110
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
* Transforms `DoctypeField[]` (authoring space) → `ResolvedField[]` (rendering space):
|
|
112
|
+
* - `kind: 'field'` (not Link) → `ResolvedScalar`
|
|
113
|
+
* - `kind: 'field'` (Link, no declaration) → `ResolvedScalar` with `component: 'AFormLink'`
|
|
114
|
+
* - `kind: 'field'` (Link, `noneOrMany`/`atLeastOne`) → `ResolvedTable`
|
|
115
|
+
* - `kind: 'field'` (Link, `one`/`atMostOne`) → `ResolvedLink`
|
|
116
|
+
* - `kind: 'fieldset'` → `ResolvedFieldset` (children resolved recursively)
|
|
117
|
+
* - `kind: 'table'` → `ResolvedTable` (columns as `ColumnSchema[]`)
|
|
114
118
|
*
|
|
115
|
-
*
|
|
116
|
-
* - Looks up the corresponding link declaration in `links` by fieldname
|
|
117
|
-
* - `cardinality: 'noneOrMany'` or `'atLeastOne'`: auto-derives `columns` from the target's schema,
|
|
118
|
-
* sets `component` to `link.component ?? 'ATable'`, `config: { view: 'list' }`.
|
|
119
|
-
* - `cardinality: 'one'` or `'atMostOne'`: embeds the target schema as the entry's
|
|
120
|
-
* `schema` property, sets `component` to `link.component ?? 'AForm'`.
|
|
121
|
-
*
|
|
122
|
-
* Recurses for deeply nested doctypes. Circular references are protected against.
|
|
123
|
-
* Returns a new array — does not mutate the original.
|
|
119
|
+
* Circular references are protected against via the `visited` set.
|
|
124
120
|
*
|
|
125
121
|
* @param doctype - The doctype to resolve
|
|
126
122
|
* @param visited - Internal — set of already-visited doctype slugs for cycle detection
|
|
127
|
-
* @returns A
|
|
123
|
+
* @returns A resolved schema array ready for AForm
|
|
128
124
|
*
|
|
129
125
|
* @public
|
|
130
126
|
*/
|
|
131
|
-
resolveSchema(doctype: Doctype, visited?: Set<string>):
|
|
127
|
+
resolveSchema(doctype: Doctype, visited?: Set<string>): ResolvedField[] {
|
|
132
128
|
const seen = visited ?? new Set<string>()
|
|
133
129
|
const slug = doctype.slug
|
|
134
130
|
|
|
135
|
-
// Prevent circular resolution
|
|
131
|
+
// Prevent circular resolution — return all ValueField entries as scalars (link not expanded)
|
|
136
132
|
if (seen.has(slug)) {
|
|
137
|
-
|
|
133
|
+
const fallback: DoctypeField[] = doctype.schema ? doctype.schema.toArray() : []
|
|
134
|
+
return fallback
|
|
135
|
+
.filter((f): f is ValueField => f.kind === 'field')
|
|
136
|
+
.map(({ cardinality: _c, ...rest }): ResolvedScalar => rest)
|
|
138
137
|
}
|
|
139
138
|
seen.add(slug)
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
const schemaArray: SchemaTypes[] = doctype.schema
|
|
143
|
-
? Array.isArray(doctype.schema)
|
|
144
|
-
? doctype.schema
|
|
145
|
-
: Array.from(doctype.schema)
|
|
146
|
-
: []
|
|
140
|
+
const schemaArray: DoctypeField[] = doctype.schema ? doctype.schema.toArray() : []
|
|
147
141
|
|
|
148
|
-
//
|
|
149
|
-
// Use the link's fieldname property if set, otherwise use the key
|
|
142
|
+
// Map link declarations by fieldname (link.fieldname ?? key)
|
|
150
143
|
const linksByFieldname = new Map<string, LinkDeclaration>()
|
|
151
144
|
if (doctype.links) {
|
|
152
145
|
for (const [key, link] of Object.entries(doctype.links)) {
|
|
153
|
-
|
|
154
|
-
linksByFieldname.set(linkFieldname, link)
|
|
146
|
+
linksByFieldname.set(link.fieldname ?? key, link)
|
|
155
147
|
}
|
|
156
148
|
}
|
|
157
149
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
150
|
+
const result = this.resolveFields(schemaArray, linksByFieldname, seen)
|
|
151
|
+
seen.delete(slug)
|
|
152
|
+
return result
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Recursively resolve a `DoctypeField[]` using the provided link context.
|
|
157
|
+
* Called by `resolveSchema` and recursively for fieldset children.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
private resolveFields(
|
|
161
|
+
fields: DoctypeField[],
|
|
162
|
+
links: Map<string, LinkDeclaration>,
|
|
163
|
+
visited: Set<string>
|
|
164
|
+
): ResolvedField[] {
|
|
165
|
+
const resolved: ResolvedField[] = []
|
|
169
166
|
|
|
167
|
+
for (const field of fields) {
|
|
168
|
+
if (field.kind === 'field' && field.fieldtype === 'Link') {
|
|
169
|
+
const link = links.get(field.fieldname)
|
|
170
|
+
if (!link) {
|
|
171
|
+
// Unresolved link — warn and produce a scalar with component: 'AFormLink'
|
|
172
|
+
const linkDoctype = typeof field.options === 'string' ? field.options : undefined
|
|
170
173
|
if (linkDoctype === undefined) {
|
|
171
174
|
console.warn(
|
|
172
175
|
`[Stonecrop] Link field "${field.fieldname}" has no \`options\` or corresponding \`links\` declaration. ` +
|
|
@@ -174,227 +177,144 @@ export default class Registry {
|
|
|
174
177
|
`Add \`"options": "<doctype-slug>"\` to the field definition.`
|
|
175
178
|
)
|
|
176
179
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
component?: string
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
resolvedFields.push({
|
|
185
|
-
...fieldRest,
|
|
186
|
-
component: fieldRest.component || 'AFormLink',
|
|
180
|
+
const { cardinality: _c, ...rest } = field
|
|
181
|
+
resolved.push({
|
|
182
|
+
...rest,
|
|
183
|
+
component: rest.component || 'AFormLink',
|
|
187
184
|
...(linkDoctype !== undefined ? { doctype: linkDoctype } : {}),
|
|
188
185
|
})
|
|
189
|
-
|
|
190
186
|
continue
|
|
191
187
|
}
|
|
192
188
|
|
|
193
189
|
const targetDoctype = this.registry[link.target]
|
|
194
190
|
if (!targetDoctype) {
|
|
195
|
-
// Target not
|
|
196
|
-
|
|
191
|
+
// Target not registered — copy as scalar
|
|
192
|
+
const { cardinality: _c, ...rest } = field
|
|
193
|
+
resolved.push({ ...rest })
|
|
197
194
|
continue
|
|
198
195
|
}
|
|
199
196
|
|
|
200
|
-
const childSchema = this.resolveSchema(targetDoctype,
|
|
201
|
-
|
|
202
|
-
// Extract properties consumed by resolution; preserve everything else
|
|
203
|
-
// TODO: options and cardinality are untyped runtime properties on link fields; add them to
|
|
204
|
-
// FormSchema (or a dedicated link field type) to remove this cast
|
|
205
|
-
const {
|
|
206
|
-
fieldtype: _ft,
|
|
207
|
-
options: _opt,
|
|
208
|
-
cardinality: _card,
|
|
209
|
-
...fieldRest
|
|
210
|
-
} = field as typeof field & { options?: unknown; cardinality?: unknown }
|
|
197
|
+
const childSchema = this.resolveSchema(targetDoctype, new Set(visited))
|
|
198
|
+
const { fieldtype: _ft, options: _opt, cardinality: _card, kind: _kind, ...fieldRest } = field
|
|
211
199
|
|
|
212
200
|
if (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne') {
|
|
213
|
-
|
|
214
|
-
resolvedFields.push(
|
|
215
|
-
this.buildTableConfig(
|
|
216
|
-
{ ...fieldRest, label: fieldRest.label || field.fieldname },
|
|
217
|
-
childSchema,
|
|
218
|
-
link.component
|
|
219
|
-
)
|
|
220
|
-
)
|
|
201
|
+
resolved.push(this.buildTableConfig(field, childSchema, link.component))
|
|
221
202
|
} else {
|
|
222
|
-
|
|
223
|
-
resolvedFields.push({
|
|
203
|
+
const linkEntry: ResolvedLink = {
|
|
224
204
|
...fieldRest,
|
|
205
|
+
kind: 'link',
|
|
225
206
|
label: fieldRest.label || field.fieldname,
|
|
226
207
|
component: link.component || fieldRest.component || 'AForm',
|
|
227
208
|
schema: childSchema,
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
} else if ('schema' in field && Array.isArray(field.schema)) {
|
|
231
|
-
// Fieldset — recursively resolve nested fields
|
|
232
|
-
const resolvedChildren = this.resolveFields(field.schema, linksByFieldname, seen)
|
|
233
|
-
resolvedFields.push({ ...field, schema: resolvedChildren })
|
|
234
|
-
} else {
|
|
235
|
-
// Scalar field — copy as-is
|
|
236
|
-
resolvedFields.push({ ...field })
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
seen.delete(slug)
|
|
241
|
-
|
|
242
|
-
return resolvedFields
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Recursively resolve a flat fields array using the provided link context.
|
|
247
|
-
* Used by resolveSchema to handle fieldset children.
|
|
248
|
-
* @internal
|
|
249
|
-
*/
|
|
250
|
-
private resolveFields(
|
|
251
|
-
fields: SchemaTypes[],
|
|
252
|
-
links: Map<string, LinkDeclaration>,
|
|
253
|
-
visited: Set<string>
|
|
254
|
-
): SchemaTypes[] {
|
|
255
|
-
const resolved: SchemaTypes[] = []
|
|
256
|
-
for (const field of fields) {
|
|
257
|
-
if ('fieldtype' in field && field.fieldtype === 'Link') {
|
|
258
|
-
const link = links.get(field.fieldname)
|
|
259
|
-
if (!link) {
|
|
260
|
-
resolved.push({ ...field })
|
|
261
|
-
continue
|
|
209
|
+
}
|
|
210
|
+
resolved.push(linkEntry)
|
|
262
211
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
212
|
+
} else if (field.kind === 'fieldset') {
|
|
213
|
+
const resolvedChildren = this.resolveFields(field.schema, links, visited)
|
|
214
|
+
const { schema: _s, ...fieldRest } = field
|
|
215
|
+
const fieldsetEntry: ResolvedFieldset = {
|
|
216
|
+
...fieldRest,
|
|
217
|
+
schema: resolvedChildren,
|
|
267
218
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
...fieldRest
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
{ ...fieldRest, label: fieldRest.label || field.fieldname },
|
|
279
|
-
childSchema,
|
|
280
|
-
link.component
|
|
281
|
-
)
|
|
282
|
-
)
|
|
283
|
-
} else {
|
|
284
|
-
resolved.push({
|
|
285
|
-
...fieldRest,
|
|
286
|
-
label: fieldRest.label || field.fieldname,
|
|
287
|
-
component: link.component || fieldRest.component || 'AForm',
|
|
288
|
-
schema: childSchema,
|
|
289
|
-
})
|
|
219
|
+
resolved.push(fieldsetEntry)
|
|
220
|
+
} else if (field.kind === 'table') {
|
|
221
|
+
// Inline table — columns become ColumnSchema[], add default config
|
|
222
|
+
const { columns, config, ...fieldRest } = field
|
|
223
|
+
const tableEntry: ResolvedTable = {
|
|
224
|
+
...fieldRest,
|
|
225
|
+
kind: 'table',
|
|
226
|
+
component: fieldRest.component || 'ATable',
|
|
227
|
+
schema: columns,
|
|
228
|
+
config: config ?? { view: 'list' },
|
|
290
229
|
}
|
|
291
|
-
|
|
292
|
-
resolved.push({ ...field, schema: this.resolveFields(field.schema, links, visited) })
|
|
230
|
+
resolved.push(tableEntry)
|
|
293
231
|
} else {
|
|
294
|
-
|
|
232
|
+
// Scalar field (kind: 'field', not a Link) — strip cardinality
|
|
233
|
+
const { cardinality: _c, ...rest } = field
|
|
234
|
+
resolved.push({ ...rest })
|
|
295
235
|
}
|
|
296
236
|
}
|
|
237
|
+
|
|
297
238
|
return resolved
|
|
298
239
|
}
|
|
299
240
|
|
|
300
241
|
/**
|
|
301
|
-
* Build
|
|
302
|
-
*
|
|
242
|
+
* Build a `ResolvedTable` from a resolved Link field with many cardinality.
|
|
243
|
+
* Extracts scalar column definitions from the child schema.
|
|
303
244
|
* @internal
|
|
304
245
|
*/
|
|
305
|
-
private buildTableConfig(field:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
kind: 'table',
|
|
311
|
-
schema: childSchema,
|
|
312
|
-
config: field.config,
|
|
313
|
-
}
|
|
246
|
+
private buildTableConfig(field: ValueField, childSchema: ResolvedField[], component?: string): ResolvedTable {
|
|
247
|
+
// Only scalar fields become columns; strip kind and cardinality (runtime column spec)
|
|
248
|
+
const columns: ColumnSchema[] = childSchema
|
|
249
|
+
.filter((f): f is ResolvedScalar => f.kind === 'field')
|
|
250
|
+
.map(({ kind: _k, ...col }) => col)
|
|
314
251
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
252
|
+
const config: TableViewConfig = (field as ValueField & { config?: TableViewConfig }).config ?? { view: 'list' }
|
|
253
|
+
const { fieldtype: _ft, options: _opt, cardinality: _card, ...fieldRest } = field
|
|
318
254
|
|
|
319
|
-
return
|
|
255
|
+
return {
|
|
256
|
+
...fieldRest,
|
|
257
|
+
kind: 'table',
|
|
258
|
+
label: fieldRest.label || field.fieldname,
|
|
259
|
+
component: component || fieldRest.component || 'ATable',
|
|
260
|
+
schema: columns,
|
|
261
|
+
config,
|
|
262
|
+
}
|
|
320
263
|
}
|
|
321
264
|
|
|
322
265
|
/**
|
|
323
|
-
* Initialize a new record with default values based on a schema.
|
|
324
|
-
*
|
|
325
|
-
* @remarks
|
|
326
|
-
* Creates a plain object with keys from the schema's fieldnames and default values
|
|
327
|
-
* derived from each field's `fieldtype`:
|
|
328
|
-
* - Data, Text → `''`
|
|
329
|
-
* - Check → `false`
|
|
330
|
-
* - Int, Float, Decimal, Currency, Quantity → `0`
|
|
331
|
-
* - JSON → `{}`
|
|
332
|
-
* - Doctype with `cardinality: 'noneOrMany'` or `'atLeastOne'` → `[]`
|
|
333
|
-
* - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
|
|
334
|
-
* - All others → `null`
|
|
266
|
+
* Initialize a new record with default values based on a resolved schema.
|
|
267
|
+
* Narrows by `kind` discriminator for precise branch selection.
|
|
335
268
|
*
|
|
336
|
-
*
|
|
337
|
-
* initializes
|
|
269
|
+
* - `kind: 'table'` or `kind: 'link'` → `[]` or `{}`
|
|
270
|
+
* - `kind: 'fieldset'` → recursively initializes children as `{}`
|
|
271
|
+
* - `kind: 'field'` → derives default from `fieldtype`; falls back to `null`
|
|
338
272
|
*
|
|
339
|
-
* @param schema - The schema array to derive defaults from
|
|
273
|
+
* @param schema - The resolved schema array to derive defaults from
|
|
340
274
|
* @returns A plain object with default values for each field
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* ```ts
|
|
344
|
-
* const defaults = registry.initializeRecord(addressSchema)
|
|
345
|
-
* // { street: '', city: '', state: '', zip_code: '' }
|
|
346
|
-
* ```
|
|
347
|
-
*
|
|
348
275
|
* @public
|
|
349
276
|
*/
|
|
350
|
-
initializeRecord(schema:
|
|
277
|
+
initializeRecord(schema: ResolvedField[]): Record<string, any> {
|
|
351
278
|
const record: Record<string, any> = {}
|
|
352
279
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const cardinality = 'cardinality' in field ? field.cardinality : undefined
|
|
356
|
-
|
|
357
|
-
// 1:many — cardinality signals an array
|
|
358
|
-
if (cardinality === 'noneOrMany' || cardinality === 'atLeastOne') {
|
|
359
|
-
record[field.fieldname] = []
|
|
360
|
-
return
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Resolved 1:many table entry — kind discriminant set by buildTableConfig
|
|
364
|
-
if ('kind' in field && field.kind === 'table') {
|
|
280
|
+
for (const field of schema) {
|
|
281
|
+
if (field.kind === 'table') {
|
|
365
282
|
record[field.fieldname] = []
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Resolved 1:1 link entry — has schema property (e.g., FieldsetSchema with nested schema)
|
|
370
|
-
if ('schema' in field && Array.isArray(field.schema)) {
|
|
283
|
+
} else if (field.kind === 'link') {
|
|
371
284
|
record[field.fieldname] = this.initializeRecord(field.schema)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
285
|
+
} else if (field.kind === 'fieldset') {
|
|
286
|
+
record[field.fieldname] = this.initializeRecord(field.schema)
|
|
287
|
+
} else {
|
|
288
|
+
// kind: 'field' — derive from fieldtype
|
|
289
|
+
const fieldDefault = field.default
|
|
290
|
+
if (fieldDefault !== undefined) {
|
|
291
|
+
record[field.fieldname] = fieldDefault
|
|
292
|
+
} else {
|
|
293
|
+
switch (field.fieldtype) {
|
|
294
|
+
case 'Data':
|
|
295
|
+
case 'Text':
|
|
296
|
+
case 'Code':
|
|
297
|
+
record[field.fieldname] = ''
|
|
298
|
+
break
|
|
299
|
+
case 'Check':
|
|
300
|
+
record[field.fieldname] = false
|
|
301
|
+
break
|
|
302
|
+
case 'Int':
|
|
303
|
+
case 'Float':
|
|
304
|
+
case 'Decimal':
|
|
305
|
+
case 'Currency':
|
|
306
|
+
case 'Quantity':
|
|
307
|
+
record[field.fieldname] = 0
|
|
308
|
+
break
|
|
309
|
+
case 'JSON':
|
|
310
|
+
record[field.fieldname] = {}
|
|
311
|
+
break
|
|
312
|
+
default:
|
|
313
|
+
record[field.fieldname] = null
|
|
314
|
+
}
|
|
315
|
+
}
|
|
396
316
|
}
|
|
397
|
-
}
|
|
317
|
+
}
|
|
398
318
|
|
|
399
319
|
return record
|
|
400
320
|
}
|