@stonecrop/stonecrop 0.10.15 → 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 +4 -4
  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
@@ -0,0 +1,96 @@
1
+ import { nextTick } from 'vue';
2
+ import Registry from '../registry';
3
+ import { Stonecrop } from '../stonecrop';
4
+ import { useOperationLogStore } from '../stores/operation-log';
5
+ /**
6
+ * Setup auto-initialization for user-defined initialization logic
7
+ * This function handles the post-mount initialization automatically
8
+ */
9
+ async function setupAutoInitialization(registry, stonecrop, onRouterInitialized) {
10
+ // Wait for the next tick to ensure the app is mounted
11
+ await nextTick();
12
+ try {
13
+ await onRouterInitialized(registry, stonecrop);
14
+ }
15
+ catch {
16
+ // Silent error handling - application should handle initialization errors
17
+ }
18
+ }
19
+ /**
20
+ * Stonecrop Vue plugin
21
+ * @param app - The Vue app instance
22
+ * @param options - The plugin options
23
+ * @example
24
+ * ```ts
25
+ * import { createApp } from 'vue'
26
+ * import Stonecrop from '@stonecrop/stonecrop'
27
+ * import { StonecropClient } from '@stonecrop/graphql-client'
28
+ * import router from './router'
29
+ *
30
+ * const client = new StonecropClient({ endpoint: '/graphql' })
31
+ *
32
+ * const app = createApp(App)
33
+ * app.use(Stonecrop, {
34
+ * router,
35
+ * client,
36
+ * getMeta: async (routeContext) => {
37
+ * // routeContext contains: { path, segments }
38
+ * // use the client to fetch doctype meta
39
+ * return client.getMeta({ doctype: routeContext.segments[0] })
40
+ * },
41
+ * autoInitializeRouter: true,
42
+ * onRouterInitialized: async (registry, stonecrop) => {
43
+ * // your custom initialization logic here
44
+ * }
45
+ * })
46
+ * app.mount('#app')
47
+ * ```
48
+ * @public
49
+ */
50
+ const plugin = {
51
+ install: (app, options) => {
52
+ // Check for existing router installation
53
+ const existingRouter = app.config.globalProperties.$router;
54
+ const providedRouter = options?.router;
55
+ const router = existingRouter || providedRouter;
56
+ if (!existingRouter && providedRouter) {
57
+ app.use(providedRouter);
58
+ }
59
+ // Create registry with available router
60
+ const registry = new Registry(router, options?.getMeta);
61
+ app.provide('$registry', registry);
62
+ app.config.globalProperties.$registry = registry;
63
+ // Create and provide a global Stonecrop instance
64
+ const stonecrop = new Stonecrop(registry, undefined, options?.client ? { client: options.client } : undefined);
65
+ app.provide('$stonecrop', stonecrop);
66
+ app.config.globalProperties.$stonecrop = stonecrop;
67
+ // Initialize operation log store if Pinia is available
68
+ // This ensures the store is created with the app's Pinia instance
69
+ try {
70
+ const pinia = app.config.globalProperties.$pinia;
71
+ if (pinia) {
72
+ // Initialize the operation log store with the app's Pinia instance
73
+ const operationLogStore = useOperationLogStore(pinia);
74
+ // Provide the store so components can access it
75
+ app.provide('$operationLogStore', operationLogStore);
76
+ app.config.globalProperties.$operationLogStore = operationLogStore;
77
+ }
78
+ }
79
+ catch (error) {
80
+ // Pinia not available - operation log won't work, but app should still function
81
+ // eslint-disable-next-line no-console
82
+ console.warn('Pinia not available - operation log features will be disabled:', error);
83
+ }
84
+ // Register custom components
85
+ if (options?.components) {
86
+ for (const [tag, component] of Object.entries(options.components)) {
87
+ app.component(tag, component);
88
+ }
89
+ }
90
+ // Setup auto-initialization if requested
91
+ if (options?.autoInitializeRouter && options.onRouterInitialized) {
92
+ void setupAutoInitialization(registry, stonecrop, options.onRouterInitialized);
93
+ }
94
+ },
95
+ };
96
+ export default plugin;
@@ -1,4 +1,5 @@
1
1
  import type { SchemaTypes } from '@stonecrop/aform';
