@stonecrop/stonecrop 0.11.4 → 0.11.5
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/registry.js +64 -12
- package/dist/src/registry.d.ts +9 -2
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/stonecrop.d.ts +9 -2
- package/dist/stonecrop.js +225 -176
- package/dist/stonecrop.js.map +1 -1
- package/package.json +4 -4
- package/src/registry.ts +83 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/stonecrop",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
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.11.
|
|
37
|
+
"@stonecrop/schema": "0.11.5"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"pinia": "^3.0.4",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"vue-router": "^5.0.2",
|
|
61
61
|
"vite": "^7.3.1",
|
|
62
62
|
"vitest": "^4.0.18",
|
|
63
|
-
"@stonecrop/atable": "0.11.
|
|
63
|
+
"@stonecrop/atable": "0.11.5",
|
|
64
64
|
"stonecrop-rig": "0.7.0",
|
|
65
|
-
"@stonecrop/aform": "0.11.
|
|
65
|
+
"@stonecrop/aform": "0.11.5"
|
|
66
66
|
},
|
|
67
67
|
"description": "Schema-driven framework with XState workflows and HST state management",
|
|
68
68
|
"publishConfig": {
|
package/src/registry.ts
CHANGED
|
@@ -114,7 +114,7 @@ export default class Registry {
|
|
|
114
114
|
* For each link field:
|
|
115
115
|
* - Looks up the corresponding link declaration in `links` by fieldname
|
|
116
116
|
* - `cardinality: 'noneOrMany'` or `'atLeastOne'`: auto-derives `columns` from the target's schema,
|
|
117
|
-
* sets `component` to `link.component ?? 'ATable'`, `config: { view: 'list' }
|
|
117
|
+
* sets `component` to `link.component ?? 'ATable'`, `config: { view: 'list' }`.
|
|
118
118
|
* - `cardinality: 'one'` or `'atMostOne'`: embeds the target schema as the entry's
|
|
119
119
|
* `schema` property, sets `component` to `link.component ?? 'AForm'`.
|
|
120
120
|
*
|
|
@@ -175,24 +175,39 @@ export default class Registry {
|
|
|
175
175
|
|
|
176
176
|
const childSchema = this.resolveSchema(targetDoctype, seen)
|
|
177
177
|
|
|
178
|
+
// Extract properties consumed by resolution; preserve everything else
|
|
179
|
+
// TODO: options and cardinality are untyped runtime properties on link fields; add them to
|
|
180
|
+
// FormSchema (or a dedicated link field type) to remove this cast
|
|
181
|
+
const {
|
|
182
|
+
fieldtype: _ft,
|
|
183
|
+
options: _opt,
|
|
184
|
+
cardinality: _card,
|
|
185
|
+
...fieldRest
|
|
186
|
+
} = field as typeof field & { options?: unknown; cardinality?: unknown }
|
|
187
|
+
|
|
178
188
|
if (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne') {
|
|
179
189
|
// Many relationship — build table config
|
|
180
190
|
resolvedFields.push(
|
|
181
191
|
this.buildTableConfig(
|
|
182
|
-
{
|
|
192
|
+
{ ...fieldRest, label: fieldRest.label || field.fieldname },
|
|
183
193
|
childSchema,
|
|
184
194
|
link.component
|
|
185
195
|
)
|
|
186
196
|
)
|
|
187
197
|
} else {
|
|
188
198
|
// One relationship — embed form schema
|
|
199
|
+
// TODO: remove assertion once resolved link output has a dedicated type separate from input schema
|
|
189
200
|
resolvedFields.push({
|
|
190
|
-
|
|
191
|
-
label:
|
|
192
|
-
component: link.component || 'AForm',
|
|
201
|
+
...fieldRest,
|
|
202
|
+
label: fieldRest.label || field.fieldname,
|
|
203
|
+
component: link.component || fieldRest.component || 'AForm',
|
|
193
204
|
schema: childSchema,
|
|
194
|
-
})
|
|
205
|
+
} as SchemaTypes)
|
|
195
206
|
}
|
|
207
|
+
} else if ('schema' in field && Array.isArray(field.schema)) {
|
|
208
|
+
// Fieldset — recursively resolve nested fields
|
|
209
|
+
const resolvedChildren = this.resolveFields(field.schema, linksByFieldname, seen)
|
|
210
|
+
resolvedFields.push({ ...field, schema: resolvedChildren })
|
|
196
211
|
} else {
|
|
197
212
|
// Scalar field — copy as-is
|
|
198
213
|
resolvedFields.push({ ...field })
|
|
@@ -205,16 +220,73 @@ export default class Registry {
|
|
|
205
220
|
}
|
|
206
221
|
|
|
207
222
|
/**
|
|
208
|
-
*
|
|
223
|
+
* Recursively resolve a flat fields array using the provided link context.
|
|
224
|
+
* Used by resolveSchema to handle fieldset children.
|
|
225
|
+
* @internal
|
|
226
|
+
*/
|
|
227
|
+
private resolveFields(
|
|
228
|
+
fields: SchemaTypes[],
|
|
229
|
+
links: Map<string, LinkDeclaration>,
|
|
230
|
+
visited: Set<string>
|
|
231
|
+
): SchemaTypes[] {
|
|
232
|
+
const resolved: SchemaTypes[] = []
|
|
233
|
+
for (const field of fields) {
|
|
234
|
+
if ('fieldtype' in field && field.fieldtype === 'Link') {
|
|
235
|
+
const link = links.get(field.fieldname)
|
|
236
|
+
if (!link) {
|
|
237
|
+
resolved.push({ ...field })
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
const targetDoctype = this.registry[link.target]
|
|
241
|
+
if (!targetDoctype) {
|
|
242
|
+
resolved.push({ ...field })
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
const childSchema = this.resolveSchema(targetDoctype, new Set(visited))
|
|
246
|
+
const {
|
|
247
|
+
fieldtype: _ft,
|
|
248
|
+
options: _opt,
|
|
249
|
+
cardinality: _card,
|
|
250
|
+
...fieldRest
|
|
251
|
+
} = field as typeof field & { options?: unknown; cardinality?: unknown }
|
|
252
|
+
if (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne') {
|
|
253
|
+
resolved.push(
|
|
254
|
+
this.buildTableConfig(
|
|
255
|
+
{ ...fieldRest, label: fieldRest.label || field.fieldname },
|
|
256
|
+
childSchema,
|
|
257
|
+
link.component
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
} else {
|
|
261
|
+
// TODO: remove assertion once resolved link output has a dedicated type separate from input schema
|
|
262
|
+
resolved.push({
|
|
263
|
+
...fieldRest,
|
|
264
|
+
label: fieldRest.label || field.fieldname,
|
|
265
|
+
component: link.component || fieldRest.component || 'AForm',
|
|
266
|
+
schema: childSchema,
|
|
267
|
+
} as SchemaTypes)
|
|
268
|
+
}
|
|
269
|
+
} else if ('schema' in field && Array.isArray(field.schema)) {
|
|
270
|
+
resolved.push({ ...field, schema: this.resolveFields(field.schema, links, visited) })
|
|
271
|
+
} else {
|
|
272
|
+
resolved.push({ ...field })
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return resolved
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Build an ATable configuration from a field and child schema.
|
|
280
|
+
* Data-model properties from the source field are preserved via the spread `field` argument.
|
|
209
281
|
* @internal
|
|
210
282
|
*/
|
|
211
283
|
private buildTableConfig(field: Record<string, any>, childSchema: SchemaTypes[], component?: string): TableSchema {
|
|
212
284
|
const resolved: TableSchema = {
|
|
285
|
+
...field,
|
|
213
286
|
fieldname: field.fieldname,
|
|
214
287
|
component: component || field.component || 'ATable',
|
|
215
288
|
columns: field.columns,
|
|
216
289
|
config: field.config,
|
|
217
|
-
rows: field.rows,
|
|
218
290
|
}
|
|
219
291
|
|
|
220
292
|
if (!resolved.columns) {
|
|
@@ -234,10 +306,6 @@ export default class Registry {
|
|
|
234
306
|
resolved.config = { view: 'list' }
|
|
235
307
|
}
|
|
236
308
|
|
|
237
|
-
if (!resolved.rows) {
|
|
238
|
-
resolved.rows = []
|
|
239
|
-
}
|
|
240
|
-
|
|
241
309
|
return resolved
|
|
242
310
|
}
|
|
243
311
|
|
|
@@ -282,8 +350,9 @@ export default class Registry {
|
|
|
282
350
|
return
|
|
283
351
|
}
|
|
284
352
|
|
|
285
|
-
// Resolved 1:many table entry —
|
|
286
|
-
|
|
353
|
+
// Resolved 1:many table entry — structural detection via columns
|
|
354
|
+
// TODO: replace 'columns' presence check with a type discriminant on SchemaTypes once one exists
|
|
355
|
+
if ('columns' in field) {
|
|
287
356
|
record[field.fieldname] = []
|
|
288
357
|
return
|
|
289
358
|
}
|