@stonecrop/stonecrop 0.10.16 → 0.11.0

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.
Files changed (117) hide show
  1. package/README.md +72 -29
  2. package/dist/composable.js +1 -0
  3. package/dist/composables/lazy-link.js +125 -0
  4. package/dist/composables/stonecrop.js +123 -68
  5. package/dist/composables/use-lazy-link-state.js +125 -0
  6. package/dist/composables/use-stonecrop.js +476 -0
  7. package/dist/doctype.js +10 -2
  8. package/dist/field-triggers.js +15 -3
  9. package/dist/index.js +4 -3
  10. package/dist/operation-log-DB-dGNT9.js +593 -0
  11. package/dist/operation-log-DB-dGNT9.js.map +1 -0
  12. package/dist/registry.js +261 -101
  13. package/dist/schema-validator.js +105 -1
  14. package/dist/src/composable.d.ts +11 -0
  15. package/dist/src/composable.d.ts.map +1 -0
  16. package/dist/src/composable.js +477 -0
  17. package/dist/src/composables/lazy-link.d.ts +25 -0
  18. package/dist/src/composables/lazy-link.d.ts.map +1 -0
  19. package/dist/src/composables/operation-log.d.ts +5 -5
  20. package/dist/src/composables/operation-log.d.ts.map +1 -1
  21. package/dist/src/composables/operation-log.js +224 -0
  22. package/dist/src/composables/stonecrop.d.ts +11 -1
  23. package/dist/src/composables/stonecrop.d.ts.map +1 -1
  24. package/dist/src/composables/stonecrop.js +574 -0
  25. package/dist/src/composables/use-lazy-link-state.d.ts +25 -0
  26. package/dist/src/composables/use-lazy-link-state.d.ts.map +1 -0
  27. package/dist/src/composables/use-stonecrop.d.ts +93 -0
  28. package/dist/src/composables/use-stonecrop.d.ts.map +1 -0
  29. package/dist/src/composables/useNestedSchema.d.ts +110 -0
  30. package/dist/src/composables/useNestedSchema.d.ts.map +1 -0
  31. package/dist/src/composables/useNestedSchema.js +155 -0
  32. package/dist/src/doctype.d.ts +9 -1
  33. package/dist/src/doctype.d.ts.map +1 -1
  34. package/dist/src/doctype.js +234 -0
  35. package/dist/src/exceptions.js +16 -0
  36. package/dist/src/field-triggers.d.ts +6 -0
  37. package/dist/src/field-triggers.d.ts.map +1 -1
  38. package/dist/src/field-triggers.js +567 -0
  39. package/dist/src/index.d.ts +3 -2
  40. package/dist/src/index.d.ts.map +1 -1
  41. package/dist/src/index.js +23 -0
  42. package/dist/src/plugins/index.js +96 -0
  43. package/dist/src/registry.d.ts +102 -23
  44. package/dist/src/registry.d.ts.map +1 -1
  45. package/dist/src/registry.js +246 -0
  46. package/dist/src/schema-validator.d.ts +8 -1
  47. package/dist/src/schema-validator.d.ts.map +1 -1
  48. package/dist/src/schema-validator.js +315 -0
  49. package/dist/src/stonecrop.d.ts +73 -28
  50. package/dist/src/stonecrop.d.ts.map +1 -1
  51. package/dist/src/stonecrop.js +339 -0
  52. package/dist/src/stores/data.d.ts +11 -0
  53. package/dist/src/stores/data.d.ts.map +1 -0
  54. package/dist/src/stores/hst.d.ts +5 -75
  55. package/dist/src/stores/hst.d.ts.map +1 -1
  56. package/dist/src/stores/hst.js +495 -0
  57. package/dist/src/stores/index.js +12 -0
  58. package/dist/src/stores/operation-log.d.ts +14 -14
  59. package/dist/src/stores/operation-log.d.ts.map +1 -1
  60. package/dist/src/stores/operation-log.js +568 -0
  61. package/dist/src/stores/xstate.d.ts +31 -0
  62. package/dist/src/stores/xstate.d.ts.map +1 -0
  63. package/dist/src/tsdoc-metadata.json +11 -0
  64. package/dist/src/types/composable.d.ts +50 -12
  65. package/dist/src/types/composable.d.ts.map +1 -1
  66. package/dist/src/types/doctype.d.ts +6 -7
  67. package/dist/src/types/doctype.d.ts.map +1 -1
  68. package/dist/src/types/field-triggers.d.ts +1 -1
  69. package/dist/src/types/field-triggers.d.ts.map +1 -1
  70. package/dist/src/types/field-triggers.js +4 -0
  71. package/dist/src/types/hst.d.ts +70 -0
  72. package/dist/src/types/hst.d.ts.map +1 -0
  73. package/dist/src/types/index.d.ts +1 -0
  74. package/dist/src/types/index.d.ts.map +1 -1
  75. package/dist/src/types/index.js +4 -0
  76. package/dist/src/types/operation-log.d.ts +4 -4
  77. package/dist/src/types/operation-log.d.ts.map +1 -1
  78. package/dist/src/types/operation-log.js +0 -0
  79. package/dist/src/types/registry.js +0 -0
  80. package/dist/src/types/schema-validator.d.ts +2 -0
  81. package/dist/src/types/schema-validator.d.ts.map +1 -1
  82. package/dist/src/utils.d.ts +24 -0
  83. package/dist/src/utils.d.ts.map +1 -0
  84. package/dist/stonecrop.d.ts +317 -99
  85. package/dist/stonecrop.js +2191 -1897
  86. package/dist/stonecrop.js.map +1 -1
  87. package/dist/stonecrop.umd.cjs +6 -0
  88. package/dist/stonecrop.umd.cjs.map +1 -0
  89. package/dist/stores/data.js +7 -0
  90. package/dist/stores/hst.js +27 -25
  91. package/dist/stores/operation-log.js +59 -47
  92. package/dist/stores/xstate.js +29 -0
  93. package/dist/tests/setup.d.ts +5 -0
  94. package/dist/tests/setup.d.ts.map +1 -0
  95. package/dist/tests/setup.js +15 -0
  96. package/dist/types/hst.js +0 -0
  97. package/dist/types/index.js +1 -0
  98. package/dist/utils.js +46 -0
  99. package/package.json +5 -5
  100. package/src/composables/lazy-link.ts +146 -0
  101. package/src/composables/operation-log.ts +1 -1
  102. package/src/composables/stonecrop.ts +142 -73
  103. package/src/doctype.ts +13 -4
  104. package/src/field-triggers.ts +18 -4
  105. package/src/index.ts +4 -2
  106. package/src/registry.ts +289 -111
  107. package/src/schema-validator.ts +120 -1
  108. package/src/stonecrop.ts +230 -106
  109. package/src/stores/hst.ts +29 -104
  110. package/src/stores/operation-log.ts +64 -50
  111. package/src/types/composable.ts +55 -12
  112. package/src/types/doctype.ts +6 -7
  113. package/src/types/field-triggers.ts +1 -1
  114. package/src/types/hst.ts +77 -0
  115. package/src/types/index.ts +1 -0
  116. package/src/types/operation-log.ts +4 -4
  117. package/src/types/schema-validator.ts +2 -0
