@stonecrop/stonecrop 0.10.11 → 0.10.12
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/composables/stonecrop.js +54 -13
- package/dist/field-triggers.js +5 -6
- package/dist/registry.js +62 -61
- package/dist/src/composables/stonecrop.d.ts +3 -3
- package/dist/src/composables/stonecrop.d.ts.map +1 -1
- package/dist/src/field-triggers.d.ts.map +1 -1
- package/dist/src/registry.d.ts +13 -10
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/stonecrop.d.ts.map +1 -1
- package/dist/stonecrop.css +1 -0
- package/dist/stonecrop.d.ts +14 -11
- package/dist/stonecrop.js +1470 -1342
- package/dist/stonecrop.js.map +1 -1
- package/package.json +5 -5
- package/src/composables/stonecrop.ts +75 -19
- package/src/field-triggers.ts +5 -6
- package/src/registry.ts +61 -66
- package/src/stonecrop.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/stonecrop",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.12",
|
|
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.10.
|
|
37
|
+
"@stonecrop/schema": "0.10.12"
|
|
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/
|
|
64
|
-
"stonecrop
|
|
65
|
-
"
|
|
63
|
+
"@stonecrop/aform": "0.10.12",
|
|
64
|
+
"@stonecrop/atable": "0.10.12",
|
|
65
|
+
"stonecrop-rig": "0.7.0"
|
|
66
66
|
},
|
|
67
67
|
"description": "Schema-driven framework with XState workflows and HST state management",
|
|
68
68
|
"publishConfig": {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type DoctypeManySchema,
|
|
3
|
+
type DoctypeOneSchema,
|
|
4
|
+
type DoctypeSchema,
|
|
5
|
+
type SchemaTypes,
|
|
6
|
+
isDoctypeMany,
|
|
7
|
+
} from '@stonecrop/aform'
|
|
8
|
+
import { storeToRefs } from 'pinia'
|
|
9
|
+
import { inject, onMounted, Ref, ref, watch, provide, computed, type ComputedRef } from 'vue'
|
|
2
10
|
|
|
3
11
|
import Registry from '../registry'
|
|
4
12
|
import { Stonecrop } from '../stonecrop'
|
|
5
13
|
import Doctype from '../doctype'
|
|
6
14
|
import type { HSTNode } from '../stores/hst'
|
|
7
|
-
import { RouteContext } from '../types/registry'
|
|
8
|
-
import { storeToRefs } from 'pinia'
|
|
15
|
+
import type { RouteContext } from '../types/registry'
|
|
9
16
|
import type { HSTOperation, OperationLogConfig, OperationLogSnapshot } from '../types/operation-log'
|
|
10
|
-
import { SchemaTypes, DoctypeSchema } from '@stonecrop/aform'
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* Operation Log API - nested object containing all operation log functionality
|
|
@@ -66,7 +72,7 @@ export type HSTStonecropReturn = BaseStonecropReturn & {
|
|
|
66
72
|
formData: Ref<Record<string, any>>
|
|
67
73
|
resolvedSchema: Ref<SchemaTypes[]>
|
|
68
74
|
loadNestedData: (parentPath: string, childDoctype: Doctype, recordId?: string) => Record<string, any>
|
|
69
|
-
|
|
75
|
+
collectRecordPayload: (doctype: Doctype, recordId: string) => Record<string, any>
|
|
70
76
|
createNestedContext: (
|
|
71
77
|
basePath: string,
|
|
72
78
|
childDoctype: Doctype
|
|
@@ -501,12 +507,12 @@ export function useStonecrop(options?: {
|
|
|
501
507
|
}
|
|
502
508
|
|
|
503
509
|
/**
|
|
504
|
-
*
|
|
510
|
+
* Collect a record payload with all nested doctype fields from HST
|
|
505
511
|
* @param doctype - The doctype metadata
|
|
506
|
-
* @param recordId - The record ID to
|
|
507
|
-
* @returns The complete
|
|
512
|
+
* @param recordId - The record ID to collect
|
|
513
|
+
* @returns The complete record payload ready for API submission
|
|
508
514
|
*/
|
|
509
|
-
const
|
|
515
|
+
const collectRecordPayload = (doctype: Doctype, recordId: string): Record<string, any> => {
|
|
510
516
|
if (!hstStore.value || !stonecrop.value) {
|
|
511
517
|
throw new Error('HST store not initialized')
|
|
512
518
|
}
|
|
@@ -524,18 +530,40 @@ export function useStonecrop(options?: {
|
|
|
524
530
|
: Array.from(doctype.schema)
|
|
525
531
|
: []
|
|
526
532
|
const resolved = registry ? registry.resolveSchema(schemaArray) : schemaArray
|
|
533
|
+
|
|
534
|
+
// 1:1 nested Doctype fields (cardinality: 'one' or undefined)
|
|
527
535
|
const doctypeFields = resolved.filter(
|
|
528
|
-
field =>
|
|
536
|
+
field =>
|
|
537
|
+
'fieldtype' in field &&
|
|
538
|
+
field.fieldtype === 'Doctype' &&
|
|
539
|
+
!isDoctypeMany(field as DoctypeSchema) &&
|
|
540
|
+
'schema' in field &&
|
|
541
|
+
Array.isArray(field.schema)
|
|
529
542
|
)
|
|
530
543
|
|
|
531
544
|
// Recursively collect nested data from HST using resolved schemas
|
|
532
545
|
for (const field of doctypeFields) {
|
|
533
|
-
const doctypeField = field as
|
|
546
|
+
const doctypeField = field as DoctypeOneSchema
|
|
534
547
|
const fieldPath = `${recordPath}.${doctypeField.fieldname}`
|
|
535
548
|
const nestedData = collectNestedData(doctypeField.schema!, fieldPath, hstStore.value)
|
|
536
549
|
payload[doctypeField.fieldname] = nestedData
|
|
537
550
|
}
|
|
538
551
|
|
|
552
|
+
// 1:many child tables (cardinality: 'many')
|
|
553
|
+
const doctypeManyFields = resolved.filter(
|
|
554
|
+
field => 'fieldtype' in field && field.fieldtype === 'Doctype' && isDoctypeMany(field as DoctypeSchema)
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
// Read array data from HST for cardinality: 'many' fields
|
|
558
|
+
for (const field of doctypeManyFields) {
|
|
559
|
+
const doctypeField = field as DoctypeManySchema
|
|
560
|
+
const fieldPath = `${recordPath}.${doctypeField.fieldname}`
|
|
561
|
+
const arrayData = hstStore.value.get(fieldPath)
|
|
562
|
+
if (Array.isArray(arrayData)) {
|
|
563
|
+
payload[doctypeField.fieldname] = arrayData
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
539
567
|
return payload
|
|
540
568
|
}
|
|
541
569
|
|
|
@@ -599,7 +627,7 @@ export function useStonecrop(options?: {
|
|
|
599
627
|
formData,
|
|
600
628
|
resolvedSchema,
|
|
601
629
|
loadNestedData,
|
|
602
|
-
|
|
630
|
+
collectRecordPayload,
|
|
603
631
|
createNestedContext,
|
|
604
632
|
isLoading,
|
|
605
633
|
error,
|
|
@@ -616,7 +644,7 @@ export function useStonecrop(options?: {
|
|
|
616
644
|
formData,
|
|
617
645
|
resolvedSchema,
|
|
618
646
|
loadNestedData,
|
|
619
|
-
|
|
647
|
+
collectRecordPayload,
|
|
620
648
|
createNestedContext,
|
|
621
649
|
isLoading,
|
|
622
650
|
error,
|
|
@@ -656,12 +684,21 @@ function initializeNewRecord(doctype: Doctype): Record<string, any> {
|
|
|
656
684
|
case 'Float':
|
|
657
685
|
initialData[field.fieldname] = 0
|
|
658
686
|
break
|
|
659
|
-
case 'Table':
|
|
660
|
-
initialData[field.fieldname] = []
|
|
661
|
-
break
|
|
662
687
|
case 'JSON':
|
|
663
688
|
initialData[field.fieldname] = {}
|
|
664
689
|
break
|
|
690
|
+
case 'Doctype': {
|
|
691
|
+
// Check cardinality to determine initial value
|
|
692
|
+
const cardinality = 'cardinality' in field ? field.cardinality : undefined
|
|
693
|
+
if (cardinality === 'many') {
|
|
694
|
+
// 1:many child table - initialize as empty array
|
|
695
|
+
initialData[field.fieldname] = []
|
|
696
|
+
} else {
|
|
697
|
+
// 1:1 nested form - initialize as empty object
|
|
698
|
+
initialData[field.fieldname] = {}
|
|
699
|
+
}
|
|
700
|
+
break
|
|
701
|
+
}
|
|
665
702
|
default:
|
|
666
703
|
initialData[field.fieldname] = null
|
|
667
704
|
}
|
|
@@ -728,18 +765,37 @@ function collectNestedData(resolvedSchema: SchemaTypes[], basePath: string, hstS
|
|
|
728
765
|
const data = hstStore.get(basePath) || {}
|
|
729
766
|
const payload: Record<string, any> = { ...data }
|
|
730
767
|
|
|
731
|
-
// Find Doctype fields that have resolved child schemas
|
|
768
|
+
// Find Doctype fields that have resolved child schemas (1:1 only, not cardinality: 'many')
|
|
732
769
|
const doctypeFields = resolvedSchema.filter(
|
|
733
|
-
field =>
|
|
770
|
+
field =>
|
|
771
|
+
'fieldtype' in field &&
|
|
772
|
+
field.fieldtype === 'Doctype' &&
|
|
773
|
+
!isDoctypeMany(field as DoctypeSchema) &&
|
|
774
|
+
'schema' in field &&
|
|
775
|
+
Array.isArray(field.schema)
|
|
734
776
|
)
|
|
735
777
|
|
|
736
778
|
// Recursively collect nested data
|
|
737
779
|
for (const field of doctypeFields) {
|
|
738
|
-
const doctypeField = field as
|
|
780
|
+
const doctypeField = field as DoctypeOneSchema
|
|
739
781
|
const fieldPath = `${basePath}.${doctypeField.fieldname}`
|
|
740
782
|
const nestedData = collectNestedData(doctypeField.schema!, fieldPath, hstStore)
|
|
741
783
|
payload[doctypeField.fieldname] = nestedData
|
|
742
784
|
}
|
|
743
785
|
|
|
786
|
+
// Also collect array data for cardinality: 'many' fields
|
|
787
|
+
const doctypeManyFields = resolvedSchema.filter(
|
|
788
|
+
field => 'fieldtype' in field && field.fieldtype === 'Doctype' && isDoctypeMany(field as DoctypeSchema)
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
for (const field of doctypeManyFields) {
|
|
792
|
+
const doctypeField = field as DoctypeManySchema
|
|
793
|
+
const fieldPath = `${basePath}.${doctypeField.fieldname}`
|
|
794
|
+
const arrayData = hstStore.get(fieldPath)
|
|
795
|
+
if (Array.isArray(arrayData)) {
|
|
796
|
+
payload[doctypeField.fieldname] = arrayData
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
744
800
|
return payload
|
|
745
801
|
}
|
package/src/field-triggers.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-new-func, no-eval */
|
|
2
1
|
import type { Map as ImmutableMap } from 'immutable'
|
|
3
2
|
import { useOperationLogStore } from './stores/operation-log'
|
|
4
3
|
import type {
|
|
@@ -101,11 +100,11 @@ export class FieldTriggerEngine {
|
|
|
101
100
|
const transitionMap = new Map<string, string[]>()
|
|
102
101
|
|
|
103
102
|
// Convert from different Map types to regular Map
|
|
104
|
-
//
|
|
105
|
-
|
|
103
|
+
// Check for Immutable.js Map first (has entrySeq method)
|
|
104
|
+
const immutableActions = actions as ImmutableMap<string, string[]>
|
|
105
|
+
if (typeof immutableActions.entrySeq === 'function') {
|
|
106
106
|
// Immutable Map
|
|
107
|
-
|
|
108
|
-
;(actions as any).entrySeq().forEach(([key, value]: [string, string[]]) => {
|
|
107
|
+
immutableActions.entrySeq().forEach(([key, value]: [string, string[]]) => {
|
|
109
108
|
this.categorizeAction(key, value, actionMap, transitionMap)
|
|
110
109
|
})
|
|
111
110
|
} else if (actions instanceof Map) {
|
|
@@ -506,7 +505,7 @@ export class FieldTriggerEngine {
|
|
|
506
505
|
})
|
|
507
506
|
.catch(error => {
|
|
508
507
|
clearTimeout(timeoutId)
|
|
509
|
-
reject(error)
|
|
508
|
+
reject(error instanceof Error ? error : new Error(String(error)))
|
|
510
509
|
})
|
|
511
510
|
})
|
|
512
511
|
}
|
package/src/registry.ts
CHANGED
|
@@ -85,17 +85,18 @@ export default class Registry {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Resolve nested Doctype
|
|
88
|
+
* Resolve nested Doctype fields in a schema by embedding child schemas inline.
|
|
89
89
|
*
|
|
90
90
|
* @remarks
|
|
91
91
|
* Walks the schema array and for each field with `fieldtype: 'Doctype'` and a string
|
|
92
|
-
* `options` value, looks up the referenced doctype in the registry and
|
|
93
|
-
* as the field's `schema` property. Recurses for deeply nested doctypes.
|
|
92
|
+
* `options` value, looks up the referenced doctype in the registry and:
|
|
94
93
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
94
|
+
* - If `cardinality: 'many'`: auto-derives `columns` from the child doctype's schema,
|
|
95
|
+
* sets `component: 'ATable'`, `config: { view: 'list' }`, and initializes `rows: []`.
|
|
96
|
+
* - Otherwise (default/`cardinality: 'one'`): embeds the child schema as the field's
|
|
97
|
+
* `schema` property for 1:1 nested forms.
|
|
98
|
+
*
|
|
99
|
+
* Recurses for deeply nested doctypes. Circular references are protected against.
|
|
99
100
|
*
|
|
100
101
|
* Returns a new array — does not mutate the original schema.
|
|
101
102
|
*
|
|
@@ -137,64 +138,52 @@ export default class Registry {
|
|
|
137
138
|
// Convert Immutable.List to plain array if needed
|
|
138
139
|
const childSchema: SchemaTypes[] = Array.isArray(doctype.schema) ? doctype.schema : Array.from(doctype.schema)
|
|
139
140
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
const resolvedChild = this.resolveSchema(childSchema, seen)
|
|
143
|
-
seen.delete(doctypeSlug)
|
|
144
|
-
|
|
145
|
-
return { ...field, schema: resolvedChild }
|
|
146
|
-
}
|
|
147
|
-
}
|
|
141
|
+
// Check cardinality to determine handling
|
|
142
|
+
const cardinality = 'cardinality' in field ? field.cardinality : undefined
|
|
148
143
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
field.fieldtype === 'Table' &&
|
|
153
|
-
'options' in field &&
|
|
154
|
-
typeof field.options === 'string'
|
|
155
|
-
) {
|
|
156
|
-
const doctypeSlug = field.options
|
|
144
|
+
if (cardinality === 'many') {
|
|
145
|
+
// 1:many child table - derive columns, set component, config, rows
|
|
146
|
+
const resolved: Record<string, any> = { ...field }
|
|
157
147
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
148
|
+
// Auto-derive columns from child schema fields if not already provided
|
|
149
|
+
if (!('columns' in field) || !field.columns) {
|
|
150
|
+
resolved.columns = childSchema.map(childField => ({
|
|
151
|
+
name: childField.fieldname,
|
|
152
|
+
fieldname: childField.fieldname,
|
|
153
|
+
label: ('label' in childField && childField.label) || childField.fieldname,
|
|
154
|
+
fieldtype: 'fieldtype' in childField ? childField.fieldtype : 'Data',
|
|
155
|
+
align: ('align' in childField && childField.align) || 'left',
|
|
156
|
+
edit: 'edit' in childField ? childField.edit : true,
|
|
157
|
+
width: ('width' in childField && childField.width) || '20ch',
|
|
158
|
+
}))
|
|
159
|
+
}
|
|
162
160
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
// Set default component if not already specified
|
|
162
|
+
if (!resolved.component) {
|
|
163
|
+
resolved.component = 'ATable'
|
|
164
|
+
}
|
|
167
165
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
fieldname: childField.fieldname,
|
|
173
|
-
label: ('label' in childField && childField.label) || childField.fieldname,
|
|
174
|
-
fieldtype: 'fieldtype' in childField ? childField.fieldtype : 'Data',
|
|
175
|
-
align: ('align' in childField && childField.align) || 'left',
|
|
176
|
-
edit: 'edit' in childField ? childField.edit : true,
|
|
177
|
-
width: ('width' in childField && childField.width) || '20ch',
|
|
178
|
-
}))
|
|
179
|
-
}
|
|
166
|
+
// Set default config if not already specified
|
|
167
|
+
if (!('config' in field) || !field.config) {
|
|
168
|
+
resolved.config = { view: 'list' }
|
|
169
|
+
}
|
|
180
170
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
171
|
+
// Initialize rows to empty array so componentProps fallback
|
|
172
|
+
// routes data from the form's dataModel[fieldname]
|
|
173
|
+
if (!('rows' in field) || !field.rows) {
|
|
174
|
+
resolved.rows = []
|
|
175
|
+
}
|
|
185
176
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
177
|
+
return resolved as SchemaTypes
|
|
178
|
+
} else {
|
|
179
|
+
// 1:1 nested form (default cardinality: 'one')
|
|
180
|
+
// Recurse into child schema to resolve deeply nested doctypes
|
|
181
|
+
seen.add(doctypeSlug)
|
|
182
|
+
const resolvedChild = this.resolveSchema(childSchema, seen)
|
|
183
|
+
seen.delete(doctypeSlug)
|
|
190
184
|
|
|
191
|
-
|
|
192
|
-
// routes data from the form's dataModel[fieldname]
|
|
193
|
-
if (!('rows' in field) || !field.rows) {
|
|
194
|
-
resolved.rows = []
|
|
185
|
+
return { ...field, schema: resolvedChild }
|
|
195
186
|
}
|
|
196
|
-
|
|
197
|
-
return resolved as SchemaTypes
|
|
198
187
|
}
|
|
199
188
|
}
|
|
200
189
|
|
|
@@ -211,11 +200,13 @@ export default class Registry {
|
|
|
211
200
|
* - Data, Text → `''`
|
|
212
201
|
* - Check → `false`
|
|
213
202
|
* - Int, Float, Decimal, Currency, Quantity → `0`
|
|
214
|
-
* -
|
|
215
|
-
* -
|
|
203
|
+
* - JSON → `{}`
|
|
204
|
+
* - Doctype with `cardinality: 'many'` → `[]`
|
|
205
|
+
* - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
|
|
216
206
|
* - All others → `null`
|
|
217
207
|
*
|
|
218
|
-
* For Doctype fields with a resolved `schema` array, recursively
|
|
208
|
+
* For Doctype fields with a resolved `schema` array (cardinality: 'one'), recursively
|
|
209
|
+
* initializes the nested record.
|
|
219
210
|
*
|
|
220
211
|
* @param schema - The schema array to derive defaults from
|
|
221
212
|
* @returns A plain object with default values for each field
|
|
@@ -250,20 +241,24 @@ export default class Registry {
|
|
|
250
241
|
case 'Quantity':
|
|
251
242
|
record[field.fieldname] = 0
|
|
252
243
|
break
|
|
253
|
-
case 'Table':
|
|
254
|
-
record[field.fieldname] = []
|
|
255
|
-
break
|
|
256
244
|
case 'JSON':
|
|
257
245
|
record[field.fieldname] = {}
|
|
258
246
|
break
|
|
259
|
-
case 'Doctype':
|
|
260
|
-
//
|
|
261
|
-
|
|
247
|
+
case 'Doctype': {
|
|
248
|
+
// Check cardinality to determine initial value
|
|
249
|
+
const cardinality = 'cardinality' in field ? field.cardinality : undefined
|
|
250
|
+
if (cardinality === 'many') {
|
|
251
|
+
// 1:many child table - initialize as empty array
|
|
252
|
+
record[field.fieldname] = []
|
|
253
|
+
} else if ('schema' in field && Array.isArray(field.schema)) {
|
|
254
|
+
// 1:1 nested form with resolved schema - recursively initialize
|
|
262
255
|
record[field.fieldname] = this.initializeRecord(field.schema)
|
|
263
256
|
} else {
|
|
257
|
+
// 1:1 without resolved schema - empty object
|
|
264
258
|
record[field.fieldname] = {}
|
|
265
259
|
}
|
|
266
260
|
break
|
|
261
|
+
}
|
|
267
262
|
default:
|
|
268
263
|
record[field.fieldname] = null
|
|
269
264
|
}
|
package/src/stonecrop.ts
CHANGED