@stonecrop/stonecrop 0.10.5 → 0.10.7
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 +32 -3
- package/dist/composables/stonecrop.js +69 -12
- package/dist/doctype.js +81 -4
- package/dist/index.js +2 -2
- package/dist/registry.js +4 -4
- package/dist/src/composables/stonecrop.d.ts +9 -6
- package/dist/src/composables/stonecrop.d.ts.map +1 -1
- package/dist/src/doctype.d.ts +95 -4
- package/dist/src/doctype.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/registry.d.ts +10 -10
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/stonecrop.d.ts +14 -14
- package/dist/src/stonecrop.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +2 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/stonecrop.d.ts +125 -32
- package/dist/stonecrop.js +4647 -1391
- package/dist/stonecrop.js.map +1 -1
- package/package.json +4 -4
- package/src/composables/stonecrop.ts +92 -28
- package/src/doctype.ts +112 -4
- package/src/index.ts +3 -2
- package/src/registry.ts +11 -11
- package/src/stonecrop.ts +15 -15
- package/src/types/index.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/stonecrop",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.7",
|
|
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.7"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"pinia": "^3.0.4",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"vue-router": "^5.0.2",
|
|
61
61
|
"vite": "^7.3.1",
|
|
62
62
|
"vitest": "^4.0.18",
|
|
63
|
-
"@stonecrop/
|
|
64
|
-
"@stonecrop/
|
|
63
|
+
"@stonecrop/atable": "0.10.7",
|
|
64
|
+
"@stonecrop/aform": "0.10.7",
|
|
65
65
|
"stonecrop-rig": "0.7.0"
|
|
66
66
|
},
|
|
67
67
|
"description": "Schema-driven framework with XState workflows and HST state management",
|
|
@@ -2,7 +2,7 @@ import { inject, onMounted, Ref, ref, watch, provide, computed, ComputedRef } fr
|
|
|
2
2
|
|
|
3
3
|
import Registry from '../registry'
|
|
4
4
|
import { Stonecrop } from '../stonecrop'
|
|
5
|
-
import
|
|
5
|
+
import Doctype from '../doctype'
|
|
6
6
|
import type { HSTNode } from '../stores/hst'
|
|
7
7
|
import { RouteContext } from '../types/registry'
|
|
8
8
|
import { storeToRefs } from 'pinia'
|
|
@@ -65,15 +65,18 @@ export type HSTStonecropReturn = BaseStonecropReturn & {
|
|
|
65
65
|
hstStore: Ref<HSTNode | undefined>
|
|
66
66
|
formData: Ref<Record<string, any>>
|
|
67
67
|
resolvedSchema: Ref<SchemaTypes[]>
|
|
68
|
-
loadNestedData: (parentPath: string, childDoctype:
|
|
69
|
-
saveRecursive: (doctype:
|
|
68
|
+
loadNestedData: (parentPath: string, childDoctype: Doctype, recordId?: string) => Record<string, any>
|
|
69
|
+
saveRecursive: (doctype: Doctype, recordId: string) => Promise<Record<string, any>>
|
|
70
70
|
createNestedContext: (
|
|
71
71
|
basePath: string,
|
|
72
|
-
childDoctype:
|
|
72
|
+
childDoctype: Doctype
|
|
73
73
|
) => {
|
|
74
74
|
provideHSTPath: (fieldname: string) => string
|
|
75
75
|
handleHSTChange: (changeData: HSTChangeData) => void
|
|
76
76
|
}
|
|
77
|
+
isLoading: Ref<boolean>
|
|
78
|
+
error: Ref<Error | null>
|
|
79
|
+
resolvedDoctype: Ref<Doctype | undefined>
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
@@ -98,13 +101,13 @@ export function useStonecrop(): BaseStonecropReturn | HSTStonecropReturn
|
|
|
98
101
|
/**
|
|
99
102
|
* Unified Stonecrop composable with HST integration for a specific doctype and record
|
|
100
103
|
*
|
|
101
|
-
* @param options - Configuration with doctype and optional recordId
|
|
104
|
+
* @param options - Configuration with doctype (string slug or Doctype instance) and optional recordId
|
|
102
105
|
* @returns Stonecrop instance with full HST integration utilities
|
|
103
106
|
* @public
|
|
104
107
|
*/
|
|
105
108
|
export function useStonecrop(options: {
|
|
106
109
|
registry?: Registry
|
|
107
|
-
doctype:
|
|
110
|
+
doctype: Doctype | string
|
|
108
111
|
recordId?: string
|
|
109
112
|
}): HSTStonecropReturn
|
|
110
113
|
/**
|
|
@@ -112,7 +115,7 @@ export function useStonecrop(options: {
|
|
|
112
115
|
*/
|
|
113
116
|
export function useStonecrop(options?: {
|
|
114
117
|
registry?: Registry
|
|
115
|
-
doctype?:
|
|
118
|
+
doctype?: Doctype | string
|
|
116
119
|
recordId?: string
|
|
117
120
|
}): BaseStonecropReturn | HSTStonecropReturn {
|
|
118
121
|
if (!options) options = {}
|
|
@@ -124,20 +127,20 @@ export function useStonecrop(options?: {
|
|
|
124
127
|
const formData = ref<Record<string, any>>({})
|
|
125
128
|
|
|
126
129
|
// Use refs for router-loaded doctype to maintain reactivity
|
|
127
|
-
const routerDoctype = ref<
|
|
130
|
+
const routerDoctype = ref<Doctype | undefined>()
|
|
128
131
|
const routerRecordId = ref<string | undefined>()
|
|
129
132
|
|
|
130
133
|
// Resolved schema with nested Doctype fields expanded
|
|
131
134
|
const resolvedSchema = ref<SchemaTypes[]>([])
|
|
132
135
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
// Loading state for lazy-loaded doctypes
|
|
137
|
+
const isLoading = ref(false)
|
|
138
|
+
const error = ref<Error | null>(null)
|
|
139
|
+
const resolvedDoctype = ref<Doctype | undefined>()
|
|
140
|
+
|
|
141
|
+
// If doctype is a Doctype instance (not string), set resolved immediately
|
|
142
|
+
if (options?.doctype && typeof options.doctype !== 'string') {
|
|
143
|
+
resolvedDoctype.value = options.doctype
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
// Operation log state and methods - will be populated after stonecrop instance is created
|
|
@@ -284,7 +287,7 @@ export function useStonecrop(options?: {
|
|
|
284
287
|
? doctype.schema
|
|
285
288
|
: Array.from(doctype.schema)
|
|
286
289
|
: []
|
|
287
|
-
resolvedSchema.value = registry.resolveSchema(schemaArray
|
|
290
|
+
resolvedSchema.value = registry.resolveSchema(schemaArray)
|
|
288
291
|
}
|
|
289
292
|
|
|
290
293
|
if (recordId && recordId !== 'new') {
|
|
@@ -318,9 +321,62 @@ export function useStonecrop(options?: {
|
|
|
318
321
|
// Handle HST integration if doctype is provided explicitly
|
|
319
322
|
if (options.doctype) {
|
|
320
323
|
hstStore.value = stonecrop.value.getStore()
|
|
321
|
-
const doctype = options.doctype
|
|
322
324
|
const recordId = options.recordId
|
|
323
325
|
|
|
326
|
+
// Resolve doctype - handle string (lazy-load) or Doctype instance
|
|
327
|
+
let doctype: Doctype | undefined
|
|
328
|
+
|
|
329
|
+
if (typeof options.doctype === 'string') {
|
|
330
|
+
// String doctype - check registry first, then lazy-load
|
|
331
|
+
const doctypeSlug = options.doctype
|
|
332
|
+
isLoading.value = true
|
|
333
|
+
error.value = null
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
// Check if already in registry
|
|
337
|
+
doctype = registry.getDoctype(doctypeSlug)
|
|
338
|
+
|
|
339
|
+
if (!doctype && registry.getMeta) {
|
|
340
|
+
// Lazy-load via getMeta
|
|
341
|
+
const routeContext: RouteContext = {
|
|
342
|
+
path: `/${doctypeSlug}`,
|
|
343
|
+
segments: [doctypeSlug],
|
|
344
|
+
}
|
|
345
|
+
doctype = await registry.getMeta(routeContext)
|
|
346
|
+
if (doctype) {
|
|
347
|
+
registry.addDoctype(doctype)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!doctype) {
|
|
352
|
+
error.value = new Error(`Doctype '${doctypeSlug}' not found in registry and getMeta returned no result`)
|
|
353
|
+
}
|
|
354
|
+
} catch (e) {
|
|
355
|
+
error.value = e instanceof Error ? e : new Error(String(e))
|
|
356
|
+
} finally {
|
|
357
|
+
isLoading.value = false
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
// Doctype instance provided directly
|
|
361
|
+
doctype = options.doctype
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Set resolved doctype for consumers
|
|
365
|
+
resolvedDoctype.value = doctype
|
|
366
|
+
|
|
367
|
+
if (!doctype) {
|
|
368
|
+
// Error already set above, just return
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Resolve schema for the doctype
|
|
373
|
+
const schemaArray = doctype.schema
|
|
374
|
+
? Array.isArray(doctype.schema)
|
|
375
|
+
? doctype.schema
|
|
376
|
+
: Array.from(doctype.schema)
|
|
377
|
+
: []
|
|
378
|
+
resolvedSchema.value = registry.resolveSchema(schemaArray)
|
|
379
|
+
|
|
324
380
|
if (recordId && recordId !== 'new') {
|
|
325
381
|
const existingRecord = stonecrop.value.getRecordById(doctype, recordId)
|
|
326
382
|
if (existingRecord) {
|
|
@@ -348,7 +404,7 @@ export function useStonecrop(options?: {
|
|
|
348
404
|
|
|
349
405
|
// HST integration functions - always created but only populated when HST is available
|
|
350
406
|
const provideHSTPath = (fieldname: string, customRecordId?: string): string => {
|
|
351
|
-
const doctype =
|
|
407
|
+
const doctype = resolvedDoctype.value || routerDoctype.value
|
|
352
408
|
if (!doctype) return ''
|
|
353
409
|
|
|
354
410
|
const actualRecordId = customRecordId || options.recordId || routerRecordId.value || 'new'
|
|
@@ -356,7 +412,7 @@ export function useStonecrop(options?: {
|
|
|
356
412
|
}
|
|
357
413
|
|
|
358
414
|
const handleHSTChange = (changeData: HSTChangeData): void => {
|
|
359
|
-
const doctype =
|
|
415
|
+
const doctype = resolvedDoctype.value || routerDoctype.value
|
|
360
416
|
if (!hstStore.value || !stonecrop.value || !doctype) {
|
|
361
417
|
return
|
|
362
418
|
}
|
|
@@ -418,7 +474,7 @@ export function useStonecrop(options?: {
|
|
|
418
474
|
* @param recordId - Optional record ID to load
|
|
419
475
|
* @returns Promise resolving to the loaded or initialized data
|
|
420
476
|
*/
|
|
421
|
-
const loadNestedData = (parentPath: string, childDoctype:
|
|
477
|
+
const loadNestedData = (parentPath: string, childDoctype: Doctype, recordId?: string): Record<string, any> => {
|
|
422
478
|
if (!stonecrop.value) {
|
|
423
479
|
return initializeNewRecord(childDoctype)
|
|
424
480
|
}
|
|
@@ -450,7 +506,7 @@ export function useStonecrop(options?: {
|
|
|
450
506
|
* @param recordId - The record ID to save
|
|
451
507
|
* @returns The complete save payload
|
|
452
508
|
*/
|
|
453
|
-
const saveRecursive = (doctype:
|
|
509
|
+
const saveRecursive = (doctype: Doctype, recordId: string): Record<string, any> => {
|
|
454
510
|
if (!hstStore.value || !stonecrop.value) {
|
|
455
511
|
throw new Error('HST store not initialized')
|
|
456
512
|
}
|
|
@@ -462,9 +518,11 @@ export function useStonecrop(options?: {
|
|
|
462
518
|
const payload: Record<string, any> = { ...recordData }
|
|
463
519
|
|
|
464
520
|
// Use resolveSchema to get the full resolved tree, then walk Doctype fields
|
|
465
|
-
const schemaArray =
|
|
466
|
-
|
|
467
|
-
|
|
521
|
+
const schemaArray = doctype.schema
|
|
522
|
+
? Array.isArray(doctype.schema)
|
|
523
|
+
? doctype.schema
|
|
524
|
+
: Array.from(doctype.schema)
|
|
525
|
+
: []
|
|
468
526
|
const resolved = registry ? registry.resolveSchema(schemaArray) : schemaArray
|
|
469
527
|
const doctypeFields = resolved.filter(
|
|
470
528
|
field => 'fieldtype' in field && field.fieldtype === 'Doctype' && 'schema' in field && Array.isArray(field.schema)
|
|
@@ -487,7 +545,7 @@ export function useStonecrop(options?: {
|
|
|
487
545
|
* @param _childDoctype - The child doctype metadata (unused but kept for API consistency)
|
|
488
546
|
* @returns Object with scoped provideHSTPath and handleHSTChange
|
|
489
547
|
*/
|
|
490
|
-
const createNestedContext = (basePath: string, _childDoctype:
|
|
548
|
+
const createNestedContext = (basePath: string, _childDoctype: Doctype) => {
|
|
491
549
|
const nestedProvideHSTPath = (fieldname: string): string => {
|
|
492
550
|
return `${basePath}.${fieldname}`
|
|
493
551
|
}
|
|
@@ -543,6 +601,9 @@ export function useStonecrop(options?: {
|
|
|
543
601
|
loadNestedData,
|
|
544
602
|
saveRecursive,
|
|
545
603
|
createNestedContext,
|
|
604
|
+
isLoading,
|
|
605
|
+
error,
|
|
606
|
+
resolvedDoctype,
|
|
546
607
|
} as HSTStonecropReturn
|
|
547
608
|
} else if (!options.doctype && registry?.router) {
|
|
548
609
|
// Router-based - return HST (will be populated after mount)
|
|
@@ -557,6 +618,9 @@ export function useStonecrop(options?: {
|
|
|
557
618
|
loadNestedData,
|
|
558
619
|
saveRecursive,
|
|
559
620
|
createNestedContext,
|
|
621
|
+
isLoading,
|
|
622
|
+
error,
|
|
623
|
+
resolvedDoctype,
|
|
560
624
|
} as HSTStonecropReturn
|
|
561
625
|
}
|
|
562
626
|
|
|
@@ -570,7 +634,7 @@ export function useStonecrop(options?: {
|
|
|
570
634
|
/**
|
|
571
635
|
* Initialize new record structure based on doctype schema
|
|
572
636
|
*/
|
|
573
|
-
function initializeNewRecord(doctype:
|
|
637
|
+
function initializeNewRecord(doctype: Doctype): Record<string, any> {
|
|
574
638
|
const initialData: Record<string, any> = {}
|
|
575
639
|
|
|
576
640
|
if (!doctype.schema) {
|
|
@@ -610,7 +674,7 @@ function initializeNewRecord(doctype: DoctypeMeta): Record<string, any> {
|
|
|
610
674
|
* Setup deep reactivity between form data and HST store
|
|
611
675
|
*/
|
|
612
676
|
function setupDeepReactivity(
|
|
613
|
-
doctype:
|
|
677
|
+
doctype: Doctype,
|
|
614
678
|
recordId: string,
|
|
615
679
|
formData: Ref<Record<string, any>>,
|
|
616
680
|
hstStore: HSTNode
|
package/src/doctype.ts
CHANGED
|
@@ -1,12 +1,42 @@
|
|
|
1
|
+
import { List, Map } from 'immutable'
|
|
1
2
|
import { Component } from 'vue'
|
|
2
3
|
|
|
4
|
+
import type { SchemaTypes } from '@stonecrop/aform'
|
|
5
|
+
import type { UnknownMachineConfig } from 'xstate'
|
|
6
|
+
|
|
3
7
|
import type { ImmutableDoctype } from './types'
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
10
|
+
* Plain object representation of doctype configuration for serialization/API responses.
|
|
11
|
+
* Compatible with the DoctypeMeta type from \@stonecrop/schema.
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export type DoctypeConfig = {
|
|
15
|
+
/** Display name of the doctype */
|
|
16
|
+
name: string
|
|
17
|
+
/** URL-friendly slug (kebab-case) */
|
|
18
|
+
slug?: string
|
|
19
|
+
/** Database table name */
|
|
20
|
+
tableName?: string
|
|
21
|
+
/** Field definitions */
|
|
22
|
+
fields?: SchemaTypes[]
|
|
23
|
+
/** Workflow configuration */
|
|
24
|
+
workflow?: UnknownMachineConfig
|
|
25
|
+
/** Actions and their field triggers */
|
|
26
|
+
actions?: Record<string, string[]>
|
|
27
|
+
/** Parent doctype for inheritance */
|
|
28
|
+
inherits?: string
|
|
29
|
+
/** Doctype to use for list views */
|
|
30
|
+
listDoctype?: string
|
|
31
|
+
/** Parent doctype for child tables */
|
|
32
|
+
parentDoctype?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Doctype runtime class with Immutable.js collections for HST change tracking.
|
|
7
37
|
* @public
|
|
8
38
|
*/
|
|
9
|
-
export default class
|
|
39
|
+
export default class Doctype {
|
|
10
40
|
/**
|
|
11
41
|
* The doctype name
|
|
12
42
|
* @public
|
|
@@ -52,7 +82,7 @@ export default class DoctypeMeta {
|
|
|
52
82
|
readonly component?: Component
|
|
53
83
|
|
|
54
84
|
/**
|
|
55
|
-
* Creates a new
|
|
85
|
+
* Creates a new Doctype instance
|
|
56
86
|
* @param doctype - The doctype name
|
|
57
87
|
* @param schema - The doctype schema definition
|
|
58
88
|
* @param workflow - The doctype workflow configuration (XState machine)
|
|
@@ -73,6 +103,84 @@ export default class DoctypeMeta {
|
|
|
73
103
|
this.component = component
|
|
74
104
|
}
|
|
75
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Creates a Doctype instance from a plain configuration object.
|
|
108
|
+
* Handles conversion of arrays to Immutable.js collections internally.
|
|
109
|
+
*
|
|
110
|
+
* This is the recommended way to create a Doctype from API responses
|
|
111
|
+
* or configuration files, as it encapsulates the Immutable.js construction
|
|
112
|
+
* that the framework uses internally.
|
|
113
|
+
*
|
|
114
|
+
* @param config - Plain object with doctype configuration (typically from API response)
|
|
115
|
+
* @returns A new Doctype instance with Immutable.js collections
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* // From an API response
|
|
120
|
+
* const response = await client.getMeta({ doctype: 'plan' })
|
|
121
|
+
* const doctype = Doctype.fromObject(response)
|
|
122
|
+
* registry.addDoctype(doctype)
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* // From a configuration object
|
|
128
|
+
* const planDoctype = Doctype.fromObject({
|
|
129
|
+
* name: 'Plan',
|
|
130
|
+
* fields: [
|
|
131
|
+
* { fieldname: 'title', label: 'Title', fieldtype: 'Data' },
|
|
132
|
+
* { fieldname: 'status', label: 'Status', fieldtype: 'Data' },
|
|
133
|
+
* ],
|
|
134
|
+
* workflow: {
|
|
135
|
+
* id: 'plan',
|
|
136
|
+
* initial: 'draft',
|
|
137
|
+
* states: { draft: {}, submitted: {} }
|
|
138
|
+
* }
|
|
139
|
+
* })
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
static fromObject(config: DoctypeConfig): Doctype {
|
|
145
|
+
const schema = config.fields ? List(config.fields) : List<SchemaTypes>()
|
|
146
|
+
const actions = config.actions ? Map(config.actions) : Map<string, string[]>()
|
|
147
|
+
|
|
148
|
+
return new Doctype(config.name, schema, config.workflow, actions)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns the schema as a plain array for use with components that expect
|
|
153
|
+
* plain JavaScript arrays (e.g., AForm, ATable).
|
|
154
|
+
*
|
|
155
|
+
* @returns Array of schema fields
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* const schemaArray = doctype.getSchemaArray()
|
|
160
|
+
* // Use with AForm
|
|
161
|
+
* <AForm :schema="schemaArray" v-model:data="formData" />
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @public
|
|
165
|
+
*/
|
|
166
|
+
getSchemaArray(): SchemaTypes[] {
|
|
167
|
+
if (!this.schema) return []
|
|
168
|
+
return this.schema.toArray()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Returns the actions as a plain object for use with components that expect
|
|
173
|
+
* plain JavaScript objects.
|
|
174
|
+
*
|
|
175
|
+
* @returns Object mapping action names to field trigger arrays
|
|
176
|
+
*
|
|
177
|
+
* @public
|
|
178
|
+
*/
|
|
179
|
+
getActionsObject(): Record<string, string[]> {
|
|
180
|
+
if (!this.actions) return {}
|
|
181
|
+
return this.actions.toObject()
|
|
182
|
+
}
|
|
183
|
+
|
|
76
184
|
/**
|
|
77
185
|
* Returns the transitions available from a given workflow state, derived from the
|
|
78
186
|
* doctype's XState workflow configuration.
|
|
@@ -109,7 +217,7 @@ export default class DoctypeMeta {
|
|
|
109
217
|
*
|
|
110
218
|
* @example
|
|
111
219
|
* ```ts
|
|
112
|
-
* const doctype = new
|
|
220
|
+
* const doctype = new Doctype('TaskItem', schema, workflow, actions)
|
|
113
221
|
* console.log(doctype.slug) // 'task-item'
|
|
114
222
|
* ```
|
|
115
223
|
*
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type * from '@stonecrop/atable/types'
|
|
|
3
3
|
|
|
4
4
|
import { useStonecrop } from './composables/stonecrop'
|
|
5
5
|
import { useOperationLog, useUndoRedoShortcuts, withBatch } from './composables/operation-log'
|
|
6
|
-
import
|
|
6
|
+
import Doctype, { type DoctypeConfig } from './doctype'
|
|
7
7
|
import {
|
|
8
8
|
getGlobalTriggerEngine,
|
|
9
9
|
markOperationIrreversible,
|
|
@@ -36,7 +36,8 @@ export type { ValidationIssue, ValidationResult, ValidatorOptions } from './sche
|
|
|
36
36
|
export { ValidationSeverity } from './schema-validator'
|
|
37
37
|
|
|
38
38
|
export {
|
|
39
|
-
|
|
39
|
+
Doctype,
|
|
40
|
+
DoctypeConfig,
|
|
40
41
|
Registry,
|
|
41
42
|
Stonecrop,
|
|
42
43
|
StonecropOptions,
|
package/src/registry.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SchemaTypes } from '@stonecrop/aform'
|
|
2
2
|
import { Router } from 'vue-router'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import Doctype from './doctype'
|
|
5
5
|
import { getGlobalTriggerEngine } from './field-triggers'
|
|
6
6
|
import { RouteContext } from './types/registry'
|
|
7
7
|
|
|
@@ -24,9 +24,9 @@ export default class Registry {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* The registry property contains a collection of doctypes
|
|
27
|
-
* @see {@link
|
|
27
|
+
* @see {@link Doctype}
|
|
28
28
|
*/
|
|
29
|
-
readonly registry: Record<string,
|
|
29
|
+
readonly registry: Record<string, Doctype>
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* The Vue router instance
|
|
@@ -39,7 +39,7 @@ export default class Registry {
|
|
|
39
39
|
* @param router - Optional Vue router instance for route management
|
|
40
40
|
* @param getMeta - Optional function to fetch doctype metadata from an API
|
|
41
41
|
*/
|
|
42
|
-
constructor(router?: Router, getMeta?: (routeContext: RouteContext) =>
|
|
42
|
+
constructor(router?: Router, getMeta?: (routeContext: RouteContext) => Doctype | Promise<Doctype>) {
|
|
43
43
|
if (Registry._root) {
|
|
44
44
|
return Registry._root
|
|
45
45
|
}
|
|
@@ -52,17 +52,17 @@ export default class Registry {
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* The getMeta function fetches doctype metadata from an API based on route context
|
|
55
|
-
* @see {@link
|
|
55
|
+
* @see {@link Doctype}
|
|
56
56
|
*/
|
|
57
|
-
getMeta?: (routeContext: RouteContext) =>
|
|
57
|
+
getMeta?: (routeContext: RouteContext) => Doctype | Promise<Doctype>
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Get doctype metadata
|
|
61
61
|
* @param doctype - The doctype to fetch metadata for
|
|
62
62
|
* @returns The doctype metadata
|
|
63
|
-
* @see {@link
|
|
63
|
+
* @see {@link Doctype}
|
|
64
64
|
*/
|
|
65
|
-
addDoctype(doctype:
|
|
65
|
+
addDoctype(doctype: Doctype) {
|
|
66
66
|
if (!(doctype.slug in this.registry)) {
|
|
67
67
|
this.registry[doctype.slug] = doctype
|
|
68
68
|
}
|
|
@@ -153,7 +153,7 @@ export default class Registry {
|
|
|
153
153
|
'options' in field &&
|
|
154
154
|
typeof field.options === 'string'
|
|
155
155
|
) {
|
|
156
|
-
const doctypeSlug = field.options
|
|
156
|
+
const doctypeSlug = field.options
|
|
157
157
|
|
|
158
158
|
// Circular reference protection
|
|
159
159
|
if (seen.has(doctypeSlug)) {
|
|
@@ -275,10 +275,10 @@ export default class Registry {
|
|
|
275
275
|
/**
|
|
276
276
|
* Get a registered doctype by slug
|
|
277
277
|
* @param slug - The doctype slug to look up
|
|
278
|
-
* @returns The
|
|
278
|
+
* @returns The Doctype instance if found, or undefined
|
|
279
279
|
* @public
|
|
280
280
|
*/
|
|
281
|
-
getDoctype(slug: string):
|
|
281
|
+
getDoctype(slug: string): Doctype | undefined {
|
|
282
282
|
return this.registry[slug]
|
|
283
283
|
}
|
|
284
284
|
|
package/src/stonecrop.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DataClient } from '@stonecrop/schema'
|
|
2
2
|
import { reactive } from 'vue'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import Doctype from './doctype'
|
|
5
5
|
import Registry from './registry'
|
|
6
6
|
import { createHST, type HSTNode } from './stores/hst'
|
|
7
7
|
import { useOperationLogStore } from './stores/operation-log'
|
|
@@ -119,7 +119,7 @@ export class Stonecrop {
|
|
|
119
119
|
// Extend Registry.addDoctype to auto-create HST store sections
|
|
120
120
|
const originalAddDoctype = this.registry.addDoctype.bind(this.registry)
|
|
121
121
|
|
|
122
|
-
this.registry.addDoctype = (doctype:
|
|
122
|
+
this.registry.addDoctype = (doctype: Doctype) => {
|
|
123
123
|
// Call original method
|
|
124
124
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
125
125
|
originalAddDoctype(doctype)
|
|
@@ -136,7 +136,7 @@ export class Stonecrop {
|
|
|
136
136
|
* @param doctype - The doctype to get records for
|
|
137
137
|
* @returns HST node containing records hash
|
|
138
138
|
*/
|
|
139
|
-
records(doctype: string |
|
|
139
|
+
records(doctype: string | Doctype): HSTNode {
|
|
140
140
|
const slug = typeof doctype === 'string' ? doctype : doctype.slug
|
|
141
141
|
this.ensureDoctypeExists(slug)
|
|
142
142
|
return this.hstStore.getNode(slug)
|
|
@@ -148,7 +148,7 @@ export class Stonecrop {
|
|
|
148
148
|
* @param recordId - The record ID
|
|
149
149
|
* @param recordData - The record data
|
|
150
150
|
*/
|
|
151
|
-
addRecord(doctype: string |
|
|
151
|
+
addRecord(doctype: string | Doctype, recordId: string, recordData: any): void {
|
|
152
152
|
const slug = typeof doctype === 'string' ? doctype : doctype.slug
|
|
153
153
|
|
|
154
154
|
this.ensureDoctypeExists(slug)
|
|
@@ -163,7 +163,7 @@ export class Stonecrop {
|
|
|
163
163
|
* @param recordId - The record ID
|
|
164
164
|
* @returns HST node for the record or undefined
|
|
165
165
|
*/
|
|
166
|
-
getRecordById(doctype: string |
|
|
166
|
+
getRecordById(doctype: string | Doctype, recordId: string): HSTNode | undefined {
|
|
167
167
|
const slug = typeof doctype === 'string' ? doctype : doctype.slug
|
|
168
168
|
this.ensureDoctypeExists(slug)
|
|
169
169
|
|
|
@@ -188,7 +188,7 @@ export class Stonecrop {
|
|
|
188
188
|
* @param doctype - The doctype
|
|
189
189
|
* @param recordId - The record ID
|
|
190
190
|
*/
|
|
191
|
-
removeRecord(doctype: string |
|
|
191
|
+
removeRecord(doctype: string | Doctype, recordId: string): void {
|
|
192
192
|
const slug = typeof doctype === 'string' ? doctype : doctype.slug
|
|
193
193
|
this.ensureDoctypeExists(slug)
|
|
194
194
|
|
|
@@ -203,7 +203,7 @@ export class Stonecrop {
|
|
|
203
203
|
* @param doctype - The doctype
|
|
204
204
|
* @returns Array of record IDs
|
|
205
205
|
*/
|
|
206
|
-
getRecordIds(doctype: string |
|
|
206
|
+
getRecordIds(doctype: string | Doctype): string[] {
|
|
207
207
|
const slug = typeof doctype === 'string' ? doctype : doctype.slug
|
|
208
208
|
this.ensureDoctypeExists(slug)
|
|
209
209
|
|
|
@@ -219,7 +219,7 @@ export class Stonecrop {
|
|
|
219
219
|
* Clear all records for a doctype
|
|
220
220
|
* @param doctype - The doctype
|
|
221
221
|
*/
|
|
222
|
-
clearRecords(doctype: string |
|
|
222
|
+
clearRecords(doctype: string | Doctype): void {
|
|
223
223
|
const slug = typeof doctype === 'string' ? doctype : doctype.slug
|
|
224
224
|
this.ensureDoctypeExists(slug)
|
|
225
225
|
|
|
@@ -234,7 +234,7 @@ export class Stonecrop {
|
|
|
234
234
|
* Setup method for doctype initialization
|
|
235
235
|
* @param doctype - The doctype to setup
|
|
236
236
|
*/
|
|
237
|
-
setup(doctype:
|
|
237
|
+
setup(doctype: Doctype): void {
|
|
238
238
|
// Ensure doctype exists in store
|
|
239
239
|
this.ensureDoctypeExists(doctype.slug)
|
|
240
240
|
}
|
|
@@ -246,7 +246,7 @@ export class Stonecrop {
|
|
|
246
246
|
* @param action - The action to run
|
|
247
247
|
* @param args - Action arguments (typically record IDs)
|
|
248
248
|
*/
|
|
249
|
-
runAction(doctype:
|
|
249
|
+
runAction(doctype: Doctype, action: string, args?: any[]): void {
|
|
250
250
|
const registry = this.registry.registry[doctype.slug]
|
|
251
251
|
const actions = registry?.actions?.get(action)
|
|
252
252
|
const recordIds = Array.isArray(args) ? args.filter((arg): arg is string => typeof arg === 'string') : undefined
|
|
@@ -285,7 +285,7 @@ export class Stonecrop {
|
|
|
285
285
|
* @param doctype - The doctype
|
|
286
286
|
* @throws Error if no data client has been configured
|
|
287
287
|
*/
|
|
288
|
-
async getRecords(doctype:
|
|
288
|
+
async getRecords(doctype: Doctype): Promise<void> {
|
|
289
289
|
if (!this._client) {
|
|
290
290
|
throw new Error(
|
|
291
291
|
'No data client configured. Call setClient() with a DataClient implementation ' +
|
|
@@ -309,7 +309,7 @@ export class Stonecrop {
|
|
|
309
309
|
* @param recordId - The record ID
|
|
310
310
|
* @throws Error if no data client has been configured
|
|
311
311
|
*/
|
|
312
|
-
async getRecord(doctype:
|
|
312
|
+
async getRecord(doctype: Doctype, recordId: string): Promise<void> {
|
|
313
313
|
if (!this._client) {
|
|
314
314
|
throw new Error(
|
|
315
315
|
'No data client configured. Call setClient() with a DataClient implementation ' +
|
|
@@ -335,7 +335,7 @@ export class Stonecrop {
|
|
|
335
335
|
* @throws Error if no data client has been configured
|
|
336
336
|
*/
|
|
337
337
|
async dispatchAction(
|
|
338
|
-
doctype:
|
|
338
|
+
doctype: Doctype,
|
|
339
339
|
action: string,
|
|
340
340
|
args?: unknown[]
|
|
341
341
|
): Promise<{ success: boolean; data: unknown; error: string | null }> {
|
|
@@ -386,13 +386,13 @@ export class Stonecrop {
|
|
|
386
386
|
* empty the doctype's declared `workflow.initial` state is used as the fallback,
|
|
387
387
|
* giving callers a reliable state name without having to duplicate that logic.
|
|
388
388
|
*
|
|
389
|
-
* @param doctype - The doctype slug or
|
|
389
|
+
* @param doctype - The doctype slug or Doctype instance
|
|
390
390
|
* @param recordId - The record identifier
|
|
391
391
|
* @returns The current state name, or an empty string if the doctype has no workflow
|
|
392
392
|
*
|
|
393
393
|
* @public
|
|
394
394
|
*/
|
|
395
|
-
getRecordState(doctype: string |
|
|
395
|
+
getRecordState(doctype: string | Doctype, recordId: string): string {
|
|
396
396
|
const slug = typeof doctype === 'string' ? doctype : doctype.slug
|
|
397
397
|
const meta = this.registry.getDoctype(slug)
|
|
398
398
|
if (!meta?.workflow) return ''
|
package/src/types/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Component } from 'vue'
|
|
|
5
5
|
import type { Router } from 'vue-router'
|
|
6
6
|
import type { AnyStateNodeConfig, UnknownMachineConfig } from 'xstate'
|
|
7
7
|
|
|
8
|
-
import type
|
|
8
|
+
import type Doctype from '../doctype'
|
|
9
9
|
import Registry from '../registry'
|
|
10
10
|
import { Stonecrop } from '../stonecrop'
|
|
11
11
|
import type { RouteContext } from './registry'
|
|
@@ -47,7 +47,7 @@ export type Schema = {
|
|
|
47
47
|
export type InstallOptions = {
|
|
48
48
|
router?: Router
|
|
49
49
|
components?: Record<string, Component>
|
|
50
|
-
getMeta?: (routeContext: RouteContext) =>
|
|
50
|
+
getMeta?: (routeContext: RouteContext) => Doctype | Promise<Doctype>
|
|
51
51
|
/**
|
|
52
52
|
* Data client for fetching doctype metadata and records.
|
|
53
53
|
* Use \@stonecrop/graphql-client's StonecropClient for GraphQL backends,
|