@@ -2,12 +2,14 @@ import type { AnyStateNodeConfig } from 'xstate';
2
2
  import { Component } from 'vue';
3
3
  import { ComputedRef } from 'vue';
4
4
  import type { DataClient } from '@stonecrop/schema';
5
+ import type { DoctypeMeta } from '@stonecrop/schema';
6
+ import type { LinkDeclaration } from '@stonecrop/schema';
5
7
  import { List } from 'immutable';
6
8
  import { Map as Map_2 } from 'immutable';
7
9
  import { Plugin as Plugin_2 } from 'vue';
8
10
  import { Ref } from 'vue';
9
11
  import { Router } from 'vue-router';
10
- import { SchemaTypes } from '@stonecrop/aform';
12
+ import type { SchemaTypes } from '@stonecrop/aform';
11
13
  import { Store } from 'pinia';
12
14
  import { StoreDefinition } from 'pinia';
13
15
  import type { UnknownMachineConfig } from 'xstate';
@@ -80,28 +82,17 @@ export declare interface BatchOperation {
80
82
  reversible: boolean;
81
83
  }
82
84
 
83
- /**
84
- * Recursively collect nested data from HST using pre-resolved schemas
85
- * @param resolvedSchema - The already-resolved schema (with nested schemas embedded)
86
- * @param basePath - The base path in HST (e.g., "customer.123.address")
87
- * @param hstStore - The HST store instance
88
- * @returns The collected data object
89
- * @public
90
- */
91
- export declare function collectNestedData(resolvedSchema: SchemaTypes[], basePath: string, hstStore: HSTNode): Record<string, any>;
92
-
93
85
  /**
94
86
  * Factory function for HST creation
95
87
  * Creates a new HSTNode proxy for hierarchical state tree navigation.
96
88
  *
97
89
  * @param target - The target object to wrap with HST functionality
98
90
  * @param doctype - The document type identifier
99
- * @param parentDoctype - Optional parent document type identifier
100
91
  * @returns A new HSTNode proxy instance
101
92
  *
102
93
  * @public
103
94
  */
104
- export declare function createHST(target: any, doctype: string, parentDoctype?: string): HSTNode;
95
+ export declare function createHST(target: any, doctype: string): HSTNode;
105
96
 
106
97
  /**
107
98
  * Creates a validator with the given registry
@@ -176,6 +167,12 @@ export declare class Doctype {
176
167
  * @readonly
177
168
  */
178
169
  readonly component?: Component;
170
+ /**
171
+ * Relationship links to other doctypes
172
+ * @public
173
+ * @readonly
174
+ */
175
+ readonly links?: Record<string, LinkDeclaration>;
179
176
  /**
180
177
  * Creates a new Doctype instance
181
178
  * @param doctype - The doctype name
@@ -183,8 +180,9 @@ export declare class Doctype {
183
180
  * @param workflow - The doctype workflow configuration (XState machine)
184
181
  * @param actions - The doctype actions and field triggers
185
182
  * @param component - Optional Vue component for rendering the doctype
183
+ * @param links - Optional relationship links to other doctypes
186
184
  */
187
- constructor(doctype: string, schema: ImmutableDoctype['schema'], workflow: ImmutableDoctype['workflow'], actions: ImmutableDoctype['actions'], component?: Component);
185
+ constructor(doctype: string, schema: ImmutableDoctype['schema'], workflow: ImmutableDoctype['workflow'], actions: ImmutableDoctype['actions'], component?: Component, links?: Record<string, LinkDeclaration>);
188
186
  /**
189
187
  * Creates a Doctype instance from a plain configuration object.
190
188
  * Handles conversion of arrays to Immutable.js collections internally.
@@ -322,18 +320,16 @@ export declare type DoctypeConfig = {
322
320
  slug?: string;
323
321
  /** Database table name */
324
322
  tableName?: string;
325
- /** Field definitions */
323
+ /** Field definitions (including link fields with fieldtype: 'Link') */
326
324
  fields?: SchemaTypes[];
325
+ /** Relationship links to other doctypes */
326
+ links?: Record<string, LinkDeclaration>;
327
327
  /** Workflow configuration (XState format or simple WorkflowMeta) */
328
328
  workflow?: UnknownMachineConfig | WorkflowMeta;
329
329
  /** Actions and their field triggers */