2
+ import type { DoctypeMeta, LinkDeclaration } from '@stonecrop/schema';
2
3
  import { Router } from 'vue-router';
3
4
  import Doctype from './doctype';
4
5
  import { RouteContext } from './types/registry';
@@ -19,9 +20,27 @@ export default class Registry {
19
20
  readonly name: string;
20
21
  /**
21
22
  * The registry property contains a collection of doctypes
23
+ *
24
+ * @defaultValue `{}`
22
25
  * @see {@link Doctype}
23
26
  */
24
27
  readonly registry: Record<string, Doctype>;
28
+ /**
29
+ * Reverse index: backlink fieldname → list of \{ doctype slug, link fieldname \}.
30
+ * Multiple doctypes can declare a link with the same backlink name, so each key
31
+ * maps to an array. Built at schema load time for O(1) ancestor lookups.
32
+ *
33
+ * @defaultValue `new Map()`
34
+ * @internal
35
+ */
36
+ private _ancestorIndex;
37
+ /**
38
+ * Whether the ancestor index needs rebuilding
39
+ *
40
+ * @defaultValue `true`
41
+ * @internal
42
+ */
43
+ private _ancestorIndexDirty;
25
44
  /**
26
45
  * The Vue router instance
27
46
  * @see {@link https://router.vuejs.org/}
@@ -48,35 +67,32 @@ export default class Registry {
48
67
  /**
49
68
  * Resolve nested Doctype fields in a schema by embedding child schemas inline.
50
69
  *
51
- * @remarks
52
- * Walks the schema array and for each field with `fieldtype: 'Doctype'` and a string
53
- * `options` value, looks up the referenced doctype in the registry and:
70
+ * Accepts a Doctype and extracts `fields` and `links` internally.
71
+ * Fields array contains both scalar fields and link fields (with fieldtype: 'Link').
72
+ * Render order is determined by the order of fields in the fields array.
54
73
  *
55
- * - If `cardinality: 'many'`: auto-derives `columns` from the child doctype's schema,
56
- * sets `component: 'ATable'`, `config: { view: 'list' }`, and initializes `rows: []`.
57
- * - Otherwise (default/`cardinality: 'one'`): embeds the child schema as the field's
58
- * `schema` property for 1:1 nested forms.
74
+ * For each link field:
75
+ * - Looks up the corresponding link declaration in `links` by fieldname
76
+ * - `cardinality: 'noneOrMany'` or `'atLeastOne'`: auto-derives `columns` from the target's schema,
77
+ * sets `component` to `link.component ?? 'ATable'`, `config: { view: 'list' }`, `rows: []`.
78
+ * - `cardinality: 'one'` or `'atMostOne'`: embeds the target schema as the entry's
79
+ * `schema` property, sets `component` to `link.component ?? 'AForm'`.
59
80
  *
60
81
  * Recurses for deeply nested doctypes. Circular references are protected against.
82
+ * Returns a new array — does not mutate the original.
61
83
  *
62
- * Returns a new array does not mutate the original schema.
63
- *
64
- * @param schema - The schema array to resolve
65
- * @returns A new schema array with nested Doctype fields resolved
66
- *
67
- * @example
68
- * ```ts
69
- * registry.addDoctype(addressDoctype)
70
- * registry.addDoctype(customerDoctype)
71
- *
72
- * // Before: customer schema has { fieldname: 'address', fieldtype: 'Doctype', options: 'address' }
73
- * const resolved = registry.resolveSchema(customerSchema)
74
- * // After: address field now has schema: [...address fields...]
75
- * ```
84
+ * @param doctype - The doctype to resolve
85
+ * @param visited - Internal — set of already-visited doctype slugs for cycle detection
86
+ * @returns A new schema array with nested links resolved
76
87
  *
77
88
  * @public
78
89
  */
79
- resolveSchema(schema: SchemaTypes[], visited?: Set<string>): SchemaTypes[];
90
+ resolveSchema(doctype: Doctype, visited?: Set<string>): SchemaTypes[];
91
+ /**
92
+ * Build an ATable configuration from a field and child schema
93
+ * @internal
94
+ */
95
+ private buildTableConfig;
80
96
  /**
81
97
  * Initialize a new record with default values based on a schema.
82
98
  *
@@ -87,7 +103,7 @@ export default class Registry {
87
103
  * - Check → `false`
88
104
  * - Int, Float, Decimal, Currency, Quantity → `0`
89
105
  * - JSON → `{}`
90
- * - Doctype with `cardinality: 'many'` → `[]`
106
+ * - Doctype with `cardinality: 'noneOrMany'` or `'atLeastOne'` → `[]`
91
107
  * - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
92
108
  * - All others → `null`
93
109
  *
@@ -113,5 +129,68 @@ export default class Registry {
113
129
  * @public
114
130
  */
115
131
  getDoctype(slug: string): Doctype | undefined;
132
+ /**
133
+ * Get all links declared on a doctype.
134
+ *
135
+ * @param doctypeSlug - The doctype slug to get links for
136
+ * @returns Array of link declarations with fieldname, or empty array if none
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const links = registry.getDescendantLinks('recipe')
141
+ * // [{ fieldname: 'tasks', target: 'recipe-task', cardinality: 'noneOrMany', backlink: 'recipe' }]
142
+ * ```
143
+ *
144
+ * @public
145
+ */
146
+ getDescendantLinks(doctypeSlug: string): Array<LinkDeclaration & {
147
+ fieldname: string;
148
+ }>;
149
+ /**
150
+ * Get links on other doctypes that target the given doctype.
151
+ *
152
+ * @param doctypeSlug - The doctype slug to find ancestor links for
153
+ * @returns Array of link declarations with fieldname and declaring doctype slug, or empty array
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * const ancestors = registry.getAncestorLinks('recipe-task')
158
+ * // [{ fieldname: 'tasks', target: 'recipe-task', cardinality: 'noneOrMany', backlink: 'recipe', doctype: 'recipe' }]
159
+ * ```
160
+ *
161
+ * @public
162
+ */
163
+ getAncestorLinks(doctypeSlug: string): Array<LinkDeclaration & {
164
+ fieldname: string;
165
+ doctype: string;
166
+ }>;
167
+ /**
168
+ * Ensure the ancestor index is up to date
169
+ * @internal
170
+ */
171
+ private _ensureAncestorIndex;
172
+ /**
173
+ * Convert the registry to a Map of DoctypeMeta objects for use with StonecropClient.
174
+ *
175
+ * This allows passing a Registry instance to StonecropClient by deriving the
176
+ * Map\<string, DoctypeMeta\> that StonecropClient needs for building nested GraphQL queries.
177
+ *
178
+ * @returns Map of doctype metadata keyed by doctype name
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * const registry = new Registry()
183
+ * registry.addDoctype(Doctype.fromObject(customerSchema))
184
+ * registry.addDoctype(Doctype.fromObject(orderSchema))
185
+ *
186
+ * const client = new StonecropClient({
187
+ * endpoint: '/graphql',
188
+ * registry: registry.toMetaMap(), // Convert once, use with client
189
+ * })
190
+ * ```
191
+ *
192
+ * @public
193
+ */
194
+ toMetaMap(): Map<string, DoctypeMeta>;
116
195
  }
