@stonecrop/stonecrop 0.11.4 → 0.11.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/stonecrop",
3
- "version": "0.11.4",
3
+ "version": "0.11.6",
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.4"
37
+ "@stonecrop/schema": "0.11.6"
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.4",
63
+ "@stonecrop/aform": "0.11.6",
64
64
  "stonecrop-rig": "0.7.0",
65
- "@stonecrop/aform": "0.11.4"
65
+ "@stonecrop/atable": "0.11.6"
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' }`, `rows: []`.
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
- { fieldname: field.fieldname, label: field.label || field.fieldname },
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
- fieldname: field.fieldname,
191
- label: field.label || field.fieldname,
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
- * Build an ATable configuration from a field and child schema
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 — has rows property
286
- if ('rows' in field) {
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
  }