@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
@@ -0,0 +1,477 @@
1
+ // src/composable.ts
2
+ import { inject, onMounted, ref, watch, provide, computed } from 'vue';
3
+ import { Stonecrop } from './stonecrop';
4
+ import { storeToRefs } from 'pinia';
5
+ /**
6
+ * @public
7
+ */
8
+ export function useStonecrop(options) {
9
+ if (!options)
10
+ options = {};
11
+ const registry = options.registry || inject('$registry');
12
+ const providedStonecrop = inject('$stonecrop');
13
+ const stonecrop = ref();
14
+ const hstStore = ref();
15
+ const formData = ref({});
16
+ // Use refs for router-loaded doctype to maintain reactivity
17
+ const routerDoctype = ref();
18
+ const routerRecordId = ref();
19
+ // Resolved schema with nested Doctype fields expanded
20
+ const resolvedSchema = ref([]);
21
+ // Auto-resolve schema when doctype is available
22
+ if (options.doctype && registry) {
23
+ const schemaArray = options.doctype.schema
24
+ ? Array.isArray(options.doctype.schema)
25
+ ? options.doctype.schema
26
+ : Array.from(options.doctype.schema)
27
+ : [];
28
+ resolvedSchema.value = registry.resolveSchema(schemaArray);
29
+ }
30
+ // Operation log state and methods - will be populated after stonecrop instance is created
31
+ const operations = ref([]);
32
+ const currentIndex = ref(-1);
33
+ const canUndo = computed(() => stonecrop.value?.getOperationLogStore().canUndo ?? false);
34
+ const canRedo = computed(() => stonecrop.value?.getOperationLogStore().canRedo ?? false);
35
+ const undoCount = computed(() => stonecrop.value?.getOperationLogStore().undoCount ?? 0);
36
+ const redoCount = computed(() => stonecrop.value?.getOperationLogStore().redoCount ?? 0);
37
+ const undoRedoState = computed(() => stonecrop.value?.getOperationLogStore().undoRedoState ?? {
38
+ canUndo: false,
39
+ canRedo: false,
40
+ undoCount: 0,
41
+ redoCount: 0,
42
+ currentIndex: -1,
43
+ });
44
+ // Operation log methods
45
+ const undo = (hstStore) => {
46
+ return stonecrop.value?.getOperationLogStore().undo(hstStore) ?? false;
47
+ };
48
+ const redo = (hstStore) => {
49
+ return stonecrop.value?.getOperationLogStore().redo(hstStore) ?? false;
50
+ };
51
+ const startBatch = () => {
52
+ stonecrop.value?.getOperationLogStore().startBatch();
53
+ };
54
+ const commitBatch = (description) => {
55
+ return stonecrop.value?.getOperationLogStore().commitBatch(description) ?? null;
56
+ };
57
+ const cancelBatch = () => {
58
+ stonecrop.value?.getOperationLogStore().cancelBatch();
59
+ };
60
+ const clear = () => {
61
+ stonecrop.value?.getOperationLogStore().clear();
62
+ };
63
+ const getOperationsFor = (doctype, recordId) => {
64
+ return stonecrop.value?.getOperationLogStore().getOperationsFor(doctype, recordId) ?? [];
65
+ };
66
+ const getSnapshot = () => {
67
+ return (stonecrop.value?.getOperationLogStore().getSnapshot() ?? {
68
+ operations: [],
69
+ currentIndex: -1,
70
+ totalOperations: 0,
71
+ reversibleOperations: 0,
72
+ irreversibleOperations: 0,
73
+ });
74
+ };
75
+ const markIrreversible = (operationId, reason) => {
76
+ stonecrop.value?.getOperationLogStore().markIrreversible(operationId, reason);
77
+ };
78
+ const logAction = (doctype, actionName, recordIds, result = 'success', error) => {
79
+ return stonecrop.value?.getOperationLogStore().logAction(doctype, actionName, recordIds, result, error) ?? '';
80
+ };
81
+ const configure = (config) => {
82
+ stonecrop.value?.getOperationLogStore().configure(config);
83
+ };
84
+ // Initialize Stonecrop instance
85
+ onMounted(async () => {
86
+ if (!registry) {
87
+ return;
88
+ }
89
+ stonecrop.value = providedStonecrop || new Stonecrop(registry);
90
+ // Set up reactive refs from operation log store - only if Pinia is available
91
+ try {
92
+ const opLogStore = stonecrop.value.getOperationLogStore();
93
+ const opLogRefs = storeToRefs(opLogStore);
94
+ operations.value = opLogRefs.operations.value;
95
+ currentIndex.value = opLogRefs.currentIndex.value;
96
+ // Watch for changes in operation log state
97
+ watch(() => opLogRefs.operations.value, newOps => {
98
+ operations.value = newOps;
99
+ });
100
+ watch(() => opLogRefs.currentIndex.value, newIndex => {
101
+ currentIndex.value = newIndex;
102
+ });
103
+ }
104
+ catch {
105
+ // Pinia not available (e.g., in tests) - operation log features will not be available
106
+ // Silently fail - operation log is optional
107
+ }
108
+ // Handle router-based setup if no specific doctype provided
109
+ if (!options.doctype && registry.router) {
110
+ const route = registry.router.currentRoute.value;
111
+ // Parse route path - let the application determine the doctype from the route
112
+ if (!route.path)
113
+ return; // Early return if no path available
114
+ const pathSegments = route.path.split('/').filter(segment => segment.length > 0);
115
+ const recordId = pathSegments[1]?.toLowerCase();
116
+ if (pathSegments.length > 0) {
117
+ // Create route context for getMeta function
118
+ const routeContext = {
119
+ path: route.path,
120
+ segments: pathSegments,
121
+ };
122
+ const doctype = await registry.getMeta?.(routeContext);
123
+ if (doctype) {
124
+ registry.addDoctype(doctype);
125
+ stonecrop.value.setup(doctype);
126
+ // Set reactive refs for router-based doctype
127
+ routerDoctype.value = doctype;
128
+ routerRecordId.value = recordId;
129
+ hstStore.value = stonecrop.value.getStore();
130
+ // Resolve schema for router-loaded doctype
131
+ if (registry) {
132
+ const schemaArray = doctype.schema
133
+ ? Array.isArray(doctype.schema)
134
+ ? doctype.schema
135
+ : Array.from(doctype.schema)
136
+ : [];
137
+ resolvedSchema.value = registry.resolveSchema(schemaArray);
138
+ }
139
+ if (recordId && recordId !== 'new') {
140
+ const existingRecord = stonecrop.value.getRecordById(doctype, recordId);
141
+ if (existingRecord) {
142
+ formData.value = existingRecord.get('') || {};
143
+ }
144
+ else {
145
+ try {
146
+ await stonecrop.value.getRecord(doctype, recordId);
147
+ const loadedRecord = stonecrop.value.getRecordById(doctype, recordId);
148
+ if (loadedRecord) {
149
+ formData.value = loadedRecord.get('') || {};
150
+ }
151
+ }
152
+ catch {
153
+ formData.value = initializeNewRecord(doctype);
154
+ }
155
+ }
156
+ }
157
+ else {
158
+ formData.value = initializeNewRecord(doctype);
159
+ }
160
+ if (hstStore.value) {
161
+ setupDeepReactivity(doctype, recordId || 'new', formData, hstStore.value);
162
+ }
163
+ stonecrop.value.runAction(doctype, 'load', recordId ? [recordId] : undefined);
164
+ }
165
+ }
166
+ }
167
+ // Handle HST integration if doctype is provided explicitly
168
+ if (options.doctype) {
169
+ hstStore.value = stonecrop.value.getStore();
170
+ const doctype = options.doctype;
171
+ const recordId = options.recordId;
172
+ if (recordId && recordId !== 'new') {
173
+ const existingRecord = stonecrop.value.getRecordById(doctype, recordId);
174
+ if (existingRecord) {
175
+ formData.value = existingRecord.get('') || {};
176
+ }
177
+ else {
178
+ try {
179
+ await stonecrop.value.getRecord(doctype, recordId);
180
+ const loadedRecord = stonecrop.value.getRecordById(doctype, recordId);
181
+ if (loadedRecord) {
182
+ formData.value = loadedRecord.get('') || {};
183
+ }
184
+ }
185
+ catch {
186
+ formData.value = initializeNewRecord(doctype);
187
+ }
188
+ }
189
+ }
190
+ else {
191
+ formData.value = initializeNewRecord(doctype);
192
+ }
193
+ if (hstStore.value) {
194
+ setupDeepReactivity(doctype, recordId || 'new', formData, hstStore.value);
195
+ }
196
+ }
197
+ });
198
+ // HST integration functions - always created but only populated when HST is available
199
+ const provideHSTPath = (fieldname, customRecordId) => {
200
+ const doctype = options.doctype || routerDoctype.value;
201
+ if (!doctype)
202
+ return '';
203
+ const actualRecordId = customRecordId || options.recordId || routerRecordId.value || 'new';
204
+ return `${doctype.slug}.${actualRecordId}.${fieldname}`;
205
+ };
206
+ const handleHSTChange = (changeData) => {
207
+ const doctype = options.doctype || routerDoctype.value;
208
+ if (!hstStore.value || !stonecrop.value || !doctype) {
209
+ return;
210
+ }
211
+ try {
212
+ const pathParts = changeData.path.split('.');
213
+ if (pathParts.length >= 2) {
214
+ const doctypeSlug = pathParts[0];
215
+ const recordId = pathParts[1];
216
+ if (!hstStore.value.has(`${doctypeSlug}.${recordId}`)) {
217
+ stonecrop.value.addRecord(doctype, recordId, { ...formData.value });
218
+ }
219
+ if (pathParts.length > 3) {
220
+ const recordPath = `${doctypeSlug}.${recordId}`;
221
+ const nestedParts = pathParts.slice(2);
222
+ let currentPath = recordPath;
223
+ for (let i = 0; i < nestedParts.length - 1; i++) {
224
+ currentPath += `.${nestedParts[i]}`;
225
+ if (!hstStore.value.has(currentPath)) {
226
+ const nextPart = nestedParts[i + 1];
227
+ const isArray = !isNaN(Number(nextPart));
228
+ hstStore.value.set(currentPath, isArray ? [] : {});
229
+ }
230
+ }
231
+ }
232
+ }
233
+ hstStore.value.set(changeData.path, changeData.value);
234
+ const fieldParts = changeData.fieldname.split('.');
235
+ const newFormData = { ...formData.value };
236
+ if (fieldParts.length === 1) {
237
+ newFormData[fieldParts[0]] = changeData.value;
238
+ }
239
+ else {
240
+ updateNestedObject(newFormData, fieldParts, changeData.value);
241
+ }
242
+ formData.value = newFormData;
243
+ }
244
+ catch {
245
+ // Silently handle errors
246
+ }
247
+ };
248
+ // Provide injection tokens if HST will be available
249
+ if (options.doctype || registry?.router) {
250
+ provide('hstPathProvider', provideHSTPath);
251
+ provide('hstChangeHandler', handleHSTChange);
252
+ }
253
+ /**
254
+ * Load nested doctype data from API or initialize empty structure
255
+ * @param parentPath - The parent path (e.g., "customer.123.address")
256
+ * @param childDoctype - The child doctype metadata
257
+ * @param recordId - Optional record ID to load
258
+ * @returns Promise resolving to the loaded or initialized data
259
+ */
260
+ const loadNestedData = (parentPath, childDoctype, recordId) => {
261
+ if (!stonecrop.value) {
262
+ return initializeNewRecord(childDoctype);
263
+ }
264
+ // If recordId provided, try to load existing data
265
+ if (recordId) {
266
+ try {
267
+ // Check if data already exists in HST
268
+ const existingData = hstStore.value?.get(parentPath);
269
+ if (existingData && typeof existingData === 'object') {
270
+ return existingData;
271
+ }
272
+ // TODO: Add API fetch logic here if needed
273
+ // For now, initialize new record
274
+ return initializeNewRecord(childDoctype);
275
+ }
276
+ catch {
277
+ return initializeNewRecord(childDoctype);
278
+ }
279
+ }
280
+ // Initialize new record
281
+ return initializeNewRecord(childDoctype);
282
+ };
283
+ /**
284
+ * Recursively save a record with all nested doctype fields
285
+ * @param doctype - The doctype metadata
286
+ * @param recordId - The record ID to save
287
+ * @returns Promise resolving to the complete save payload
288
+ */
289
+ const saveRecursive = async (doctype, recordId) => {
290
+ if (!hstStore.value || !stonecrop.value) {
291
+ throw new Error('HST store not initialized');
292
+ }
293
+ const recordPath = `${doctype.slug}.${recordId}`;
294
+ const recordData = hstStore.value.get(recordPath) || {};
295
+ // Build the save payload using resolved schema
296
+ const payload = { ...recordData };
297
+ // Use resolveSchema to get the full resolved tree, then walk Doctype fields
298
+ const schemaArray = (doctype.schema ? (Array.isArray(doctype.schema) ? doctype.schema : Array.from(doctype.schema)) : []);
299
+ const resolved = registry ? registry.resolveSchema(schemaArray) : schemaArray;
300
+ const doctypeFields = resolved.filter(field => 'fieldtype' in field && field.fieldtype === 'Doctype' && 'schema' in field && Array.isArray(field.schema));
301
+ // Recursively collect nested data from HST using resolved schemas
302
+ for (const field of doctypeFields) {
303
+ const doctypeField = field;
304
+ const fieldPath = `${recordPath}.${doctypeField.fieldname}`;
305
+ const nestedData = collectNestedData(doctypeField.schema, fieldPath, hstStore.value);
306
+ payload[doctypeField.fieldname] = nestedData;
307
+ }
308
+ return payload;
309
+ };
310
+ /**
311
+ * Create a nested context for child forms
312
+ * @param basePath - The base path for the nested context (e.g., "customer.123.address")
313
+ * @param _childDoctype - The child doctype metadata (unused but kept for API consistency)
314
+ * @returns Object with scoped provideHSTPath and handleHSTChange
315
+ */
316
+ const createNestedContext = (basePath, _childDoctype) => {
317
+ const nestedProvideHSTPath = (fieldname) => {
318
+ return `${basePath}.${fieldname}`;
319
+ };
320
+ const nestedHandleHSTChange = (changeData) => {
321
+ // Update the path to be relative to the nested base path
322
+ const nestedPath = changeData.path.startsWith(basePath) ? changeData.path : `${basePath}.${changeData.fieldname}`;
323
+ handleHSTChange({
324
+ ...changeData,
325
+ path: nestedPath,
326
+ });
327
+ };
328
+ return {
329
+ provideHSTPath: nestedProvideHSTPath,
330
+ handleHSTChange: nestedHandleHSTChange,
331
+ };
332
+ };
333
+ // Create operation log API object
334
+ const operationLog = {
335
+ operations,
336
+ currentIndex,
337
+ undoRedoState,
338
+ canUndo,
339
+ canRedo,
340
+ undoCount,
341
+ redoCount,
342
+ undo,
343
+ redo,
344
+ startBatch,
345
+ commitBatch,
346
+ cancelBatch,
347
+ clear,
348
+ getOperationsFor,
349
+ getSnapshot,
350
+ markIrreversible,
351
+ logAction,
352
+ configure,
353
+ };
354
+ // Always return HST functions if doctype is provided or will be loaded from router
355
+ if (options.doctype) {
356
+ // Explicit doctype - return HST immediately
357
+ return {
358
+ stonecrop,
359
+ operationLog,
360
+ provideHSTPath,
361
+ handleHSTChange,
362
+ hstStore,
363
+ formData,
364
+ resolvedSchema,
365
+ loadNestedData,
366
+ saveRecursive,
367
+ createNestedContext,
368
+ };
369
+ }
370
+ else if (!options.doctype && registry?.router) {
371
+ // Router-based - return HST (will be populated after mount)
372
+ return {
373
+ stonecrop,
374
+ operationLog,
375
+ provideHSTPath,
376
+ handleHSTChange,
377
+ hstStore,
378
+ formData,
379
+ resolvedSchema,
380
+ loadNestedData,
381
+ saveRecursive,
382
+ createNestedContext,
383
+ };
384
+ }
385
+ // No doctype and no router - basic mode
386
+ return {
387
+ stonecrop,
388
+ operationLog,
389
+ };
390
+ }
391
+ /**
392
+ * Initialize new record structure based on doctype schema
393
+ */
394
+ function initializeNewRecord(doctype) {
395
+ const initialData = {};
396
+ if (!doctype.schema) {
397
+ return initialData;
398
+ }
399
+ doctype.schema.forEach(field => {
400
+ const fieldtype = 'fieldtype' in field ? field.fieldtype : 'Data';
401
+ switch (fieldtype) {
402
+ case 'Data':
403
+ case 'Text':
404
+ initialData[field.fieldname] = '';
405
+ break;
406
+ case 'Check':
407
+ initialData[field.fieldname] = false;
408
+ break;
409
+ case 'Int':
410
+ case 'Float':
411
+ initialData[field.fieldname] = 0;
412
+ break;
413
+ case 'Table':
414
+ initialData[field.fieldname] = [];
415
+ break;
416
+ case 'JSON':
417
+ initialData[field.fieldname] = {};
418
+ break;
419
+ default:
420
+ initialData[field.fieldname] = null;
421
+ }
422
+ });
423
+ return initialData;
424
+ }
425
+ /**
426
+ * Setup deep reactivity between form data and HST store
427
+ */
428
+ function setupDeepReactivity(doctype, recordId, formData, hstStore) {
429
+ watch(formData, newData => {
430
+ const recordPath = `${doctype.slug}.${recordId}`;
431
+ Object.keys(newData).forEach(fieldname => {
432
+ const path = `${recordPath}.${fieldname}`;
433
+ try {
434
+ hstStore.set(path, newData[fieldname]);
435
+ }
436
+ catch {
437
+ // Silently handle errors
438
+ }
439
+ });
440
+ }, { deep: true });
441
+ }
442
+ /**
443
+ * Update nested object with dot-notation path
444
+ */
445
+ function updateNestedObject(obj, path, value) {
446
+ let current = obj;
447
+ for (let i = 0; i < path.length - 1; i++) {
448
+ const key = path[i];
449
+ if (!(key in current) || typeof current[key] !== 'object') {
450
+ current[key] = isNaN(Number(path[i + 1])) ? {} : [];
451
+ }
452
+ current = current[key];
453
+ }
454
+ const finalKey = path[path.length - 1];
455
+ current[finalKey] = value;
456
+ }
457
+ /**
458
+ * Recursively collect nested data from HST using pre-resolved schemas
459
+ * @param resolvedSchema - The already-resolved schema (with nested schemas embedded)
460
+ * @param basePath - The base path in HST (e.g., "customer.123.address")
461
+ * @param hstStore - The HST store instance
462
+ * @returns The collected data object
463
+ */
464
+ function collectNestedData(resolvedSchema, basePath, hstStore) {
465
+ const data = hstStore.get(basePath) || {};
466
+ const payload = { ...data };
467
+ // Find Doctype fields that have resolved child schemas
468
+ const doctypeFields = resolvedSchema.filter(field => 'fieldtype' in field && field.fieldtype === 'Doctype' && 'schema' in field && Array.isArray(field.schema));
469
+ // Recursively collect nested data
470
+ for (const field of doctypeFields) {
471
+ const doctypeField = field;
472
+ const fieldPath = `${basePath}.${doctypeField.fieldname}`;
473
+ const nestedData = collectNestedData(doctypeField.schema, fieldPath, hstStore);
474
+ payload[doctypeField.fieldname] = nestedData;
475
+ }
476
+ return payload;
477
+ }
@@ -0,0 +1,25 @@
1
+ import Doctype from '../doctype';
2
+ import type { LazyLink } from '../types/composable';
3
+ /**
4
+ * Get the lazy link state for a specific link field on a doctype record.
5
+ *
6
+ * This composable provides reactive state for lazy-loaded links:
7
+ * - `loading`: true while fetching
8
+ * - `loaded`: true after successful fetch (permanent until reload)
9
+ * - `error`: error state if any
10
+ * - `reload()`: explicitly trigger a fetch
11
+ * - `data`: computed from HST, or undefined if not loaded
12
+ *
13
+ * The reload() function respects the link's fetch strategy:
14
+ * - `sync`: fetches via GraphQL query through fetchNestedData
15
+ * - `lazy`: fetches via GraphQL query through fetchNestedData
16
+ * - `custom`: invokes the serialized handler function directly
17
+ *
18
+ * @param doctype - The doctype instance
19
+ * @param recordId - The record ID
20
+ * @param linkFieldname - The link fieldname to load
21
+ * @returns LazyLink with loading, loaded, error, reload, and data
22
+ * @public
23
+ */
24
+ export declare function useLazyLink(doctype: Doctype, recordId: string, linkFieldname: string): LazyLink;
25
+ //# sourceMappingURL=lazy-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-link.d.ts","sourceRoot":"","sources":["../../../src/composables/lazy-link.ts"],"names":[],"mappings":"AAGA,OAAO,OAAO,MAAM,YAAY,CAAA;AAEhC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,QAAQ,CAqH/F"}
@@ -1,4 +1,4 @@
1
- import type { HSTNode } from '../stores/hst';
1
+ import type { HSTNode } from '../types/hst';
2
2
  import type { OperationLogConfig } from '../types/operation-log';