330
330
  actions?: Record<string, string[]>;
331
- /** Parent doctype for inheritance */
331
+ /** Ancestor doctype for inheritance */
332
332
  inherits?: string;
333
- /** Doctype to use for list views */
334
- listDoctype?: string;
335
- /** Parent doctype for child tables */
336
- parentDoctype?: string;
337
333
  };
338
334
 
339
335
  /**
@@ -425,6 +421,12 @@ export declare class FieldTriggerEngine {
425
421
  * @param fn - The action function
426
422
  */
427
423
  registerAction(name: string, fn: FieldActionFunction): void;
424
+ /**
425
+ * Look up a registered action function by name.
426
+ * Returns `undefined` if the action has not been registered.
427
+ * @param name - The action name
428
+ */
429
+ getAction(name: string): FieldActionFunction | undefined;
428
430
  /**
429
431
  * Register a global XState transition action function
430
432
  * @param name - The name of the transition action
@@ -573,6 +575,18 @@ export declare interface FieldTriggerOptions {
573
575
  */
574
576
  export declare function getGlobalTriggerEngine(options?: FieldTriggerOptions): FieldTriggerEngine;
575
577
 
578
+ /**
579
+ * Returns the global Stonecrop singleton instance, or `undefined` if no
580
+ * instance has been created yet.
581
+ *
582
+ * Use this when you need the Stonecrop instance outside a Vue component
583
+ * context (e.g., in workflow action handlers, plugin setup code, or
584
+ * non-component utilities). Inside a component, prefer `useStonecrop()`.
585
+ *
586
+ * @public
587
+ */
588
+ export declare function getStonecrop(): Stonecrop | undefined;
589
+
576
590
  /**
577
591
  * Global HST Manager (Singleton)
578
592
  * Manages hierarchical state trees and provides access to the global registry.
@@ -641,10 +655,10 @@ export declare interface HSTNode {
641
655
  */
642
656
  has(path: string): boolean;
643
657
  /**
644
- * Gets the parent node in the tree hierarchy
645
- * @returns The parent HSTNode or null if this is the root
658
+ * Gets the ancestor node in the tree hierarchy (immediate predecessor)
659
+ * @returns The ancestor HSTNode or null if this is the root
646
660
  */
647
- getParent(): HSTNode | null;
661
+ getAncestor(): HSTNode | null;
648
662
  /**
649
663
  * Gets the root node of the tree
650
664
  * @returns The root HSTNode
@@ -732,10 +746,10 @@ export declare interface HSTOperation {
732
746
  userId?: string;
733
747
  /** Additional metadata for custom use cases */
734
748
  metadata?: Record<string, any>;
735
- /** Parent operation ID for batch operations */
736
- parentOperationId?: string;
737
- /** Child operation IDs for batch operations */
738
- childOperationIds?: string[];
749
+ /** Ancestor operation ID for batch operations */
750
+ ancestorOperationId?: string;
751
+ /** Descendant operation IDs for batch operations */
752
+ descendantOperationIds?: string[];
739
753
  }
740
754
 
741
755
  /**
@@ -788,14 +802,24 @@ export declare type HSTStonecropReturn = BaseStonecropReturn & {
788
802
  */
789
803
  resolvedSchema: Ref<SchemaTypes[]>;
790
804
  /**
791
- * Loads or initializes nested doctype data.
792
- * Use this when rendering a nested form component. Checks HST first, then initializes defaults.
793
- * @param parentPath - The HST path to check for existing data
794
- * @param childDoctype - The nested doctype metadata
795
- * @param recordId - Optional record ID (reserved for future API fetch)
796
- * @returns The loaded or initialized data object
805
+ * Scaffold empty descendant records from defaults for all descendant links.
806
+ * @param path - The HST path where initialized data should be stored
807
+ * @param doctype - The doctype to initialize
808
+ */
809
+ initializeNestedData: (path: string, doctype: Doctype) => void;
810
+ /**
811
+ * Fetch a record and its nested data from the server.
812
+ * Stores each field at its own HST path per the field-level convention.
813
+ * @param path - The HST path (e.g., "recipe.r1")
814
+ * @param doctype - The doctype to fetch
815
+ * @param recordId - Record ID to fetch
816
+ * @param options - Query options (includeNested to control which links are fetched)
817
+ * @throws Error with code "CLIENT_REQUIRED" if no data client is configured
818
+ * @throws Error with code "RECORD_NOT_FOUND" if the server returns null
797
819
  */
798
- loadNestedData: (parentPath: string, childDoctype: Doctype, recordId?: string) => Record<string, any>;
820
+ fetchNestedData: (path: string, doctype: Doctype, recordId: string, options?: {
821
+ includeNested?: boolean | string[];
822
+ }) => Promise<void>;
799
823
  /**
800
824
  * Collects a complete record payload with all nested data from HST.
801
825
  * Use this before submitting to an API. Recursively includes 1:1 and 1:many nested records.
@@ -805,13 +829,13 @@ export declare type HSTStonecropReturn = BaseStonecropReturn & {
805
829
  */
806
830
  collectRecordPayload: (doctype: Doctype, recordId: string) => Record<string, any>;
807
831
  /**
808
- * Creates a nested context for a child doctype component.
832
+ * Creates a nested context for a descendant doctype component.
809
833
  * Use this in parent components to pass scoped handlers to child components.
810
834
  * @param basePath - The parent HST path prefix
811
- * @param childDoctype - The child doctype metadata
835
+ * @param descendantDoctype - The descendant doctype metadata
812
836
  * @returns Scoped provideHSTPath and handleHSTChange functions
813
837
  */