117
196
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,OAAO,MAAM,WAAW,CAAA;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC5B;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAA;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAE1C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IAExB;;;;OAIG;gBACS,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAWjG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEpE;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO;IAsB3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE;IA4E1E;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAgD5D;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;CAgB7C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,OAAO,MAAM,WAAW,CAAA;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC5B;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAA;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAa;IAElC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAK;IAE/C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc,CAAqE;IAE3F;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB,CAAgB;IAE3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IAExB;;;;OAIG;gBACS,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IASjG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEpE;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO;IAuB3B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE;IA6ErE;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAiCxB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAoD5D;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAI7C;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAUvF;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBtG;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC;CA4BrC"}
@@ -0,0 +1,246 @@
1
+ import { getGlobalTriggerEngine } from './field-triggers';
2
+ /**
3
+ * Stonecrop Registry class
4
+ * @public
5
+ */
6
+ export default class Registry {
7
+ /**
8
+ * The root Registry instance
9
+ */
10
+ static _root;
11
+ /**
12
+ * The name of the Registry instance
13
+ *
14
+ * @defaultValue 'Registry'
15
+ */
16
+ name = 'Registry';
17
+ /**
18
+ * The registry property contains a collection of doctypes
19
+ * @see {@link Doctype}
20
+ */
21
+ registry = {};
22
+ /**
23
+ * The Vue router instance
24
+ * @see {@link https://router.vuejs.org/}
25
+ */
26
+ router;
27
+ /**
28
+ * Creates a new Registry instance (singleton pattern)
29
+ * @param router - Optional Vue router instance for route management
30
+ * @param getMeta - Optional function to fetch doctype metadata from an API
31
+ */
32
+ constructor(router, getMeta) {
33
+ if (Registry._root) {
34
+ return Registry._root;
35
+ }
36
+ Registry._root = this;
37
+ this.router = router;
38
+ this.getMeta = getMeta;
39
+ }
40
+ /**
41
+ * The getMeta function fetches doctype metadata from an API based on route context
42
+ * @see {@link Doctype}
43
+ */
44
+ getMeta;
45
+ /**
46
+ * Get doctype metadata
47
+ * @param doctype - The doctype to fetch metadata for
48
+ * @returns The doctype metadata
49
+ * @see {@link Doctype}
50
+ */
51
+ addDoctype(doctype) {
52
+ if (!(doctype.slug in this.registry)) {
53
+ this.registry[doctype.slug] = doctype;
54
+ }
55
+ // Register actions (including field triggers) with the field trigger engine
56
+ const triggerEngine = getGlobalTriggerEngine();
57
+ // Register under both doctype name and slug to handle different lookup patterns
58
+ triggerEngine.registerDoctypeActions(doctype.doctype, doctype.actions);
59
+ if (doctype.slug !== doctype.doctype) {
60
+ triggerEngine.registerDoctypeActions(doctype.slug, doctype.actions);
61
+ }
62
+ if (doctype.component && this.router && !this.router.hasRoute(doctype.doctype)) {
63
+ this.router.addRoute({
64
+ path: `/${doctype.slug}`,
65
+ name: doctype.slug,
66
+ component: doctype.component,
67
+ });
68
+ }
69
+ }
70
+ /**
71
+ * Resolve nested Doctype fields in a schema by embedding child schemas inline.
72
+ *
73
+ * @remarks
74
+ * Walks the schema array and for each field with `fieldtype: 'Doctype'` and a string
75
+ * `options` value, looks up the referenced doctype in the registry and:
76
+ *
77
+ * - If `cardinality: 'many'`: auto-derives `columns` from the child doctype's schema,
78
+ * sets `component: 'ATable'`, `config: { view: 'list' }`, and initializes `rows: []`.
79
+ * - Otherwise (default/`cardinality: 'one'`): embeds the child schema as the field's
80
+ * `schema` property for 1:1 nested forms.
81
+ *
82
+ * Recurses for deeply nested doctypes. Circular references are protected against.
83
+ *
84
+ * Returns a new array — does not mutate the original schema.
85
+ *
86
+ * @param schema - The schema array to resolve
87
+ * @returns A new schema array with nested Doctype fields resolved
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * registry.addDoctype(addressDoctype)
92
+ * registry.addDoctype(customerDoctype)
93
+ *
94
+ * // Before: customer schema has { fieldname: 'address', fieldtype: 'Doctype', options: 'address' }
95
+ * const resolved = registry.resolveSchema(customerSchema)
96
+ * // After: address field now has schema: [...address fields...]
97
+ * ```
98
+ *
99
+ * @public
100
+ */
101
+ resolveSchema(schema, visited) {
102
+ const seen = visited || new Set();
103
+ return schema.map(field => {
104
+ // Check for Doctype fieldtype with a string options (slug reference)
105
+ if ('fieldtype' in field &&
106
+ field.fieldtype === 'Doctype' &&
107
+ 'options' in field &&
108
+ typeof field.options === 'string') {
109
+ const doctypeSlug = field.options;
110
+ // Circular reference protection
111
+ if (seen.has(doctypeSlug)) {
112
+ return { ...field };
113
+ }
114
+ const doctype = this.registry[doctypeSlug];
115
+ if (doctype && doctype.schema) {
116
+ // Convert Immutable.List to plain array if needed
117
+ const childSchema = Array.isArray(doctype.schema) ? doctype.schema : Array.from(doctype.schema);
118
+ // Check cardinality to determine handling
119
+ const cardinality = 'cardinality' in field ? field.cardinality : undefined;
120
+ if (cardinality === 'many') {
121
+ // 1:many child table - derive columns, set component, config, rows
122
+ const resolved = { ...field };
123
+ // Auto-derive columns from child schema fields if not already provided
124
+ if (!('columns' in field) || !field.columns) {
125
+ resolved.columns = childSchema.map(childField => ({
126
+ name: childField.fieldname,
127
+ fieldname: childField.fieldname,
128
+ label: ('label' in childField && childField.label) || childField.fieldname,
129
+ fieldtype: 'fieldtype' in childField ? childField.fieldtype : 'Data',
130
+ align: ('align' in childField && childField.align) || 'left',
131
+ edit: 'edit' in childField ? childField.edit : true,
132
+ width: ('width' in childField && childField.width) || '20ch',
133
+ }));
134
+ }
135
+ // Set default component if not already specified
136
+ if (!resolved.component) {
137
+ resolved.component = 'ATable';
138
+ }
139
+ // Set default config if not already specified
140
+ if (!('config' in field) || !field.config) {
141
+ resolved.config = { view: 'list' };
142
+ }
143
+ // Initialize rows to empty array so componentProps fallback
144
+ // routes data from the form's dataModel[fieldname]
145
+ if (!('rows' in field) || !field.rows) {
146
+ resolved.rows = [];
147
+ }
148
+ return resolved;
149
+ }
150
+ else {
151
+ // 1:1 nested form (default cardinality: 'one')
152
+ // Recurse into child schema to resolve deeply nested doctypes
153
+ seen.add(doctypeSlug);
154
+ const resolvedChild = this.resolveSchema(childSchema, seen);
155
+ seen.delete(doctypeSlug);
156
+ return { ...field, schema: resolvedChild };
157
+ }
158
+ }
159
+ }
160
+ return { ...field };
161
+ });
162
+ }
163
+ /**
164
+ * Initialize a new record with default values based on a schema.
165
+ *
166
+ * @remarks
167
+ * Creates a plain object with keys from the schema's fieldnames and default values
168
+ * derived from each field's `fieldtype`:
169
+ * - Data, Text → `''`
170
+ * - Check → `false`
171
+ * - Int, Float, Decimal, Currency, Quantity → `0`
172
+ * - JSON → `{}`
173
+ * - Doctype with `cardinality: 'many'` → `[]`
174
+ * - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
175
+ * - All others → `null`
176
+ *
177
+ * For Doctype fields with a resolved `schema` array (cardinality: 'one'), recursively
178
+ * initializes the nested record.
179
+ *
180
+ * @param schema - The schema array to derive defaults from
181
+ * @returns A plain object with default values for each field
182
+ *
183
+ * @example
184
+ * ```ts
185
+ * const defaults = registry.initializeRecord(addressSchema)
186
+ * // { street: '', city: '', state: '', zip_code: '' }
187
+ * ```
188
+ *
189
+ * @public
190
+ */
191
+ initializeRecord(schema) {
192
+ const record = {};
193
+ schema.forEach(field => {
194
+ const fieldtype = 'fieldtype' in field ? field.fieldtype : 'Data';
195
+ switch (fieldtype) {
196
+ case 'Data':
197
+ case 'Text':
198
+ case 'Code':
199
+ record[field.fieldname] = '';
200
+ break;
201
+ case 'Check':
202
+ record[field.fieldname] = false;
203
+ break;
204
+ case 'Int':
205
+ case 'Float':
206
+ case 'Decimal':
207
+ case 'Currency':
208
+ case 'Quantity':
209
+ record[field.fieldname] = 0;
210
+ break;
211
+ case 'JSON':
212
+ record[field.fieldname] = {};
213
+ break;
214
+ case 'Doctype': {
215
+ // Check cardinality to determine initial value
216
+ const cardinality = 'cardinality' in field ? field.cardinality : undefined;
217
+ if (cardinality === 'many') {
218
+ // 1:many child table - initialize as empty array
219
+ record[field.fieldname] = [];
220
+ }
221
+ else if ('schema' in field && Array.isArray(field.schema)) {
222
+ // 1:1 nested form with resolved schema - recursively initialize
223
+ record[field.fieldname] = this.initializeRecord(field.schema);
224
+ }
225
+ else {
226
+ // 1:1 without resolved schema - empty object
227
+ record[field.fieldname] = {};
228
+ }
229
+ break;
230
+ }
231
+ default:
232
+ record[field.fieldname] = null;
233
+ }
234
+ });
235
+ return record;
236
+ }
237
+ /**
238
+ * Get a registered doctype by slug
239
+ * @param slug - The doctype slug to look up
240
+ * @returns The Doctype instance if found, or undefined
241
+ * @public
242
+ */
243
+ getDoctype(slug) {
244
+ return this.registry[slug];
245
+ }
246
+ }
@@ -4,6 +4,7 @@
4
4
  * @packageDocumentation
