@stonecrop/stonecrop 0.10.7 → 0.10.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/stonecrop",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
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.7"
37
+ "@stonecrop/schema": "0.10.9"
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/atable": "0.10.7",
64
- "@stonecrop/aform": "0.10.7",
63
+ "@stonecrop/aform": "0.10.9",
64
+ "@stonecrop/atable": "0.10.9",
65
65
  "stonecrop-rig": "0.7.0"
66
66
  },
67
67
  "description": "Schema-driven framework with XState workflows and HST state management",
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 XState workflow configuration.
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 states = this.workflow?.states
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
- const initialState =
404
- typeof meta.workflow.initial === 'string'
405
- ? meta.workflow.initial
406
- : Object.keys(meta.workflow.states ?? {})[0] ?? ''
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
  }
@@ -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