3
3
  /**
4
4
  * Composable for operation log management
@@ -48,8 +48,8 @@ export declare function useOperationLog(config?: Partial<OperationLogConfig>): {
48
48
  actionError?: string | undefined;
49
49
  userId?: string | undefined;
50
50
  metadata?: Record<string, any> | undefined;
51
- parentOperationId?: string | undefined;
52
- childOperationIds?: string[] | undefined;
51
+ ancestorOperationId?: string | undefined;
52
+ descendantOperationIds?: string[] | undefined;
53
53
  }[], import("..").HSTOperation[] | {
54
54
  id: string;
55
55
  type: import("..").HSTOperationType;
@@ -72,8 +72,8 @@ export declare function useOperationLog(config?: Partial<OperationLogConfig>): {
72
72
  actionError?: string | undefined;
73
73
  userId?: string | undefined;
74
74
  metadata?: Record<string, any> | undefined;
75
- parentOperationId?: string | undefined;
76
- childOperationIds?: string[] | undefined;
75
+ ancestorOperationId?: string | undefined;
76
+ descendantOperationIds?: string[] | undefined;
77
77
  }[]>;
78
78
  currentIndex: import("vue").Ref<number, number>;
79
79
  undoRedoState: import("vue").ComputedRef<import("..").UndoRedoState>;
@@ -1 +1 @@
1
- {"version":3,"file":"operation-log.d.ts","sourceRoot":"","sources":["../../../src/composables/operation-log.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAE5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAoB3C,OAAO,KAAG,OAAO;qBAOjB,OAAO,KAAG,OAAO;;gCAcN,MAAM,KAAG,MAAM,GAAG,IAAI;;;gCAqBtB,MAAM,aAAa,MAAM;;oCAgBrB,MAAM,UAAU,MAAM;yBAcnD,MAAM,cACH,MAAM,cACN,MAAM,EAAE,WACZ,SAAS,GAAG,SAAS,GAAG,SAAS,UACjC,MAAM,KACZ,MAAM;yBAQmB,OAAO,CAAC,kBAAkB,CAAC;EA2BvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,UAAO,QAqCrE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYzG"}
1
+ {"version":3,"file":"operation-log.d.ts","sourceRoot":"","sources":["../../../src/composables/operation-log.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAoB3C,OAAO,KAAG,OAAO;qBAOjB,OAAO,KAAG,OAAO;;gCAcN,MAAM,KAAG,MAAM,GAAG,IAAI;;;gCAqBtB,MAAM,aAAa,MAAM;;oCAgBrB,MAAM,UAAU,MAAM;yBAcnD,MAAM,cACH,MAAM,cACN,MAAM,EAAE,WACZ,SAAS,GAAG,SAAS,GAAG,SAAS,UACjC,MAAM,KACZ,MAAM;yBAQmB,OAAO,CAAC,kBAAkB,CAAC;EA2BvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,UAAO,QAqCrE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYzG"}