814
- createNestedContext: (basePath: string, childDoctype: Doctype) => {
838
+ createNestedContext: (basePath: string, descendantDoctype: Doctype) => {
815
839
  provideHSTPath: (fieldname: string) => string;
816
840
  handleHSTChange: (changeData: HSTChangeData) => void;
817
841
  };
@@ -830,6 +854,17 @@ export declare type HSTStonecropReturn = BaseStonecropReturn & {
830
854
  * Available immediately if Doctype instance passed, after async resolution if slug string passed.
831
855
  */
832
856
  resolvedDoctype: Ref<Doctype | undefined>;
857
+ /**
858
+ * Computed ref indicating whether workflow actions are ready to run.
859
+ * True when all links with blockWorkflows are loaded in HST.
860
+ * Use with v-bind:disabled="!isWorkflowReady" on action buttons.
861
+ */
862
+ isWorkflowReady: ComputedRef<boolean>;
863
+ /**
864
+ * List of link fieldnames that are blocking workflow execution.
865
+ * Empty array when isWorkflowReady is true.
866
+ */
867
+ blockedLinks: ComputedRef<string[]>;
833
868
  };
834
869
 
835
870
  /**
@@ -840,6 +875,7 @@ export declare type ImmutableDoctype = {
840
875
  readonly schema?: List<SchemaTypes>;
841
876
  readonly workflow?: UnknownMachineConfig | AnyStateNodeConfig | WorkflowMeta;
842
877
  readonly actions?: Map_2<string, string[]>;
878
+ readonly links?: Record<string, LinkDeclaration>;
843
879
  };
844
880
 
845
881
  /**
@@ -872,6 +908,24 @@ export declare type InstallOptions = {
872
908
  onRouterInitialized?: (registry: Registry, stonecrop: Stonecrop) => void | Promise<void>;
873
909
  };
874
910
 
911
+ /**
912
+ * Lazy link state for a single link field.
913
+ * Provides reactive state and reload capability for lazy-loaded links.
914
+ * @public
915
+ */
916
+ export declare type LazyLink = {
917
+ /** True while fetching data */
918
+ loading: Ref<boolean>;
919
+ /** True after successful fetch (permanent until reload) */
920
+ loaded: Ref<boolean>;
921
+ /** Error state, if any */
922
+ error: Ref<Error | null>;
923
+ /** Explicitly trigger a fetch for this link */
924
+ reload: () => Promise<void>;
925
+ /** The loaded data from HST, or undefined if not loaded */
926
+ data: ComputedRef<any>;
927
+ };
928
+
875
929
  /**
876
930
  * Mark a specific operation as irreversible.
877
931
  * Used to prevent undo of critical operations like publishing or deletion.
@@ -1133,9 +1187,27 @@ export declare class Registry {
1133
1187
  readonly name: string;
1134
1188
  /**
1135
1189
  * The registry property contains a collection of doctypes
1190
+ *
1191
+ * @defaultValue `{}`
1136
1192
  * @see {@link Doctype}
1137
1193
  */
1138
1194
  readonly registry: Record<string, Doctype>;
1195
+ /**
1196
+ * Reverse index: backlink fieldname → list of \{ doctype slug, link fieldname \}.
1197
+ * Multiple doctypes can declare a link with the same backlink name, so each key
1198
+ * maps to an array. Built at schema load time for O(1) ancestor lookups.
1199
+ *
1200
+ * @defaultValue `new Map()`
1201
+ * @internal
1202
+ */
1203
+ private _ancestorIndex;
1204
+ /**
1205
+ * Whether the ancestor index needs rebuilding
1206
+ *
1207
+ * @defaultValue `true`
1208
+ * @internal
1209
+ */
1210
+ private _ancestorIndexDirty;
1139
1211
  /**
1140
1212
  * The Vue router instance
1141
1213
  * @see {@link https://router.vuejs.org/}
@@ -1162,35 +1234,32 @@ export declare class Registry {
1162
1234
  /**
1163
1235
  * Resolve nested Doctype fields in a schema by embedding child schemas inline.
1164
1236
  *
1165
- * @remarks
1166
- * Walks the schema array and for each field with `fieldtype: 'Doctype'` and a string
1167
- * `options` value, looks up the referenced doctype in the registry and:
1237
+ * Accepts a Doctype and extracts `fields` and `links` internally.
1238
+ * Fields array contains both scalar fields and link fields (with fieldtype: 'Link').
1239
+ * Render order is determined by the order of fields in the fields array.
1168
1240
  *
1169
- * - If `cardinality: 'many'`: auto-derives `columns` from the child doctype's schema,
1170
- * sets `component: 'ATable'`, `config: { view: 'list' }`, and initializes `rows: []`.
1171
- * - Otherwise (default/`cardinality: 'one'`): embeds the child schema as the field's
1172
- * `schema` property for 1:1 nested forms.
1241
+ * For each link field:
1242
+ * - Looks up the corresponding link declaration in `links` by fieldname
1243
+ * - `cardinality: 'noneOrMany'` or `'atLeastOne'`: auto-derives `columns` from the target's schema,
1244
+ * sets `component` to `link.component ?? 'ATable'`, `config: { view: 'list' }`, `rows: []`.
1245
+ * - `cardinality: 'one'` or `'atMostOne'`: embeds the target schema as the entry's
1246
+ * `schema` property, sets `component` to `link.component ?? 'AForm'`.
1173
1247
  *
1174
1248
  * Recurses for deeply nested doctypes. Circular references are protected against.
1249
+ * Returns a new array — does not mutate the original.
1175
1250
  *
1176
- * Returns a new array does not mutate the original schema.
1177
- *
1178
- * @param schema - The schema array to resolve
1179
- * @returns A new schema array with nested Doctype fields resolved
1180
- *
1181
- * @example
1182
- * ```ts
1183
- * registry.addDoctype(addressDoctype)
1184
- * registry.addDoctype(customerDoctype)
1185
- *
1186
- * // Before: customer schema has { fieldname: 'address', fieldtype: 'Doctype', options: 'address' }
1187
- * const resolved = registry.resolveSchema(customerSchema)
1188
- * // After: address field now has schema: [...address fields...]
1189
- * ```
1251
+ * @param doctype - The doctype to resolve
1252
+ * @param visited - Internal — set of already-visited doctype slugs for cycle detection
1253
+ * @returns A new schema array with nested links resolved
1190
1254
  *
1191
1255
  * @public
1192
1256
  */
1193
- resolveSchema(schema: SchemaTypes[], visited?: Set<string>): SchemaTypes[];
1257
+ resolveSchema(doctype: Doctype, visited?: Set<string>): SchemaTypes[];
1258
+ /**
1259
+ * Build an ATable configuration from a field and child schema
1260
+ * @internal
1261
+ */
1262
+ private buildTableConfig;
1194
1263
  /**
1195
1264
  * Initialize a new record with default values based on a schema.
1196
1265
  *
@@ -1201,7 +1270,7 @@ export declare class Registry {
1201
1270
  * - Check → `false`
1202
1271
  * - Int, Float, Decimal, Currency, Quantity → `0`
1203
1272
  * - JSON → `{}`
1204
- * - Doctype with `cardinality: 'many'` → `[]`
1273
+ * - Doctype with `cardinality: 'noneOrMany'` or `'atLeastOne'` → `[]`
1205
1274
  * - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
1206
1275
  * - All others → `null`
1207
1276
  *
@@ -1227,6 +1296,69 @@ export declare class Registry {
1227
1296
  * @public
1228
1297
  */
1229
1298
  getDoctype(slug: string): Doctype | undefined;
1299
+ /**
1300
+ * Get all links declared on a doctype.
1301
+ *
1302
+ * @param doctypeSlug - The doctype slug to get links for
1303
+ * @returns Array of link declarations with fieldname, or empty array if none
1304
+ *
1305
+ * @example
1306
+ * ```ts
1307
+ * const links = registry.getDescendantLinks('recipe')
1308
+ * // [{ fieldname: 'tasks', target: 'recipe-task', cardinality: 'noneOrMany', backlink: 'recipe' }]
1309
+ * ```
1310
+ *
1311
+ * @public
1312
+ */
1313
+ getDescendantLinks(doctypeSlug: string): Array<LinkDeclaration & {
1314
+ fieldname: string;
1315
+ }>;
1316
+ /**
1317
+ * Get links on other doctypes that target the given doctype.
1318
+ *
1319
+ * @param doctypeSlug - The doctype slug to find ancestor links for
1320
+ * @returns Array of link declarations with fieldname and declaring doctype slug, or empty array
1321
+ *
1322
+ * @example
1323
+ * ```ts
1324
+ * const ancestors = registry.getAncestorLinks('recipe-task')
1325
+ * // [{ fieldname: 'tasks', target: 'recipe-task', cardinality: 'noneOrMany', backlink: 'recipe', doctype: 'recipe' }]
1326
+ * ```
1327
+ *
1328
+ * @public
1329
+ */
1330
+ getAncestorLinks(doctypeSlug: string): Array<LinkDeclaration & {
1331
+ fieldname: string;
1332
+ doctype: string;
1333
+ }>;
1334
+ /**
1335
+ * Ensure the ancestor index is up to date
1336
+ * @internal
1337
+ */
1338
+ private _ensureAncestorIndex;
1339
+ /**
1340
+ * Convert the registry to a Map of DoctypeMeta objects for use with StonecropClient.
1341
+ *
1342
+ * This allows passing a Registry instance to StonecropClient by deriving the
1343
+ * Map\<string, DoctypeMeta\> that StonecropClient needs for building nested GraphQL queries.
1344
+ *
1345
+ * @returns Map of doctype metadata keyed by doctype name
1346
+ *
1347
+ * @example
1348
+ * ```typescript
1349
+ * const registry = new Registry()
1350
+ * registry.addDoctype(Doctype.fromObject(customerSchema))
1351
+ * registry.addDoctype(Doctype.fromObject(orderSchema))
1352
+ *
1353
+ * const client = new StonecropClient({
1354
+ * endpoint: '/graphql',
1355
+ * registry: registry.toMetaMap(), // Convert once, use with client
1356
+ * })
1357
+ * ```
1358
+ *
1359
+ * @public
1360
+ */
1361
+ toMetaMap(): Map<string, DoctypeMeta>;
1230
1362
  }
