@stonecrop/stonecrop 0.10.16 → 0.11.0
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/README.md +72 -29
- package/dist/composable.js +1 -0
- package/dist/composables/lazy-link.js +125 -0
- package/dist/composables/stonecrop.js +123 -68
- package/dist/composables/use-lazy-link-state.js +125 -0
- package/dist/composables/use-stonecrop.js +476 -0
- package/dist/doctype.js +10 -2
- package/dist/field-triggers.js +15 -3
- package/dist/index.js +4 -3
- package/dist/operation-log-DB-dGNT9.js +593 -0
- package/dist/operation-log-DB-dGNT9.js.map +1 -0
- package/dist/registry.js +261 -101
- package/dist/schema-validator.js +105 -1
- package/dist/src/composable.d.ts +11 -0
- package/dist/src/composable.d.ts.map +1 -0
- package/dist/src/composable.js +477 -0
- package/dist/src/composables/lazy-link.d.ts +25 -0
- package/dist/src/composables/lazy-link.d.ts.map +1 -0
- package/dist/src/composables/operation-log.d.ts +5 -5
- package/dist/src/composables/operation-log.d.ts.map +1 -1
- package/dist/src/composables/operation-log.js +224 -0
- package/dist/src/composables/stonecrop.d.ts +11 -1
- package/dist/src/composables/stonecrop.d.ts.map +1 -1
- package/dist/src/composables/stonecrop.js +574 -0
- package/dist/src/composables/use-lazy-link-state.d.ts +25 -0
- package/dist/src/composables/use-lazy-link-state.d.ts.map +1 -0
- package/dist/src/composables/use-stonecrop.d.ts +93 -0
- package/dist/src/composables/use-stonecrop.d.ts.map +1 -0
- package/dist/src/composables/useNestedSchema.d.ts +110 -0
- package/dist/src/composables/useNestedSchema.d.ts.map +1 -0
- package/dist/src/composables/useNestedSchema.js +155 -0
- package/dist/src/doctype.d.ts +9 -1
- package/dist/src/doctype.d.ts.map +1 -1
- package/dist/src/doctype.js +234 -0
- package/dist/src/exceptions.js +16 -0
- package/dist/src/field-triggers.d.ts +6 -0
- package/dist/src/field-triggers.d.ts.map +1 -1
- package/dist/src/field-triggers.js +567 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +23 -0
- package/dist/src/plugins/index.js +96 -0
- package/dist/src/registry.d.ts +102 -23
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +246 -0
- package/dist/src/schema-validator.d.ts +8 -1
- package/dist/src/schema-validator.d.ts.map +1 -1
- package/dist/src/schema-validator.js +315 -0
- package/dist/src/stonecrop.d.ts +73 -28
- package/dist/src/stonecrop.d.ts.map +1 -1
- package/dist/src/stonecrop.js +339 -0
- package/dist/src/stores/data.d.ts +11 -0
- package/dist/src/stores/data.d.ts.map +1 -0
- package/dist/src/stores/hst.d.ts +5 -75
- package/dist/src/stores/hst.d.ts.map +1 -1
- package/dist/src/stores/hst.js +495 -0
- package/dist/src/stores/index.js +12 -0
- package/dist/src/stores/operation-log.d.ts +14 -14
- package/dist/src/stores/operation-log.d.ts.map +1 -1
- package/dist/src/stores/operation-log.js +568 -0
- package/dist/src/stores/xstate.d.ts +31 -0
- package/dist/src/stores/xstate.d.ts.map +1 -0
- package/dist/src/tsdoc-metadata.json +11 -0
- package/dist/src/types/composable.d.ts +50 -12
- package/dist/src/types/composable.d.ts.map +1 -1
- package/dist/src/types/doctype.d.ts +6 -7
- package/dist/src/types/doctype.d.ts.map +1 -1
- package/dist/src/types/field-triggers.d.ts +1 -1
- package/dist/src/types/field-triggers.d.ts.map +1 -1
- package/dist/src/types/field-triggers.js +4 -0
- package/dist/src/types/hst.d.ts +70 -0
- package/dist/src/types/hst.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +1 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +4 -0
- package/dist/src/types/operation-log.d.ts +4 -4
- package/dist/src/types/operation-log.d.ts.map +1 -1
- package/dist/src/types/operation-log.js +0 -0
- package/dist/src/types/registry.js +0 -0
- package/dist/src/types/schema-validator.d.ts +2 -0
- package/dist/src/types/schema-validator.d.ts.map +1 -1
- package/dist/src/utils.d.ts +24 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/stonecrop.d.ts +317 -99
- package/dist/stonecrop.js +2191 -1897
- package/dist/stonecrop.js.map +1 -1
- package/dist/stonecrop.umd.cjs +6 -0
- package/dist/stonecrop.umd.cjs.map +1 -0
- package/dist/stores/data.js +7 -0
- package/dist/stores/hst.js +27 -25
- package/dist/stores/operation-log.js +59 -47
- package/dist/stores/xstate.js +29 -0
- package/dist/tests/setup.d.ts +5 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +15 -0
- package/dist/types/hst.js +0 -0
- package/dist/types/index.js +1 -0
- package/dist/utils.js +46 -0
- package/package.json +5 -5
- package/src/composables/lazy-link.ts +146 -0
- package/src/composables/operation-log.ts +1 -1
- package/src/composables/stonecrop.ts +142 -73
- package/src/doctype.ts +13 -4
- package/src/field-triggers.ts +18 -4
- package/src/index.ts +4 -2
- package/src/registry.ts +289 -111
- package/src/schema-validator.ts +120 -1
- package/src/stonecrop.ts +230 -106
- package/src/stores/hst.ts +29 -104
- package/src/stores/operation-log.ts +64 -50
- package/src/types/composable.ts +55 -12
- package/src/types/doctype.ts +6 -7
- package/src/types/field-triggers.ts +1 -1
- package/src/types/hst.ts +77 -0
- package/src/types/index.ts +1 -0
- package/src/types/operation-log.ts +4 -4
- package/src/types/schema-validator.ts +2 -0
|
@@ -5,7 +5,7 @@ import { inject, onMounted, Ref, ref, watch, provide, computed } from 'vue'
|
|
|
5
5
|
import Doctype from '../doctype'
|
|
6
6
|
import Registry from '../registry'
|
|
7
7
|
import { Stonecrop } from '../stonecrop'
|
|
8
|
-
import type { HSTNode } from '../
|
|
8
|
+
import type { HSTNode } from '../types/hst'
|
|
9
9
|
import type { BaseStonecropReturn, HSTStonecropReturn, HSTChangeData, OperationLogAPI } from '../types/composable'
|
|
10
10
|
import type { HSTOperation, OperationLogConfig } from '../types/operation-log'
|
|
11
11
|
import type { RouteContext } from '../types/registry'
|
|
@@ -19,7 +19,17 @@ import type { RouteContext } from '../types/registry'
|
|
|
19
19
|
*/
|
|
20
20
|
export function useStonecrop(): BaseStonecropReturn | HSTStonecropReturn
|
|
21
21
|
/**
|
|
22
|
-
* Unified Stonecrop composable with HST integration for a specific doctype and record
|
|
22
|
+
* Unified Stonecrop composable with HST integration for a specific doctype and record.
|
|
23
|
+
*
|
|
24
|
+
* When a `Doctype` instance is passed, all synchronous initialisation (`hstStore`,
|
|
25
|
+
* `resolvedSchema`, `formData`, `handleHSTChange`, operation-log wiring) is performed
|
|
26
|
+
* during `setup()` — before the first render and without awaiting any lifecycle hook.
|
|
27
|
+
* Callers can read `hstStore.value`, `resolvedSchema.value`, and `formData.value`
|
|
28
|
+
* immediately after calling this composable; no `nextTick`, `flushPromises`, or
|
|
29
|
+
* `setTimeout` is required.
|
|
30
|
+
*
|
|
31
|
+
* The only remaining async work in `onMounted` is fetching an existing record from the
|
|
32
|
+
* server when `recordId` is not `'new'`, and lazy-loading a doctype by slug string.
|
|
23
33
|
*
|
|
24
34
|
* @param options - Configuration with doctype (string slug or Doctype instance) and optional recordId
|
|
25
35
|
* @returns Stonecrop instance with full HST integration utilities
|
|
@@ -58,6 +68,23 @@ export function useStonecrop(options?: {
|
|
|
58
68
|
const error = ref<Error | null>(null)
|
|
59
69
|
const resolvedDoctype = ref<Doctype | undefined>()
|
|
60
70
|
|
|
71
|
+
// Workflow readiness computed properties
|
|
72
|
+
const isWorkflowReady = computed(() => {
|
|
73
|
+
if (!stonecrop.value || !resolvedDoctype.value || !options.recordId || options.recordId === 'new') {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
const status = stonecrop.value.isWorkflowReady(resolvedDoctype.value, options.recordId)
|
|
77
|
+
return status.ready
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const blockedLinks = computed(() => {
|
|
81
|
+
if (!stonecrop.value || !resolvedDoctype.value || !options.recordId || options.recordId === 'new') {
|
|
82
|
+
return []
|
|
83
|
+
}
|
|
84
|
+
const status = stonecrop.value.isWorkflowReady(resolvedDoctype.value, options.recordId)
|
|
85
|
+
return status.blockedLinks ?? []
|
|
86
|
+
})
|
|
87
|
+
|
|
61
88
|
// Initialize stonecrop instance synchronously using singleton pattern
|
|
62
89
|
// Use injected instance if available, otherwise fall back to the singleton root
|
|
63
90
|
const stonecropInstance = providedStonecrop || Stonecrop._root
|
|
@@ -70,7 +97,7 @@ export function useStonecrop(options?: {
|
|
|
70
97
|
resolvedDoctype.value = options.doctype
|
|
71
98
|
}
|
|
72
99
|
|
|
73
|
-
// Operation log state and methods
|
|
100
|
+
// Operation log state and methods
|
|
74
101
|
const operations = ref<HSTOperation[]>([])
|
|
75
102
|
const currentIndex = ref(-1)
|
|
76
103
|
const canUndo = computed(() => stonecrop.value?.getOperationLogStore().canUndo ?? false)
|
|
@@ -147,13 +174,9 @@ export function useStonecrop(options?: {
|
|
|
147
174
|
stonecrop.value?.getOperationLogStore().configure(config)
|
|
148
175
|
}
|
|
149
176
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Set up reactive refs from operation log store - only if Pinia is available
|
|
177
|
+
// Wire operation log reactive state synchronously — no lifecycle hook needed.
|
|
178
|
+
// storeToRefs and watch are both safe to call in setup() body.
|
|
179
|
+
if (registry && stonecrop.value) {
|
|
157
180
|
try {
|
|
158
181
|
const opLogStore = stonecrop.value.getOperationLogStore()
|
|
159
182
|
const opLogRefs = storeToRefs(opLogStore)
|
|
@@ -174,8 +197,31 @@ export function useStonecrop(options?: {
|
|
|
174
197
|
}
|
|
175
198
|
)
|
|
176
199
|
} catch {
|
|
177
|
-
// Pinia not available
|
|
178
|
-
|
|
200
|
+
// Pinia not available — operation log is optional, silently skip
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Synchronous HST initialisation for an explicit Doctype instance.
|
|
205
|
+
// When the caller passes a Doctype object (not a slug string), every piece of
|
|
206
|
+
// setup that doesn't require network I/O runs here during setup() so that
|
|
207
|
+
// hstStore, resolvedSchema, and formData are populated before the first render
|
|
208
|
+
// and are immediately available to callers without any await.
|
|
209
|
+
if (options.doctype && typeof options.doctype !== 'string' && registry && stonecrop.value) {
|
|
210
|
+
hstStore.value = stonecrop.value.getStore()
|
|
211
|
+
resolvedSchema.value = registry.resolveSchema(options.doctype)
|
|
212
|
+
if (!options.recordId || options.recordId === 'new') {
|
|
213
|
+
formData.value = registry.initializeRecord(resolvedSchema.value)
|
|
214
|
+
}
|
|
215
|
+
if (hstStore.value) {
|
|
216
|
+
setupDeepReactivity(options.doctype, options.recordId || 'new', formData, hstStore.value)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// onMounted handles only work that is genuinely async: lazy-loading a doctype
|
|
221
|
+
// by slug, fetching an existing record from the server, and router-based setup.
|
|
222
|
+
onMounted(async () => {
|
|
223
|
+
if (!registry || !stonecrop.value) {
|
|
224
|
+
return
|
|
179
225
|
}
|
|
180
226
|
|
|
181
227
|
// Handle router-based setup if no specific doctype provided
|
|
@@ -207,12 +253,7 @@ export function useStonecrop(options?: {
|
|
|
207
253
|
|
|
208
254
|
// Resolve schema for router-loaded doctype
|
|
209
255
|
if (registry) {
|
|
210
|
-
|
|
211
|
-
? Array.isArray(doctype.schema)
|
|
212
|
-
? doctype.schema
|
|
213
|
-
: Array.from(doctype.schema)
|
|
214
|
-
: []
|
|
215
|
-
resolvedSchema.value = registry.resolveSchema(schemaArray)
|
|
256
|
+
resolvedSchema.value = registry.resolveSchema(doctype)
|
|
216
257
|
}
|
|
217
258
|
|
|
218
259
|
if (recordId && recordId !== 'new') {
|
|
@@ -245,18 +286,16 @@ export function useStonecrop(options?: {
|
|
|
245
286
|
|
|
246
287
|
// Handle HST integration if doctype is provided explicitly
|
|
247
288
|
if (options.doctype) {
|
|
248
|
-
hstStore.value = stonecrop.value.getStore()
|
|
249
289
|
const recordId = options.recordId
|
|
250
290
|
|
|
251
|
-
// Resolve doctype - handle string (lazy-load) or Doctype instance
|
|
252
|
-
let doctype: Doctype | undefined
|
|
253
|
-
|
|
254
291
|
if (typeof options.doctype === 'string') {
|
|
255
|
-
// String doctype
|
|
292
|
+
// String doctype — resolve lazily, then do full sync-equivalent setup here.
|
|
256
293
|
const doctypeSlug = options.doctype
|
|
294
|
+
hstStore.value = stonecrop.value.getStore()
|
|
257
295
|
isLoading.value = true
|
|
258
296
|
error.value = null
|
|
259
297
|
|
|
298
|
+
let doctype: Doctype | undefined
|
|
260
299
|
try {
|
|
261
300
|
// Check if already in registry
|
|
262
301
|
doctype = registry.getDoctype(doctypeSlug)
|
|
@@ -281,48 +320,54 @@ export function useStonecrop(options?: {
|
|
|
281
320
|
} finally {
|
|
282
321
|
isLoading.value = false
|
|
283
322
|
}
|
|
284
|
-
} else {
|
|
285
|
-
// Doctype instance provided directly
|
|
286
|
-
doctype = options.doctype
|
|
287
|
-
}
|
|
288
323
|
|
|
289
|
-
|
|
290
|
-
|
|
324
|
+
resolvedDoctype.value = doctype
|
|
325
|
+
if (!doctype) return
|
|
291
326
|
|
|
292
|
-
|
|
293
|
-
// Error already set above, just return
|
|
294
|
-
return
|
|
295
|
-
}
|
|
327
|
+
resolvedSchema.value = registry.resolveSchema(doctype)
|
|
296
328
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
await stonecrop.value.getRecord(doctype, recordId)
|
|
312
|
-
const loadedRecord = stonecrop.value.getRecordById(doctype, recordId)
|
|
313
|
-
if (loadedRecord) {
|
|
314
|
-
formData.value = loadedRecord.get('') || {}
|
|
329
|
+
if (recordId && recordId !== 'new') {
|
|
330
|
+
const existingRecord = stonecrop.value.getRecordById(doctype, recordId)
|
|
331
|
+
if (existingRecord) {
|
|
332
|
+
formData.value = existingRecord.get('') || {}
|
|
333
|
+
} else {
|
|
334
|
+
try {
|
|
335
|
+
await stonecrop.value.getRecord(doctype, recordId)
|
|
336
|
+
const loadedRecord = stonecrop.value.getRecordById(doctype, recordId)
|
|
337
|
+
if (loadedRecord) {
|
|
338
|
+
formData.value = loadedRecord.get('') || {}
|
|
339
|
+
}
|
|
340
|
+
} catch {
|
|
341
|
+
formData.value = registry.initializeRecord(resolvedSchema.value)
|
|
315
342
|
}
|
|
316
|
-
} catch {
|
|
317
|
-
formData.value = registry.initializeRecord(resolvedSchema.value)
|
|
318
343
|
}
|
|
344
|
+
} else {
|
|
345
|
+
formData.value = registry.initializeRecord(resolvedSchema.value)
|
|
319
346
|
}
|
|
320
|
-
} else {
|
|
321
|
-
formData.value = registry.initializeRecord(resolvedSchema.value)
|
|
322
|
-
}
|
|
323
347
|
|
|
324
|
-
|
|
325
|
-
|
|
348
|
+
if (hstStore.value) {
|
|
349
|
+
setupDeepReactivity(doctype, recordId || 'new', formData, hstStore.value)
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
// Doctype instance — sync init was done during setup().
|
|
353
|
+
// Only handle the async path: fetching an existing record from the server.
|
|
354
|
+
if (recordId && recordId !== 'new') {
|
|
355
|
+
const doctype = options.doctype
|
|
356
|
+
const existingRecord = stonecrop.value.getRecordById(doctype, recordId)
|
|
357
|
+
if (existingRecord) {
|
|
358
|
+
formData.value = existingRecord.get('') || {}
|
|
359
|
+
} else {
|
|
360
|
+
try {
|
|
361
|
+
await stonecrop.value.getRecord(doctype, recordId)
|
|
362
|
+
const loadedRecord = stonecrop.value.getRecordById(doctype, recordId)
|
|
363
|
+
if (loadedRecord) {
|
|
364
|
+
formData.value = loadedRecord.get('') || {}
|
|
365
|
+
}
|
|
366
|
+
} catch {
|
|
367
|
+
formData.value = registry.initializeRecord(resolvedSchema.value)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
326
371
|
}
|
|
327
372
|
}
|
|
328
373
|
})
|
|
@@ -393,18 +438,36 @@ export function useStonecrop(options?: {
|
|
|
393
438
|
}
|
|
394
439
|
|
|
395
440
|
/**
|
|
396
|
-
*
|
|
397
|
-
* Delegates to Stonecrop.
|
|
398
|
-
* @param
|
|
399
|
-
* @param
|
|
400
|
-
|
|
401
|
-
|
|
441
|
+
* Scaffold empty descendant records from defaults for all descendant links.
|
|
442
|
+
* Delegates to Stonecrop.initializeNestedData method.
|
|
443
|
+
* @param path - The HST path where initialized data should be stored
|
|
444
|
+
* @param doctype - The doctype to initialize
|
|
445
|
+
*/
|
|
446
|
+
const initializeNestedData = (path: string, doctype: Doctype): void => {
|
|
447
|
+
if (!stonecrop.value) {
|
|
448
|
+
throw new Error('Stonecrop instance not available')
|
|
449
|
+
}
|
|
450
|
+
return stonecrop.value.initializeNestedData(path, doctype)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Fetch a record and its nested data from the server.
|
|
455
|
+
* Delegates to Stonecrop.fetchNestedData method.
|
|
456
|
+
* @param path - The HST path (e.g., "recipe.r1")
|
|
457
|
+
* @param doctype - The doctype to fetch
|
|
458
|
+
* @param recordId - Record ID to fetch
|
|
459
|
+
* @param options - Query options (includeNested to control which links are fetched)
|
|
402
460
|
*/
|
|
403
|
-
const
|
|
461
|
+
const fetchNestedData = async (
|
|
462
|
+
path: string,
|
|
463
|
+
doctype: Doctype,
|
|
464
|
+
recordId: string,
|
|
465
|
+
options?: { includeNested?: boolean | string[] }
|
|
466
|
+
): Promise<void> => {
|
|
404
467
|
if (!stonecrop.value) {
|
|
405
468
|
throw new Error('Stonecrop instance not available')
|
|
406
469
|
}
|
|
407
|
-
return stonecrop.value.
|
|
470
|
+
return stonecrop.value.fetchNestedData(path, doctype, recordId, options)
|
|
408
471
|
}
|
|
409
472
|
|
|
410
473
|
/**
|
|
@@ -422,12 +485,12 @@ export function useStonecrop(options?: {
|
|
|
422
485
|
}
|
|
423
486
|
|
|
424
487
|
/**
|
|
425
|
-
* Create a nested context for
|
|
488
|
+
* Create a nested context for descendant forms
|
|
426
489
|
* @param basePath - The base path for the nested context (e.g., "customer.123.address")
|
|
427
|
-
* @param
|
|
490
|
+
* @param _descendantDoctype - The descendant doctype metadata (unused but kept for API consistency)
|
|
428
491
|
* @returns Object with scoped provideHSTPath and handleHSTChange
|
|
429
492
|
*/
|
|
430
|
-
const createNestedContext = (basePath: string,
|
|
493
|
+
const createNestedContext = (basePath: string, _descendantDoctype: Doctype) => {
|
|
431
494
|
const nestedProvideHSTPath = (fieldname: string): string => {
|
|
432
495
|
return `${basePath}.${fieldname}`
|
|
433
496
|
}
|
|
@@ -480,13 +543,16 @@ export function useStonecrop(options?: {
|
|
|
480
543
|
hstStore,
|
|
481
544
|
formData,
|
|
482
545
|
resolvedSchema,
|
|
483
|
-
|
|
546
|
+
initializeNestedData,
|
|
547
|
+
fetchNestedData,
|
|
484
548
|
collectRecordPayload,
|
|
485
549
|
createNestedContext,
|
|
486
550
|
isLoading,
|
|
487
551
|
error,
|
|
488
552
|
resolvedDoctype,
|
|
489
|
-
|
|
553
|
+
isWorkflowReady,
|
|
554
|
+
blockedLinks,
|
|
555
|
+
} satisfies HSTStonecropReturn
|
|
490
556
|
} else if (!options.doctype && registry?.router) {
|
|
491
557
|
// Router-based - return HST (will be populated after mount)
|
|
492
558
|
return {
|
|
@@ -497,13 +563,16 @@ export function useStonecrop(options?: {
|
|
|
497
563
|
hstStore,
|
|
498
564
|
formData,
|
|
499
565
|
resolvedSchema,
|
|
500
|
-
|
|
566
|
+
initializeNestedData,
|
|
567
|
+
fetchNestedData,
|
|
501
568
|
collectRecordPayload,
|
|
502
569
|
createNestedContext,
|
|
503
570
|
isLoading,
|
|
504
571
|
error,
|
|
505
572
|
resolvedDoctype,
|
|
506
|
-
|
|
573
|
+
isWorkflowReady,
|
|
574
|
+
blockedLinks,
|
|
575
|
+
} satisfies HSTStonecropReturn
|
|
507
576
|
}
|
|
508
577
|
|
|
509
578
|
// No doctype and no router - basic mode
|
package/src/doctype.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { SchemaTypes } from '@stonecrop/aform'
|
|
2
|
-
import type { WorkflowMeta } from '@stonecrop/schema'
|
|
2
|
+
import type { LinkDeclaration, WorkflowMeta } from '@stonecrop/schema'
|
|
3
3
|
import { List, Map } from 'immutable'
|
|
4
4
|
import { Component } from 'vue'
|
|
5
|
-
import type { UnknownMachineConfig } from 'xstate'
|
|
6
5
|
|
|
7
6
|
import type { ImmutableDoctype } from './types'
|
|
8
7
|
import type { DoctypeConfig } from './types/doctype'
|
|
@@ -56,6 +55,13 @@ export default class Doctype {
|
|
|
56
55
|
*/
|
|
57
56
|
readonly component?: Component
|
|
58
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Relationship links to other doctypes
|
|
60
|
+
* @public
|
|
61
|
+
* @readonly
|
|
62
|
+
*/
|
|
63
|
+
readonly links?: Record<string, LinkDeclaration>
|
|
64
|
+
|
|
59
65
|
/**
|
|
60
66
|
* Creates a new Doctype instance
|
|
61
67
|
* @param doctype - The doctype name
|
|
@@ -63,19 +69,22 @@ export default class Doctype {
|
|
|
63
69
|
* @param workflow - The doctype workflow configuration (XState machine)
|
|
64
70
|
* @param actions - The doctype actions and field triggers
|
|
65
71
|
* @param component - Optional Vue component for rendering the doctype
|
|
72
|
+
* @param links - Optional relationship links to other doctypes
|
|
66
73
|
*/
|
|
67
74
|
constructor(
|
|
68
75
|
doctype: string,
|
|
69
76
|
schema: ImmutableDoctype['schema'],
|
|
70
77
|
workflow: ImmutableDoctype['workflow'],
|
|
71
78
|
actions: ImmutableDoctype['actions'],
|
|
72
|
-
component?: Component
|
|
79
|
+
component?: Component,
|
|
80
|
+
links?: Record<string, LinkDeclaration>
|
|
73
81
|
) {
|
|
74
82
|
this.doctype = doctype
|
|
75
83
|
this.schema = schema
|
|
76
84
|
this.workflow = workflow
|
|
77
85
|
this.actions = actions
|
|
78
86
|
this.component = component
|
|
87
|
+
this.links = links
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
/**
|
|
@@ -120,7 +129,7 @@ export default class Doctype {
|
|
|
120
129
|
const schema = config.fields ? List(config.fields) : List<SchemaTypes>()
|
|
121
130
|
const actions = config.actions ? Map(config.actions) : Map<string, string[]>()
|
|
122
131
|
|
|
123
|
-
return new Doctype(config.name, schema, config.workflow, actions)
|
|
132
|
+
return new Doctype(config.name, schema, config.workflow, actions, undefined, config.links)
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
/**
|
package/src/field-triggers.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Map as ImmutableMap } from 'immutable'
|
|
2
2
|
import { useOperationLogStore } from './stores/operation-log'
|
|
3
3
|
import type {
|
|
4
|
+
FieldAction,
|
|
4
5
|
FieldActionFunction,
|
|
5
6
|
FieldChangeContext,
|
|
6
7
|
FieldTriggerExecutionResult,
|
|
@@ -22,7 +23,7 @@ export class FieldTriggerEngine {
|
|
|
22
23
|
*/
|
|
23
24
|
static _root: FieldTriggerEngine
|
|
24
25
|
|
|
25
|
-
private options
|
|
26
|
+
private options!: FieldTriggerOptions & { defaultTimeout: number; debug: boolean; enableRollback: boolean }
|
|
26
27
|
private doctypeActions = new Map<string, Map<string, string[]>>() // doctype -> action/field -> functions
|
|
27
28
|
private doctypeTransitions = new Map<string, Map<string, string[]>>() // doctype -> transition -> functions
|
|
28
29
|
private fieldRollbackConfig = new Map<string, Map<string, boolean>>() // doctype -> field -> rollback enabled
|
|
@@ -55,6 +56,15 @@ export class FieldTriggerEngine {
|
|
|
55
56
|
this.globalActions.set(name, fn)
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Look up a registered action function by name.
|
|
61
|
+
* Returns `undefined` if the action has not been registered.
|
|
62
|
+
* @param name - The action name
|
|
63
|
+
*/
|
|
64
|
+
getAction(name: string): FieldActionFunction | undefined {
|
|
65
|
+
return this.globalActions.get(name)
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
/**
|
|
59
69
|
* Register a global XState transition action function
|
|
60
70
|
* @param name - The name of the transition action
|
|
@@ -227,11 +237,13 @@ export class FieldTriggerEngine {
|
|
|
227
237
|
const totalExecutionTime = performance.now() - startTime
|
|
228
238
|
|
|
229
239
|
// Call global error handler if configured and errors occurred
|
|
230
|
-
const failedResults = actionResults.filter(r => !r.success)
|
|
240
|
+
const failedResults = actionResults.filter(r => !r.success && r.error != null)
|
|
231
241
|
if (failedResults.length > 0 && this.options.errorHandler) {
|
|
232
242
|
for (const failedResult of failedResults) {
|
|
233
243
|
try {
|
|
234
|
-
|
|
244
|
+
if (failedResult.error) {
|
|
245
|
+
this.options.errorHandler(failedResult.error, context, failedResult.action)
|
|
246
|
+
}
|
|
235
247
|
} catch (handlerError) {
|
|
236
248
|
// eslint-disable-next-line no-console
|
|
237
249
|
console.error('[FieldTriggers] Error in global error handler:', handlerError)
|
|
@@ -301,7 +313,9 @@ export class FieldTriggerEngine {
|
|
|
301
313
|
for (const failedResult of failedResults) {
|
|
302
314
|
try {
|
|
303
315
|
// Call with FieldChangeContext (base context type)
|
|
304
|
-
|
|
316
|
+
if (failedResult.error) {
|
|
317
|
+
this.options.errorHandler(failedResult.error, context, failedResult.action as unknown as FieldAction)
|
|
318
|
+
}
|
|
305
319
|
} catch (handlerError) {
|
|
306
320
|
// eslint-disable-next-line no-console
|
|
307
321
|
console.error('[FieldTriggers] Error in global error handler:', handlerError)
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type * from '@stonecrop/aform/types'
|
|
2
2
|
export type * from '@stonecrop/atable/types'
|
|
3
3
|
|
|
4
|
+
import { useLazyLink } from './composables/lazy-link'
|
|
4
5
|
import { useStonecrop } from './composables/stonecrop'
|
|
5
6
|
import { useOperationLog, useUndoRedoShortcuts, withBatch } from './composables/operation-log'
|
|
6
7
|
import Doctype from './doctype'
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
} from './field-triggers'
|
|
16
17
|
import plugin from './plugins'
|
|
17
18
|
import Registry from './registry'
|
|
18
|
-
import { Stonecrop,
|
|
19
|
+
import { Stonecrop, getStonecrop } from './stonecrop'
|
|
19
20
|
import { HST, createHST, type HSTNode } from './stores/hst'
|
|
20
21
|
import { useOperationLogStore } from './stores/operation-log'
|
|
21
22
|
import { SchemaValidator, createValidator, validateSchema } from './schema-validator'
|
|
@@ -31,6 +32,7 @@ export {
|
|
|
31
32
|
Doctype,
|
|
32
33
|
Registry,
|
|
33
34
|
Stonecrop,
|
|
35
|
+
useLazyLink,
|
|
34
36
|
useStonecrop,
|
|
35
37
|
// HST exports for advanced usage
|
|
36
38
|
HST,
|
|
@@ -54,7 +56,7 @@ export {
|
|
|
54
56
|
useUndoRedoShortcuts,
|
|
55
57
|
withBatch,
|
|
56
58
|
// Utility functions
|
|
57
|
-
|
|
59
|
+
getStonecrop,
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// Default export is the Vue plugin
|