@stonecrop/stonecrop 0.10.6 → 0.10.8
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 +30 -1
- package/dist/composables/stonecrop.js +69 -12
- package/dist/doctype.js +51 -2
- package/dist/src/composables/stonecrop.d.ts +5 -2
- package/dist/src/composables/stonecrop.d.ts.map +1 -1
- package/dist/src/doctype.d.ts +28 -4
- package/dist/src/doctype.d.ts.map +1 -1
- package/dist/src/stonecrop.d.ts.map +1 -1
- package/dist/src/types/index.d.ts +3 -3
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/stonecrop.d.ts +34 -7
- package/dist/stonecrop.js +1611 -1551
- package/dist/stonecrop.js.map +1 -1
- package/package.json +4 -4
- package/src/composables/stonecrop.ts +86 -18
- package/src/doctype.ts +67 -6
- package/src/stonecrop.ts +15 -5
- package/src/types/index.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/stonecrop",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.8",
|
|
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.8"
|
|
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/aform": "0.10.
|
|
64
|
-
"@stonecrop/atable": "0.10.
|
|
63
|
+
"@stonecrop/aform": "0.10.8",
|
|
64
|
+
"@stonecrop/atable": "0.10.8",
|
|
65
65
|
"stonecrop-rig": "0.7.0"
|
|
66
66
|
},
|
|
67
67
|
"description": "Schema-driven framework with XState workflows and HST state management",
|
|
@@ -74,6 +74,9 @@ export type HSTStonecropReturn = BaseStonecropReturn & {
|
|
|
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,17 +101,21 @@ 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
|
-
export function useStonecrop(options: {
|
|
108
|
+
export function useStonecrop(options: {
|
|
109
|
+
registry?: Registry
|
|
110
|
+
doctype: Doctype | string
|
|
111
|
+
recordId?: string
|
|
112
|
+
}): HSTStonecropReturn
|
|
106
113
|
/**
|
|
107
114
|
* @public
|
|
108
115
|
*/
|
|
109
116
|
export function useStonecrop(options?: {
|
|
110
117
|
registry?: Registry
|
|
111
|
-
doctype?: Doctype
|
|
118
|
+
doctype?: Doctype | string
|
|
112
119
|
recordId?: string
|
|
113
120
|
}): BaseStonecropReturn | HSTStonecropReturn {
|
|
114
121
|
if (!options) options = {}
|
|
@@ -126,14 +133,14 @@ export function useStonecrop(options?: {
|
|
|
126
133
|
// Resolved schema with nested Doctype fields expanded
|
|
127
134
|
const resolvedSchema = ref<SchemaTypes[]>([])
|
|
128
135
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
// Operation log state and methods - will be populated after stonecrop instance is created
|
|
@@ -280,7 +287,7 @@ export function useStonecrop(options?: {
|
|
|
280
287
|
? doctype.schema
|
|
281
288
|
: Array.from(doctype.schema)
|
|
282
289
|
: []
|
|
283
|
-
resolvedSchema.value = registry.resolveSchema(schemaArray
|
|
290
|
+
resolvedSchema.value = registry.resolveSchema(schemaArray)
|
|
284
291
|
}
|
|
285
292
|
|
|
286
293
|
if (recordId && recordId !== 'new') {
|
|
@@ -314,9 +321,62 @@ export function useStonecrop(options?: {
|
|
|
314
321
|
// Handle HST integration if doctype is provided explicitly
|
|
315
322
|
if (options.doctype) {
|
|
316
323
|
hstStore.value = stonecrop.value.getStore()
|
|
317
|
-
const doctype = options.doctype
|
|
318
324
|
const recordId = options.recordId
|
|
319
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
|
+
|
|
320
380
|
if (recordId && recordId !== 'new') {
|
|
321
381
|
const existingRecord = stonecrop.value.getRecordById(doctype, recordId)
|
|
322
382
|
if (existingRecord) {
|
|
@@ -344,7 +404,7 @@ export function useStonecrop(options?: {
|
|
|
344
404
|
|
|
345
405
|
// HST integration functions - always created but only populated when HST is available
|
|
346
406
|
const provideHSTPath = (fieldname: string, customRecordId?: string): string => {
|
|
347
|
-
const doctype =
|
|
407
|
+
const doctype = resolvedDoctype.value || routerDoctype.value
|
|
348
408
|
if (!doctype) return ''
|
|
349
409
|
|
|
350
410
|
const actualRecordId = customRecordId || options.recordId || routerRecordId.value || 'new'
|
|
@@ -352,7 +412,7 @@ export function useStonecrop(options?: {
|
|
|
352
412
|
}
|
|
353
413
|
|
|
354
414
|
const handleHSTChange = (changeData: HSTChangeData): void => {
|
|
355
|
-
const doctype =
|
|
415
|
+
const doctype = resolvedDoctype.value || routerDoctype.value
|
|
356
416
|
if (!hstStore.value || !stonecrop.value || !doctype) {
|
|
357
417
|
return
|
|
358
418
|
}
|
|
@@ -458,9 +518,11 @@ export function useStonecrop(options?: {
|
|
|
458
518
|
const payload: Record<string, any> = { ...recordData }
|
|
459
519
|
|
|
460
520
|
// Use resolveSchema to get the full resolved tree, then walk Doctype fields
|
|
461
|
-
const schemaArray =
|
|
462
|
-
|
|
463
|
-
|
|
521
|
+
const schemaArray = doctype.schema
|
|
522
|
+
? Array.isArray(doctype.schema)
|
|
523
|
+
? doctype.schema
|
|
524
|
+
: Array.from(doctype.schema)
|
|
525
|
+
: []
|
|
464
526
|
const resolved = registry ? registry.resolveSchema(schemaArray) : schemaArray
|
|
465
527
|
const doctypeFields = resolved.filter(
|
|
466
528
|
field => 'fieldtype' in field && field.fieldtype === 'Doctype' && 'schema' in field && Array.isArray(field.schema)
|
|
@@ -539,6 +601,9 @@ export function useStonecrop(options?: {
|
|
|
539
601
|
loadNestedData,
|
|
540
602
|
saveRecursive,
|
|
541
603
|
createNestedContext,
|
|
604
|
+
isLoading,
|
|
605
|
+
error,
|
|
606
|
+
resolvedDoctype,
|
|
542
607
|
} as HSTStonecropReturn
|
|
543
608
|
} else if (!options.doctype && registry?.router) {
|
|
544
609
|
// Router-based - return HST (will be populated after mount)
|
|
@@ -553,6 +618,9 @@ export function useStonecrop(options?: {
|
|
|
553
618
|
loadNestedData,
|
|
554
619
|
saveRecursive,
|
|
555
620
|
createNestedContext,
|
|
621
|
+
isLoading,
|
|
622
|
+
error,
|
|
623
|
+
resolvedDoctype,
|
|
556
624
|
} as HSTStonecropReturn
|
|
557
625
|
}
|
|
558
626
|
|
package/src/doctype.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { SchemaTypes } from '@stonecrop/aform'
|
|
2
|
+
import type { WorkflowMeta } from '@stonecrop/schema'
|
|
1
3
|
import { List, Map } from 'immutable'
|
|
2
4
|
import { Component } from 'vue'
|
|
3
|
-
|
|
4
|
-
import type { SchemaTypes } from '@stonecrop/aform'
|
|
5
5
|
import type { UnknownMachineConfig } from 'xstate'
|
|
6
6
|
|
|
7
7
|
import type { ImmutableDoctype } from './types'
|
|
@@ -20,8 +20,8 @@ export type DoctypeConfig = {
|
|
|
20
20
|
tableName?: string
|
|
21
21
|
/** Field definitions */
|
|
22
22
|
fields?: SchemaTypes[]
|
|
23
|
-
/** Workflow configuration */
|
|
24
|
-
workflow?: UnknownMachineConfig
|
|
23
|
+
/** Workflow configuration (XState format or simple WorkflowMeta) */
|
|
24
|
+
workflow?: UnknownMachineConfig | WorkflowMeta
|
|
25
25
|
/** Actions and their field triggers */
|
|
26
26
|
actions?: Record<string, string[]>
|
|
27
27
|
/** Parent doctype for inheritance */
|
|
@@ -183,7 +183,7 @@ export default class Doctype {
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* Returns the transitions available from a given workflow state, derived from the
|
|
186
|
-
* doctype's
|
|
186
|
+
* doctype's workflow configuration. Supports both XState format and WorkflowMeta format.
|
|
187
187
|
*
|
|
188
188
|
* @param currentState - The state name to read transitions from
|
|
189
189
|
* @returns Array of transition descriptors with `name` and `targetState`
|
|
@@ -197,7 +197,34 @@ export default class Doctype {
|
|
|
197
197
|
* @public
|
|
198
198
|
*/
|
|
199
199
|
getAvailableTransitions(currentState: string): Array<{ name: string; targetState: string }> {
|
|
200
|
-
const
|
|
200
|
+
const workflow = this.workflow
|
|
201
|
+
if (!workflow) return []
|
|
202
|
+
|
|
203
|
+
// Check if this is WorkflowMeta format (states is an array) or XState format (states is an object)
|
|
204
|
+
if (Array.isArray(workflow.states)) {
|
|
205
|
+
// WorkflowMeta format: validate state exists and filter actions by allowedStates
|
|
206
|
+
const states = workflow.states
|
|
207
|
+
if (!states.includes(currentState)) return []
|
|
208
|
+
|
|
209
|
+
const actions = (workflow as WorkflowMeta).actions
|
|
210
|
+
if (!actions) return []
|
|
211
|
+
|
|
212
|
+
return Object.entries(actions)
|
|
213
|
+
.filter(([, actionDef]) => {
|
|
214
|
+
const allowedStates = actionDef.allowedStates
|
|
215
|
+
// If no allowedStates specified, action is available in all valid states
|
|
216
|
+
if (!allowedStates || allowedStates.length === 0) return true
|
|
217
|
+
return allowedStates.includes(currentState)
|
|
218
|
+
})
|
|
219
|
+
.map(([name]) => ({
|
|
220
|
+
name,
|
|
221
|
+
// WorkflowMeta doesn't define target states - transitions are handled server-side
|
|
222
|
+
targetState: currentState,
|
|
223
|
+
}))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// XState format: use the on property of the state
|
|
227
|
+
const states = workflow.states
|
|
201
228
|
if (!states) return []
|
|
202
229
|
const stateConfig = states[currentState]
|
|
203
230
|
if (!stateConfig?.on) return []
|
|
@@ -207,6 +234,40 @@ export default class Doctype {
|
|
|
207
234
|
}))
|
|
208
235
|
}
|
|
209
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Returns metadata for a specific action, if available.
|
|
239
|
+
* Only works with WorkflowMeta format; returns undefined for XState format.
|
|
240
|
+
*
|
|
241
|
+
* @param actionName - The action name to get metadata for
|
|
242
|
+
* @returns Action metadata or undefined
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* const actionMeta = doctype.getActionMeta('submit')
|
|
247
|
+
* // { label: 'Submit', handler: 'plan:submit', allowedStates: ['draft'] }
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* @public
|
|
251
|
+
*/
|
|
252
|
+
getActionMeta(
|
|
253
|
+
actionName: string
|
|
254
|
+
):
|
|
255
|
+
| {
|
|
256
|
+
label: string
|
|
257
|
+
handler: string
|
|
258
|
+
requiredFields?: string[]
|
|
259
|
+
allowedStates?: string[]
|
|
260
|
+
confirm?: boolean
|
|
261
|
+
args?: Record<string, unknown>
|
|
262
|
+
}
|
|
263
|
+
| undefined {
|
|
264
|
+
const workflow = this.workflow
|
|
265
|
+
if (!workflow || !Array.isArray(workflow.states)) return undefined
|
|
266
|
+
|
|
267
|
+
const actions = (workflow as WorkflowMeta).actions
|
|
268
|
+
return actions?.[actionName]
|
|
269
|
+
}
|
|
270
|
+
|
|
210
271
|
/**
|
|
211
272
|
* Converts the registered doctype string to a slug (kebab-case). The following conversions are made:
|
|
212
273
|
* - It replaces camelCase and PascalCase with kebab-case strings
|
package/src/stonecrop.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataClient } from '@stonecrop/schema'
|
|
1
|
+
import type { DataClient, WorkflowMeta } from '@stonecrop/schema'
|
|
2
2
|
import { reactive } from 'vue'
|
|
3
3
|
|
|
4
4
|
import Doctype from './doctype'
|
|
@@ -400,10 +400,20 @@ export class Stonecrop {
|
|
|
400
400
|
const record = this.getRecordById(slug, recordId)
|
|
401
401
|
const status = record?.get('status') as string | undefined
|
|
402
402
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
// Handle both XState format and WorkflowMeta format
|
|
404
|
+
const workflow = meta.workflow
|
|
405
|
+
let initialState: string
|
|
406
|
+
|
|
407
|
+
if (Array.isArray(workflow.states)) {
|
|
408
|
+
// WorkflowMeta format: states is a string array
|
|
409
|
+
initialState = workflow.states[0] ?? ''
|
|
410
|
+
} else {
|
|
411
|
+
// XState format: states is an object, use initial or first key
|
|
412
|
+
initialState =
|
|
413
|
+
typeof (workflow as { initial?: unknown }).initial === 'string'
|
|
414
|
+
? (workflow as { initial: string }).initial
|
|
415
|
+
: Object.keys(workflow.states ?? {})[0] ?? ''
|
|
416
|
+
}
|
|
407
417
|
|
|
408
418
|
return status || initialState
|
|
409
419
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataClient } from '@stonecrop/schema'
|
|
1
|
+
import type { DataClient, WorkflowMeta } from '@stonecrop/schema'
|
|
2
2
|
import type { SchemaTypes } from '@stonecrop/aform'
|
|
3
3
|
import { List, Map } from 'immutable'
|
|
4
4
|
import type { Component } from 'vue'
|
|
@@ -16,7 +16,7 @@ import type { RouteContext } from './registry'
|
|
|
16
16
|
*/
|
|
17
17
|
export type ImmutableDoctype = {
|
|
18
18
|
readonly schema?: List<SchemaTypes> // TODO: allow schema to be a function
|
|
19
|
-
readonly workflow?: UnknownMachineConfig | AnyStateNodeConfig
|
|
19
|
+
readonly workflow?: UnknownMachineConfig | AnyStateNodeConfig | WorkflowMeta
|
|
20
20
|
readonly actions?: Map<string, string[]>
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -27,7 +27,7 @@ export type ImmutableDoctype = {
|
|
|
27
27
|
export type MutableDoctype = {
|
|
28
28
|
doctype?: string
|
|
29
29
|
schema?: SchemaTypes[] // TODO: allow schema to be a function
|
|
30
|
-
workflow?: UnknownMachineConfig | AnyStateNodeConfig
|
|
30
|
+
workflow?: UnknownMachineConfig | AnyStateNodeConfig | WorkflowMeta
|
|
31
31
|
actions?: Record<string, string[]>
|
|
32
32
|
}
|
|
33
33
|
|