@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/stonecrop",
3
- "version": "0.10.5",
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.5"
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/aform": "0.10.5",
64
- "@stonecrop/atable": "0.10.5",
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 DoctypeMeta from '../doctype'
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: DoctypeMeta, recordId?: string) => Record<string, any>
69
- saveRecursive: (doctype: DoctypeMeta, recordId: string) => Promise<Record<string, any>>
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: DoctypeMeta
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: DoctypeMeta
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?: DoctypeMeta
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<DoctypeMeta | undefined>()
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
- // Auto-resolve schema when doctype is available
134
- if (options.doctype && registry) {
135
- const schemaArray = options.doctype.schema
136
- ? Array.isArray(options.doctype.schema)
137
- ? options.doctype.schema
138
- : Array.from(options.doctype.schema)
139
- : []
140
- resolvedSchema.value = registry.resolveSchema(schemaArray as SchemaTypes[])
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 as SchemaTypes[])
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 = options.doctype || routerDoctype.value
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 = options.doctype || routerDoctype.value
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: DoctypeMeta, recordId?: string): Record<string, any> => {
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: DoctypeMeta, recordId: string): Record<string, any> => {
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
- doctype.schema ? (Array.isArray(doctype.schema) ? doctype.schema : Array.from(doctype.schema)) : []
467
- ) as SchemaTypes[]
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: DoctypeMeta) => {
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: DoctypeMeta): Record<string, any> {
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: DoctypeMeta,
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
- * Doctype Meta class
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 DoctypeMeta {
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 DoctypeMeta instance
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 DoctypeMeta('TaskItem', schema, workflow, actions
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 DoctypeMeta from './doctype'
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
- DoctypeMeta,
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 DoctypeMeta from './doctype'
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 DoctypeMeta}
27
+ * @see {@link Doctype}
28
28
  */
29
- readonly registry: Record<string, DoctypeMeta>
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) => DoctypeMeta | Promise<DoctypeMeta>) {
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 DoctypeMeta}
55
+ * @see {@link Doctype}
56
56
  */
57
- getMeta?: (routeContext: RouteContext) => DoctypeMeta | Promise<DoctypeMeta>
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 DoctypeMeta}
63
+ * @see {@link Doctype}
64
64
  */
65
- addDoctype(doctype: DoctypeMeta) {
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 as string
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 DoctypeMeta instance if found, or undefined
278
+ * @returns The Doctype instance if found, or undefined
279
279
  * @public
280
280
  */
281
- getDoctype(slug: string): DoctypeMeta | undefined {
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 DoctypeMeta from './doctype'
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: DoctypeMeta) => {
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 | DoctypeMeta): HSTNode {
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 | DoctypeMeta, recordId: string, recordData: any): void {
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 | DoctypeMeta, recordId: string): HSTNode | undefined {
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 | DoctypeMeta, recordId: string): void {
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 | DoctypeMeta): 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 | DoctypeMeta): void {
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: DoctypeMeta): void {
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: DoctypeMeta, action: string, args?: any[]): void {
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: DoctypeMeta): Promise<void> {
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: DoctypeMeta, recordId: string): Promise<void> {
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: DoctypeMeta,
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 DoctypeMeta instance
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 | DoctypeMeta, recordId: string): 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 ''
@@ -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 DoctypeMeta from '../doctype'
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) => DoctypeMeta | Promise<DoctypeMeta>
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,