5
5
  */
6
6
  import type { SchemaTypes } from '@stonecrop/aform';
7
+ import type { LinkDeclaration } from '@stonecrop/schema';
7
8
  import type { List, Map as ImmutableMap } from 'immutable';
8
9
  import type { AnyStateNodeConfig } from 'xstate';
9
10
  import type Registry from './registry';
@@ -25,9 +26,10 @@ export declare class SchemaValidator {
25
26
  * @param schema - Schema fields (List or Array)
26
27
  * @param workflow - Optional workflow configuration
27
28
  * @param actions - Optional actions map
29
+ * @param links - Optional links object
28
30
  * @returns Validation result
29
31
  */
30
- validate(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>): ValidationResult;
32
+ validate(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>, links?: Record<string, LinkDeclaration>): ValidationResult;
31
33
  /**
32
34
  * Validates that required schema properties are present
33
35
  * @internal
@@ -38,6 +40,11 @@ export declare class SchemaValidator {
38
40
  * @internal
39
41
  */
40
42
  private validateLinkFields;
43
+ /**
44
+ * Validates link declarations: target resolution, backlink consistency, Link field correspondence
45
+ * @internal
46
+ */
47
+ private validateLinkDeclarations;
41
48
  /**
42
49
  * Validates workflow state machine configuration
43
50
  * @internal
@@ -1 +1 @@
1
- {"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/schema-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAA;AAGhD,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,OAAO,KAAK,EAAmB,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEnG;;;GAGG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,OAAO,CAA4B;IAE3C;;;OAGG;gBACS,OAAO,GAAE,gBAAqB;IAU1C;;;;;;;OAOG;IACH,QAAQ,CACP,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,GAAG,SAAS,EACrD,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAC9D,gBAAgB;IAyCnB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAwClC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA4D1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAmFxB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;CAuClC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,eAAe,CAKxG;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,GAAG,SAAS,EACrD,QAAQ,EAAE,QAAQ,EAClB,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAC9D,gBAAgB,CAGlB"}
1
+ {"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/schema-validator.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,YAAY,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAA;AAGhD,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,OAAO,KAAK,EAAmB,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEnG;;;GAGG;AACH,qBAAa,eAAe;IAC3B,OAAO,CAAC,OAAO,CAA4B;IAE3C;;;OAGG;gBACS,OAAO,GAAE,gBAAqB;IAW1C;;;;;;;;OAQG;IACH,QAAQ,CACP,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,GAAG,SAAS,EACrD,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAChE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GACrC,gBAAgB;IA8CnB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAwClC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA4D1B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA0GhC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAmFxB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;CAuClC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,eAAe,CAKxG;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,GAAG,SAAS,EACrD,QAAQ,EAAE,QAAQ,EAClB,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAC9D,gBAAgB,CAGlB"}