@stonecrop/stonecrop 0.10.0 → 0.10.2

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.
@@ -409,7 +409,6 @@ export const useOperationLogStore = defineStore('hst-operation-log', () => {
409
409
  return;
410
410
  broadcastChannel = new BroadcastChannel('stonecrop-operation-log');
411
411
  broadcastChannel.addEventListener('message', (event) => {
412
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
413
412
  const rawMessage = event.data;
414
413
  if (!rawMessage || typeof rawMessage !== 'object')
415
414
  return;
@@ -478,9 +477,7 @@ export const useOperationLogStore = defineStore('hst-operation-log', () => {
478
477
  serializer: {
479
478
  read: (v) => {
480
479
  try {
481
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
482
480
  const data = JSON.parse(v);
483
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
484
481
  return data;
485
482
  }
486
483
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/stonecrop",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": {
@@ -59,8 +59,8 @@
59
59
  "vue-router": "^5.0.2",
60
60
  "vite": "^7.3.1",
61
61
  "vitest": "^4.0.18",
62
- "@stonecrop/aform": "0.10.0",
63
- "@stonecrop/atable": "0.10.0",
62
+ "@stonecrop/aform": "0.10.2",
63
+ "@stonecrop/atable": "0.10.2",
64
64
  "stonecrop-rig": "0.7.0"
65
65
  },
66
66
  "description": "Schema-driven framework with XState workflows and HST state management",
@@ -1,6 +1,6 @@
1
1
  import { useMagicKeys, whenever } from '@vueuse/core'
2
2
  import { storeToRefs } from 'pinia'
3
- import { inject } from 'vue'
3
+ import { getCurrentInstance, inject } from 'vue'
4
4
 
5
5
  import type { HSTNode } from '../stores/hst'
6
6
  import { useOperationLogStore } from '../stores/operation-log'
@@ -32,9 +32,12 @@ import type { OperationLogConfig } from '../types/operation-log'
32
32
  * @public
33
33
  */
34
34
  export function useOperationLog(config?: Partial<OperationLogConfig>) {
35
- // Try to use the injected store from the Stonecrop plugin first
36
- // This ensures we use the same Pinia instance as the app
37
- const injectedStore = inject<ReturnType<typeof useOperationLogStore> | undefined>('$operationLogStore', undefined)
35
+ // inject() is only valid inside a component setup() context. When this
36
+ // composable is called outside one (e.g. directly in test bodies or plain
37
+ // scripts) skip the injection entirely and fall back to the Pinia store.
38
+ const injectedStore = getCurrentInstance()
39
+ ? inject<ReturnType<typeof useOperationLogStore> | undefined>('$operationLogStore', undefined)
40
+ : undefined
38
41
  const store = injectedStore || useOperationLogStore()
39
42
 
40
43
  // Apply configuration if provided
package/src/doctype.ts CHANGED
@@ -64,6 +64,32 @@ export default class DoctypeMeta {
64
64
  this.component = component
65
65
  }
66
66
 
67
+ /**
68
+ * Returns the transitions available from a given workflow state, derived from the
69
+ * doctype's XState workflow configuration.
70
+ *
71
+ * @param currentState - The state name to read transitions from
72
+ * @returns Array of transition descriptors with `name` and `targetState`
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const transitions = doctype.getAvailableTransitions('draft')
77
+ * // [{ name: 'SUBMIT', targetState: 'submitted' }]
78
+ * ```
79
+ *
80
+ * @public
81
+ */
82
+ getAvailableTransitions(currentState: string): Array<{ name: string; targetState: string }> {
83
+ const states = this.workflow?.states
84
+ if (!states) return []
85
+ const stateConfig = states[currentState]
86
+ if (!stateConfig?.on) return []
87
+ return Object.entries(stateConfig.on).map(([name, target]) => ({
88
+ name,
89
+ targetState: typeof target === 'string' ? target : 'unknown',
90
+ }))
91
+ }
92
+
67
93
  /**
68
94
  * Converts the registered doctype string to a slug (kebab-case). The following conversions are made:
69
95
  * - It replaces camelCase and PascalCase with kebab-case strings
package/src/registry.ts CHANGED
@@ -272,6 +272,16 @@ export default class Registry {
272
272
  return record
273
273
  }
274
274
 
275
+ /**
276
+ * Get a registered doctype by slug
277
+ * @param slug - The doctype slug to look up
278
+ * @returns The DoctypeMeta instance if found, or undefined
279
+ * @public
280
+ */
281
+ getDoctype(slug: string): DoctypeMeta | undefined {
282
+ return this.registry[slug]
283
+ }
284
+
275
285
  // TODO: should we allow clearing the registry at all?
276
286
  // clear() {
277
287
  // this.registry = {}
package/src/stonecrop.ts CHANGED
@@ -290,4 +290,33 @@ export class Stonecrop {
290
290
  getStore(): HSTNode {
291
291
  return this.hstStore
292
292
  }
293
+
294
+ /**
295
+ * Determine the current workflow state for a record.
296
+ *
297
+ * Reads the record's `status` field from the HST store. If the field is absent or
298
+ * empty the doctype's declared `workflow.initial` state is used as the fallback,
299
+ * giving callers a reliable state name without having to duplicate that logic.
300
+ *
301
+ * @param doctype - The doctype slug or DoctypeMeta instance
302
+ * @param recordId - The record identifier
303
+ * @returns The current state name, or an empty string if the doctype has no workflow
304
+ *
305
+ * @public
306
+ */
307
+ getRecordState(doctype: string | DoctypeMeta, recordId: string): string {
308
+ const slug = typeof doctype === 'string' ? doctype : doctype.slug
309
+ const meta = this.registry.getDoctype(slug)
310
+ if (!meta?.workflow) return ''
311
+
312
+ const record = this.getRecordById(slug, recordId)
313
+ const status = record?.get('status') as string | undefined
314
+
315
+ const initialState =
316
+ typeof meta.workflow.initial === 'string'
317
+ ? meta.workflow.initial
318
+ : Object.keys(meta.workflow.states ?? {})[0] ?? ''
319
+
320
+ return status || initialState
321
+ }
293
322
  }
@@ -491,7 +491,6 @@ export const useOperationLogStore = defineStore('hst-operation-log', () => {
491
491
  broadcastChannel = new BroadcastChannel('stonecrop-operation-log')
492
492
 
493
493
  broadcastChannel.addEventListener('message', (event: MessageEvent) => {
494
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
495
494
  const rawMessage = event.data
496
495
 
497
496
  if (!rawMessage || typeof rawMessage !== 'object') return
@@ -572,9 +571,7 @@ export const useOperationLogStore = defineStore('hst-operation-log', () => {
572
571
  serializer: {
573
572
  read: (v: string) => {
574
573
  try {
575
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
576
574
  const data = JSON.parse(v)
577
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
578
575
  return data
579
576
  } catch {
580
577
  return null