1231
1363
 
1232
1364
  /**
@@ -1266,9 +1398,10 @@ export declare class SchemaValidator {
1266
1398
  * @param schema - Schema fields (List or Array)
1267
1399
  * @param workflow - Optional workflow configuration
1268
1400
  * @param actions - Optional actions map
1401
+ * @param links - Optional links object
1269
1402
  * @returns Validation result
1270
1403
  */
1271
- validate(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, workflow?: AnyStateNodeConfig, actions?: Map_2<string, string[]> | Map<string, string[]>): ValidationResult;
1404
+ validate(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, workflow?: AnyStateNodeConfig, actions?: Map_2<string, string[]> | Map<string, string[]>, links?: Record<string, LinkDeclaration>): ValidationResult;
1272
1405
  /**
1273
1406
  * Validates that required schema properties are present
1274
1407
  * @internal
@@ -1279,6 +1412,11 @@ export declare class SchemaValidator {
1279
1412
  * @internal
1280
1413
  */
1281
1414
  private validateLinkFields;
1415
+ /**
1416
+ * Validates link declarations: target resolution, backlink consistency, Link field correspondence
1417
+ * @internal
1418
+ */
1419
+ private validateLinkDeclarations;
1282
1420
  /**
1283
1421
  * Validates workflow state machine configuration
1284
1422
  * @internal
@@ -1312,6 +1450,7 @@ export declare class Stonecrop {
1312
1450
  * @internal
1313
1451
  */
1314
1452
  static _root: Stonecrop;
1453
+ /** The HST store instance for reactive state management */
1315
1454
  private hstStore;
1316
1455
  private _operationLogStore?;
1317
1456
  private _operationLogConfig?;
@@ -1371,8 +1510,8 @@ export declare class Stonecrop {
1371
1510
  actionError?: string | undefined;
1372
1511
  userId?: string | undefined;
1373
1512
  metadata?: Record<string, any> | undefined;
1374
- parentOperationId?: string | undefined;
1375
- childOperationIds?: string[] | undefined;
1513
+ ancestorOperationId?: string | undefined;
1514
+ descendantOperationIds?: string[] | undefined;
1376
1515
  }[], HSTOperation[] | {
1377
1516
  id: string;
1378
1517
  type: HSTOperationType;
@@ -1395,8 +1534,8 @@ export declare class Stonecrop {
1395
1534
  actionError?: string | undefined;
1396
1535
  userId?: string | undefined;
1397
1536
  metadata?: Record<string, any> | undefined;
1398
- parentOperationId?: string | undefined;
1399
- childOperationIds?: string[] | undefined;
1537
+ ancestorOperationId?: string | undefined;
1538
+ descendantOperationIds?: string[] | undefined;
1400
1539
  }[]>;
1401
1540
  currentIndex: Ref<number, number>;
1402
1541
  config: Ref< {
@@ -1434,7 +1573,7 @@ export declare class Stonecrop {
1434
1573
  getSnapshot: () => OperationLogSnapshot;
1435
1574
  markIrreversible: (operationId: string, reason: string) => void;
1436
1575
  logAction: (doctype: string, actionName: string, recordIds?: string[], result?: "success" | "failure" | "pending", error?: string) => string;
1437
- }, "operations" | "clientId" | "currentIndex" | "config">, Pick<{
1576
+ }, "operations" | "currentIndex" | "config" | "clientId">, Pick<{
1438
1577
  operations: Ref< {
1439
1578
  id: string;
1440
1579
  type: HSTOperationType;
@@ -1457,8 +1596,8 @@ export declare class Stonecrop {
1457
1596
  actionError?: string | undefined;
1458
1597
  userId?: string | undefined;
1459
1598
  metadata?: Record<string, any> | undefined;
1460
- parentOperationId?: string | undefined;
1461
- childOperationIds?: string[] | undefined;
1599
+ ancestorOperationId?: string | undefined;
1600
+ descendantOperationIds?: string[] | undefined;
1462
1601
  }[], HSTOperation[] | {
1463
1602
  id: string;
1464
1603
  type: HSTOperationType;
@@ -1481,8 +1620,8 @@ export declare class Stonecrop {
1481
1620
  actionError?: string | undefined;
1482
1621
  userId?: string | undefined;
1483
1622
  metadata?: Record<string, any> | undefined;
1484
- parentOperationId?: string | undefined;
1485
- childOperationIds?: string[] | undefined;
1623
+ ancestorOperationId?: string | undefined;
1624
+ descendantOperationIds?: string[] | undefined;
1486
1625
  }[]>;
1487
1626
  currentIndex: Ref<number, number>;
1488
1627
  config: Ref< {
@@ -1543,8 +1682,8 @@ export declare class Stonecrop {
1543
1682
  actionError?: string | undefined;
1544
1683
  userId?: string | undefined;
1545
1684
  metadata?: Record<string, any> | undefined;
1546
- parentOperationId?: string | undefined;
1547
- childOperationIds?: string[] | undefined;
1685
+ ancestorOperationId?: string | undefined;
1686
+ descendantOperationIds?: string[] | undefined;
1548
1687
  }[], HSTOperation[] | {
1549
1688
  id: string;
1550
1689
  type: HSTOperationType;
@@ -1567,8 +1706,8 @@ export declare class Stonecrop {
1567
1706
  actionError?: string | undefined;
1568
1707
  userId?: string | undefined;
1569
1708
  metadata?: Record<string, any> | undefined;
1570
- parentOperationId?: string | undefined;
1571
- childOperationIds?: string[] | undefined;
1709
+ ancestorOperationId?: string | undefined;
1710
+ descendantOperationIds?: string[] | undefined;
1572
1711
  }[]>;
1573
1712
  currentIndex: Ref<number, number>;
1574
1713
  config: Ref< {
@@ -1664,7 +1803,25 @@ export declare class Stonecrop {
1664
1803
  * @param action - The action to run
1665
1804
  * @param args - Action arguments (typically record IDs)
1666
1805
  */
1667
- runAction(doctype: Doctype, action: string, args?: any[]): void;
1806
+ runAction(doctype: Doctype, action: string, args?: string[]): void;
1807
+ /**
1808
+ * Get the effective blockWorkflows value for a link.
1809
+ * Returns true if blockWorkflows is explicitly true, or if it's absent and fetch method is 'sync'.
1810
+ * @param link - The link declaration
1811
+ * @returns Whether workflows should be blocked until this link is loaded
1812
+ */
1813
+ private getEffectiveBlockWorkflows;
1814
+ /**
1815
+ * Check if workflow actions are ready to run (all required link data is loaded).
1816
+ * A link's data is considered loaded if it exists in HST at `slug.recordId.linkname`.
1817
+ * @param doctype - The doctype to check
1818
+ * @param recordId - The record ID
1819
+ * @returns Object with `ready: true` if all blocked links are loaded, or `ready: false` with `blockedLinks` array
1820
+ */
1821
+ isWorkflowReady(doctype: Doctype, recordId: string): {
1822
+ ready: boolean;
1823
+ blockedLinks?: string[];
1824
+ };
1668
1825
  /**
1669
1826
  * Get records from server using the configured data client.
1670
1827
  * @param doctype - The doctype
@@ -1732,14 +1889,40 @@ export declare class Stonecrop {
1732
1889
  */
1733
1890
  collectRecordPayload(doctype: Doctype, recordId: string): Record<string, any>;
1734
1891
  /**
1735
- * Load nested data from HST or initialize with defaults
1736
- * @param parentPath - The HST path to check for existing data
1737
- * @param childDoctype - The child doctype metadata
1738
- * @param _recordId - Optional record ID to load
1739
- * @returns The loaded or initialized data
1892
+ * Scaffold empty descendant records from defaults for all descendant links.
1893
+ *
1894
+ * Initializes all scalar and link fields at their HST paths with default values.
1895
+ * For new records, call this after setting up the doctype to ensure all paths exist.
1896
+ *
1897
+ * @param path - HST path (e.g., "customer.new")
1898
+ * @param doctype - The doctype to initialize
1740
1899
  * @public
1741
1900
  */
1742
- loadNestedData(parentPath: string, childDoctype: Doctype, _recordId?: string): Record<string, any>;
1901
+ initializeNestedData(path: string, doctype: Doctype): void;
1902
+ /**
1903
+ * Fetch a record and its nested data from the server.
1904
+ *
1905
+ * Calls `_client.getRecord()` with nested sub-selections and stores each scalar field at its own HST path
1906
+ * (`slug.recordId.fieldname`), descendants at the link-level path (`slug.recordId.linkname`).
1907
+ *
1908
+ * @param path - HST path (e.g., "recipe.r1")
1909
+ * @param doctype - The doctype to fetch
1910
+ * @param recordId - Record ID to fetch
1911
+ * @param options - Query options (includeNested to control which links are fetched)
1912
+ * @throws Error with code `"CLIENT_REQUIRED"` if no data client is configured
1913
+ * @throws Error with code `"RECORD_NOT_FOUND"` if the server returns null
1914
+ * @public
1915
+ */
1916
+ fetchNestedData(path: string, doctype: Doctype, recordId: string, options?: {
1917
+ includeNested?: boolean | string[];
1918
+ }): Promise<void>;
1919
+ /**
1920
+ * Recursively collect nested data from HST
1921
+ * @param basePath - The base path in HST (e.g., "customer.123.address")
1922
+ * @param doctype - The doctype whose links drive the recursive traversal
1923
+ * @returns The collected data object
1924
+ */
1925
+ private collectNestedData;
1743
1926
  }
1744
1927
 
1745
1928
  /**
@@ -1838,6 +2021,29 @@ export declare interface UndoRedoState {
1838
2021
  currentIndex: number;
1839
2022
  }
1840
2023
 
2024
+ /**
2025
+ * Get the lazy link state for a specific link field on a doctype record.
2026
+ *
2027
+ * This composable provides reactive state for lazy-loaded links:
2028
+ * - `loading`: true while fetching
2029
+ * - `loaded`: true after successful fetch (permanent until reload)
2030
+ * - `error`: error state if any
2031
+ * - `reload()`: explicitly trigger a fetch
2032
+ * - `data`: computed from HST, or undefined if not loaded
2033
+ *
2034
+ * The reload() function respects the link's fetch strategy:
2035
+ * - `sync`: fetches via GraphQL query through fetchNestedData
2036
+ * - `lazy`: fetches via GraphQL query through fetchNestedData
2037
+ * - `custom`: invokes the serialized handler function directly
2038
+ *
2039
+ * @param doctype - The doctype instance
2040
+ * @param recordId - The record ID
2041
+ * @param linkFieldname - The link fieldname to load
2042
+ * @returns LazyLink with loading, loaded, error, reload, and data
2043
+ * @public
2044
+ */
2045
+ export declare function useLazyLink(doctype: Doctype, recordId: string, linkFieldname: string): LazyLink;
2046
+
1841
2047
  /**
1842
2048
  * Composable for operation log management
1843
2049
  * Provides easy access to undo/redo functionality and operation history
@@ -1886,8 +2092,8 @@ export declare function useOperationLog(config?: Partial<OperationLogConfig>): {
1886
2092
  actionError?: string | undefined;
1887
2093
  userId?: string | undefined;
1888
2094
  metadata?: Record<string, any> | undefined;
1889
- parentOperationId?: string | undefined;
1890
- childOperationIds?: string[] | undefined;
2095
+ ancestorOperationId?: string | undefined;
2096
+ descendantOperationIds?: string[] | undefined;
1891
2097
  }[], HSTOperation[] | {
1892
2098
  id: string;
1893
2099
  type: HSTOperationType;
@@ -1910,8 +2116,8 @@ export declare function useOperationLog(config?: Partial<OperationLogConfig>): {
1910
2116
  actionError?: string | undefined;
1911
2117
  userId?: string | undefined;
1912
2118
  metadata?: Record<string, any> | undefined;
1913
- parentOperationId?: string | undefined;
1914
- childOperationIds?: string[] | undefined;
2119
+ ancestorOperationId?: string | undefined;
2120
+ descendantOperationIds?: string[] | undefined;
1915
2121
  }[]>;
1916
2122
  currentIndex: Ref<number, number>;
1917
2123
  undoRedoState: ComputedRef<UndoRedoState>;
@@ -1961,8 +2167,8 @@ actionResult?: "success" | "failure" | "pending" | undefined;
1961
2167
  actionError?: string | undefined;
1962
2168
  userId?: string | undefined;
1963
2169
  metadata?: Record<string, any> | undefined;
1964
- parentOperationId?: string | undefined;
1965
- childOperationIds?: string[] | undefined;
2170
+ ancestorOperationId?: string | undefined;
2171
+ descendantOperationIds?: string[] | undefined;
1966
2172
  }[], HSTOperation[] | {
1967
2173
  id: string;
1968
2174
  type: HSTOperationType;
@@ -1985,8 +2191,8 @@ actionResult?: "success" | "failure" | "pending" | undefined;
1985
2191
  actionError?: string | undefined;
1986
2192
  userId?: string | undefined;
1987
2193
  metadata?: Record<string, any> | undefined;
1988
- parentOperationId?: string | undefined;
1989
- childOperationIds?: string[] | undefined;
2194
+ ancestorOperationId?: string | undefined;
2195
+ descendantOperationIds?: string[] | undefined;
1990
2196
  }[]>;
1991
2197
  currentIndex: Ref<number, number>;
1992
2198
  config: Ref< {
@@ -2024,7 +2230,7 @@ getOperationsFor: (doctype: string, recordId?: string) => HSTOperation[];
2024
2230
  getSnapshot: () => OperationLogSnapshot;
2025
2231
  markIrreversible: (operationId: string, reason: string) => void;
2026
2232
  logAction: (doctype: string, actionName: string, recordIds?: string[], result?: "success" | "failure" | "pending", error?: string) => string;
2027
- }, "operations" | "clientId" | "currentIndex" | "config">, Pick<{
2233
+ }, "operations" | "currentIndex" | "config" | "clientId">, Pick<{
2028
2234
  operations: Ref< {
2029
2235
  id: string;
2030
2236
  type: HSTOperationType;
@@ -2047,8 +2253,8 @@ actionResult?: "success" | "failure" | "pending" | undefined;
2047
2253
  actionError?: string | undefined;
2048
2254
  userId?: string | undefined;
2049
2255
  metadata?: Record<string, any> | undefined;
2050
- parentOperationId?: string | undefined;
2051
- childOperationIds?: string[] | undefined;
2256
+ ancestorOperationId?: string | undefined;
2257
+ descendantOperationIds?: string[] | undefined;
2052
2258
  }[], HSTOperation[] | {
2053
2259
  id: string;
2054
2260
  type: HSTOperationType;
@@ -2071,8 +2277,8 @@ actionResult?: "success" | "failure" | "pending" | undefined;
2071
2277
  actionError?: string | undefined;
2072
2278
  userId?: string | undefined;
2073
2279
  metadata?: Record<string, any> | undefined;
2074
- parentOperationId?: string | undefined;
2075
- childOperationIds?: string[] | undefined;
2280
+ ancestorOperationId?: string | undefined;
2281
+ descendantOperationIds?: string[] | undefined;
2076
2282
  }[]>;
2077
2283
  currentIndex: Ref<number, number>;
2078
2284
  config: Ref< {
@@ -2133,8 +2339,8 @@ actionResult?: "success" | "failure" | "pending" | undefined;
2133
2339
  actionError?: string | undefined;
2134
2340
  userId?: string | undefined;
2135
2341
  metadata?: Record<string, any> | undefined;
2136
- parentOperationId?: string | undefined;
2137
- childOperationIds?: string[] | undefined;
2342
+ ancestorOperationId?: string | undefined;
2343
+ descendantOperationIds?: string[] | undefined;
2138
2344
  }[], HSTOperation[] | {
2139
2345
  id: string;
2140
2346
  type: HSTOperationType;
@@ -2157,8 +2363,8 @@ actionResult?: "success" | "failure" | "pending" | undefined;
2157
2363
  actionError?: string | undefined;
2158
2364
  userId?: string | undefined;
2159
2365
  metadata?: Record<string, any> | undefined;
2160
- parentOperationId?: string | undefined;
2161
- childOperationIds?: string[] | undefined;
2366
+ ancestorOperationId?: string | undefined;
2367
+ descendantOperationIds?: string[] | undefined;
2162
2368
  }[]>;
2163
2369
  currentIndex: Ref<number, number>;
2164
2370
  config: Ref< {
@@ -2208,7 +2414,17 @@ logAction: (doctype: string, actionName: string, recordIds?: string[], result?:
2208
2414
  export declare function useStonecrop(): BaseStonecropReturn | HSTStonecropReturn;
2209
2415
 
2210
2416
  /**
2211
- * Unified Stonecrop composable with HST integration for a specific doctype and record
2417
+ * Unified Stonecrop composable with HST integration for a specific doctype and record.
2418
+ *
2419
+ * When a `Doctype` instance is passed, all synchronous initialisation (`hstStore`,
2420
+ * `resolvedSchema`, `formData`, `handleHSTChange`, operation-log wiring) is performed
2421
+ * during `setup()` — before the first render and without awaiting any lifecycle hook.
2422
+ * Callers can read `hstStore.value`, `resolvedSchema.value`, and `formData.value`
2423
+ * immediately after calling this composable; no `nextTick`, `flushPromises`, or
2424
+ * `setTimeout` is required.
2425
+ *
2426
+ * The only remaining async work in `onMounted` is fetching an existing record from the
2427
+ * server when `recordId` is not `'new'`, and lazy-loading a doctype by slug string.
2212
2428
  *
2213
2429
  * @param options - Configuration with doctype (string slug or Doctype instance) and optional recordId
2214
2430
  * @returns Stonecrop instance with full HST integration utilities
@@ -2309,6 +2525,8 @@ export declare interface ValidatorOptions {
2309
2525
  registry?: Registry;
2310
2526
  /** Whether to validate Link field targets */
2311
2527
  validateLinkTargets?: boolean;
2528
+ /** Whether to validate links object (target resolution, backlink consistency, Link field correspondence) */
2529
+ validateLinks?: boolean;
2312
2530
  /** Whether to validate workflow reachability */
2313
2531
  validateWorkflows?: boolean;
2314
2532
  /** Whether to validate action registration */