@stonecrop/stonecrop 0.11.0 → 0.11.1

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 (49) hide show
  1. package/package.json +4 -4
  2. package/dist/composable.js +0 -1
  3. package/dist/composables/use-lazy-link-state.js +0 -125
  4. package/dist/composables/use-stonecrop.js +0 -476
  5. package/dist/operation-log-DB-dGNT9.js +0 -593
  6. package/dist/operation-log-DB-dGNT9.js.map +0 -1
  7. package/dist/src/composable.d.ts +0 -11
  8. package/dist/src/composable.d.ts.map +0 -1
  9. package/dist/src/composable.js +0 -477
  10. package/dist/src/composables/operation-log.js +0 -224
  11. package/dist/src/composables/stonecrop.js +0 -574
  12. package/dist/src/composables/use-lazy-link-state.d.ts +0 -25
  13. package/dist/src/composables/use-lazy-link-state.d.ts.map +0 -1
  14. package/dist/src/composables/use-stonecrop.d.ts +0 -93
  15. package/dist/src/composables/use-stonecrop.d.ts.map +0 -1
  16. package/dist/src/composables/useNestedSchema.d.ts +0 -110
  17. package/dist/src/composables/useNestedSchema.d.ts.map +0 -1
  18. package/dist/src/composables/useNestedSchema.js +0 -155
  19. package/dist/src/doctype.js +0 -234
  20. package/dist/src/exceptions.js +0 -16
  21. package/dist/src/field-triggers.js +0 -567
  22. package/dist/src/index.js +0 -23
  23. package/dist/src/plugins/index.js +0 -96
  24. package/dist/src/registry.js +0 -246
  25. package/dist/src/schema-validator.js +0 -315
  26. package/dist/src/stonecrop.js +0 -339
  27. package/dist/src/stores/data.d.ts +0 -11
  28. package/dist/src/stores/data.d.ts.map +0 -1
  29. package/dist/src/stores/hst.js +0 -495
  30. package/dist/src/stores/index.js +0 -12
  31. package/dist/src/stores/operation-log.js +0 -568
  32. package/dist/src/stores/xstate.d.ts +0 -31
  33. package/dist/src/stores/xstate.d.ts.map +0 -1
  34. package/dist/src/tsdoc-metadata.json +0 -11
  35. package/dist/src/types/field-triggers.js +0 -4
  36. package/dist/src/types/index.js +0 -4
  37. package/dist/src/types/operation-log.js +0 -0
  38. package/dist/src/types/registry.js +0 -0
  39. package/dist/src/utils.d.ts +0 -24
  40. package/dist/src/utils.d.ts.map +0 -1
  41. package/dist/stonecrop.css +0 -1
  42. package/dist/stonecrop.umd.cjs +0 -6
  43. package/dist/stonecrop.umd.cjs.map +0 -1
  44. package/dist/stores/data.js +0 -7
  45. package/dist/stores/xstate.js +0 -29
  46. package/dist/tests/setup.d.ts +0 -5
  47. package/dist/tests/setup.d.ts.map +0 -1
  48. package/dist/tests/setup.js +0 -15
  49. package/dist/utils.js +0 -46
@@ -1,495 +0,0 @@
1
- import { getGlobalTriggerEngine } from '../field-triggers';
2
- import { useOperationLogStore } from './operation-log';
3
- /**
4
- * Get the operation log store if available
5
- */
6
- function getOperationLogStore() {
7
- try {
8
- return useOperationLogStore();
9
- }
10
- catch {
11
- // Operation log is optional
12
- return null;
13
- }
14
- }
15
- /**
16
- * Global HST Manager (Singleton)
17
- * Manages hierarchical state trees and provides access to the global registry.
18
- *
19
- * @public
20
- */
21
- class HST {
22
- static instance;
23
- /**
24
- * Gets the singleton instance of HST
25
- * @returns The HST singleton instance
26
- */
27
- static getInstance() {
28
- if (!HST.instance) {
29
- HST.instance = new HST();
30
- }
31
- return HST.instance;
32
- }
33
- /**
34
- * Gets the global registry instance
35
- * @returns The global registry object or undefined if not found
36
- */
37
- getRegistry() {
38
- // In test environment, try different ways to access Registry
39
- // First, try the global Registry if it exists
40
- if (typeof globalThis !== 'undefined') {
41
- const globalRegistry = globalThis.Registry?._root;
42
- if (globalRegistry) {
43
- return globalRegistry;
44
- }
45
- }
46
- // Try to access through window (browser environment)
47
- if (typeof window !== 'undefined') {
48
- const windowRegistry = window.Registry?._root;
49
- if (windowRegistry) {
50
- return windowRegistry;
51
- }
52
- }
53
- // Try to access through global (Node environment)
54
- if (typeof globalThis !== 'undefined') {
55
- const nodeGlobal = globalThis;
56
- const nodeRegistry = nodeGlobal.Registry?._root;
57
- if (nodeRegistry) {
58
- return nodeRegistry;
59
- }
60
- }
61
- // If we can't find it globally, it might not be set up
62
- // This is expected in test environments where Registry is created locally
63
- return undefined;
64
- }
65
- /**
66
- * Helper method to get doctype metadata from the registry
67
- * @param doctype - The name of the doctype to retrieve metadata for
68
- * @returns The doctype metadata object or undefined if not found
69
- */
70
- getDoctypeMeta(doctype) {
71
- const registry = this.getRegistry();
72
- if (registry && typeof registry === 'object' && 'registry' in registry) {
73
- return registry.registry[doctype];
74
- }
75
- return undefined;
76
- }
77
- }
78
- // Enhanced HST Proxy with tree navigation
79
- class HSTProxy {
80
- target;
81
- parentPath;
82
- rootNode;
83
- doctype;
84
- parentDoctype;
85
- hst;
86
- constructor(target, doctype, parentPath = '', rootNode = null, parentDoctype) {
87
- this.target = target;
88
- this.parentPath = parentPath;
89
- this.rootNode = rootNode || this;
90
- this.doctype = doctype;
91
- this.parentDoctype = parentDoctype;
92
- this.hst = HST.getInstance();
93
- return new Proxy(this, {
94
- get(hst, prop) {
95
- // Return HST methods directly
96
- if (prop in hst)
97
- return hst[prop];
98
- // Handle property access - return tree nodes for navigation
99
- const path = String(prop);
100
- return hst.getNode(path);
101
- },
102
- set(hst, prop, value) {
103
- const path = String(prop);
104
- hst.set(path, value);
105
- return true;
106
- },
107
- });
108
- }
109
- get(path) {
110
- return this.resolveValue(path);
111
- }
112
- // Method to get a tree-wrapped node for navigation
113
- getNode(path) {
114
- const fullPath = this.resolvePath(path);
115
- const value = this.resolveValue(path);
116
- // Determine the correct doctype for this node based on the path
117
- const pathSegments = fullPath.split('.');
118
- let nodeDoctype = this.doctype;
119
- // If we're at the root level and this is a StonecropStore, use the first path segment as the doctype
120
- if (this.doctype === 'StonecropStore' && pathSegments.length >= 1) {
121
- nodeDoctype = pathSegments[0];
122
- }
123
- // Always wrap in HSTProxy for tree navigation
124
- if (typeof value === 'object' && value !== null && !this.isPrimitive(value)) {
125
- return new HSTProxy(value, nodeDoctype, fullPath, this.rootNode, this.parentDoctype);
126
- }
127
- // For primitives, return a minimal wrapper that throws on tree operations
128
- return new HSTProxy(value, nodeDoctype, fullPath, this.rootNode, this.parentDoctype);
129
- }
130
- set(path, value, source = 'user') {
131
- // Get current value for change context
132
- const fullPath = this.resolvePath(path);
133
- const beforeValue = this.has(path) ? this.get(path) : undefined;
134
- // Log operation if not from undo/redo and store is available
135
- if (source !== 'undo' && source !== 'redo') {
136
- const logStore = getOperationLogStore();
137
- if (logStore && typeof logStore.addOperation === 'function') {
138
- const pathSegments = fullPath.split('.');
139
- const doctype = this.doctype === 'StonecropStore' && pathSegments.length >= 1 ? pathSegments[0] : this.doctype;
140
- const recordId = pathSegments.length >= 2 ? pathSegments[1] : undefined;
141
- const fieldname = pathSegments.slice(2).join('.') || pathSegments[pathSegments.length - 1];
142
- // Detect if this is a DELETE operation (setting to undefined when a value existed)
143
- const isDelete = value === undefined && beforeValue !== undefined;
144
- const operationType = isDelete ? 'delete' : 'set';
145
- logStore.addOperation({
146
- type: operationType,
147
- path: fullPath,
148
- fieldname,
149
- beforeValue,
150
- afterValue: value,
151
- doctype,
152
- recordId,
153
- reversible: true, // Default to reversible, can be changed by field triggers
154
- }, source);
155
- }
156
- }
157
- // Update the value
158
- this.updateValue(path, value);
159
- // Trigger field actions asynchronously (don't block the set operation)
160
- void this.triggerFieldActions(fullPath, beforeValue, value);
161
- }
162
- has(path) {
163
- try {
164
- // Handle empty path case
165
- if (path === '') {
166
- return true; // empty path refers to the root object
167
- }
168
- const segments = this.parsePath(path);
169
- let current = this.target;
170
- for (let i = 0; i < segments.length; i++) {
171
- const segment = segments[i];
172
- if (current === null || current === undefined) {
173
- return false;
174
- }
175
- // Check if this is the last segment
176
- if (i === segments.length - 1) {
177
- // For the final property, check if it exists
178
- if (this.isImmutable(current)) {
179
- return current.has(segment);
180
- }
181
- else if (this.isPiniaStore(current)) {
182
- return (current.$state && segment in current.$state) || segment in current;
183
- }
184
- else {
185
- return segment in current;
186
- }
187
- }
188
- // Navigate to the next level
189
- current = this.getProperty(current, segment);
190
- }
191
- return false;
192
- }
193
- catch {
194
- return false;
195
- }
196
- }
197
- // Tree navigation methods
198
- getParent() {
199
- if (!this.parentPath)
200
- return null;
201
- const parentSegments = this.parentPath.split('.').slice(0, -1);
202
- const parentPath = parentSegments.join('.');
203
- if (parentPath === '') {
204
- return this.rootNode;
205
- }
206
- // Return a wrapped node, not raw data
207
- return this.rootNode.getNode(parentPath);
208
- }
209
- getRoot() {
210
- return this.rootNode;
211
- }
212
- getPath() {
213
- return this.parentPath;
214
- }
215
- getDepth() {
216
- return this.parentPath ? this.parentPath.split('.').length : 0;
217
- }
218
- getBreadcrumbs() {
219
- return this.parentPath ? this.parentPath.split('.') : [];
220
- }
221
- /**
222
- * Trigger an XState transition with optional context data
223
- */
224
- async triggerTransition(transition, context) {
225
- const triggerEngine = getGlobalTriggerEngine();
226
- // Determine doctype and recordId from the current path
227
- const pathSegments = this.parentPath.split('.');
228
- let doctype = this.doctype;
229
- let recordId;
230
- // If we're at the root level and this is a StonecropStore, use the first path segment as the doctype
231
- if (this.doctype === 'StonecropStore' && pathSegments.length >= 1) {
232
- doctype = pathSegments[0];
233
- }
234
- // Extract recordId from path if it follows the expected pattern
235
- if (pathSegments.length >= 2) {
236
- recordId = pathSegments[1];
237
- }
238
- // Build transition context
239
- const transitionContext = {
240
- path: this.parentPath,
241
- fieldname: '', // No specific field for transitions
242
- beforeValue: undefined,
243
- afterValue: undefined,
244
- operation: 'set',
245
- doctype,
246
- recordId,
247
- timestamp: new Date(),
248
- store: this.rootNode || undefined,
249
- transition,
250
- currentState: context?.currentState,
251
- targetState: context?.targetState,
252
- fsmContext: context?.fsmContext,
253
- };
254
- // Log FSM transition operation
255
- const logStore = getOperationLogStore();
256
- if (logStore && typeof logStore.addOperation === 'function') {
257
- logStore.addOperation({
258
- type: 'transition',
259
- path: this.parentPath,
260
- fieldname: transition,
261
- beforeValue: context?.currentState,
262
- afterValue: context?.targetState,
263
- doctype,
264
- recordId,
265
- reversible: false, // FSM transitions are generally not reversible
266
- metadata: {
267
- transition,
268
- currentState: context?.currentState,
269
- targetState: context?.targetState,
270
- fsmContext: context?.fsmContext,
271
- },
272
- }, 'user');
273
- }
274
- // Execute transition actions
275
- return await triggerEngine.executeTransitionActions(transitionContext);
276
- }
277
- // Private helper methods
278
- resolvePath(path) {
279
- if (path === '')
280
- return this.parentPath;
281
- return this.parentPath ? `${this.parentPath}.${path}` : path;
282
- }
283
- resolveValue(path) {
284
- // Handle empty path - return the target object
285
- if (path === '') {
286
- return this.target;
287
- }
288
- const segments = this.parsePath(path);
289
- let current = this.target;
290
- for (const segment of segments) {
291
- if (current === null || current === undefined) {
292
- return undefined;
293
- }
294
- current = this.getProperty(current, segment);
295
- }
296
- return current;
297
- }
298
- updateValue(path, value) {
299
- // Handle empty path case - should throw error
300
- if (path === '') {
301
- throw new Error('Cannot set value on empty path');
302
- }
303
- const segments = this.parsePath(path);
304
- const lastSegment = segments.pop();
305
- let current = this.target;
306
- // Navigate to parent object
307
- for (const segment of segments) {
308
- current = this.getProperty(current, segment);
309
- if (current === null || current === undefined) {
310
- throw new Error(`Cannot set property on null/undefined path: ${path}`);
311
- }
312
- }
313
- // Set the final property
314
- this.setProperty(current, lastSegment, value);
315
- }
316
- getProperty(obj, key) {
317
- // Immutable objects
318
- if (this.isImmutable(obj)) {
319
- return obj.get(key);
320
- }
321
- // Vue reactive object
322
- if (this.isVueReactive(obj)) {
323
- return obj[key];
324
- }
325
- // Pinia store
326
- if (this.isPiniaStore(obj)) {
327
- return obj.$state?.[key] ?? obj[key];
328
- }
329
- // Plain object
330
- return obj[key];
331
- }
332
- setProperty(obj, key, value) {
333
- // Immutable objects
334
- if (this.isImmutable(obj)) {
335
- throw new Error('Cannot directly mutate immutable objects. Use immutable update methods instead.');
336
- }
337
- // Pinia store
338
- if (this.isPiniaStore(obj)) {
339
- if (obj.$patch) {
340
- obj.$patch({ [key]: value });
341
- }
342
- else {
343
- ;
344
- obj[key] = value;
345
- }
346
- return;
347
- }
348
- // Vue reactive or plain object
349
- ;
350
- obj[key] = value;
351
- }
352
- async triggerFieldActions(fullPath, beforeValue, afterValue) {
353
- try {
354
- // Guard against undefined or null fullPath
355
- if (!fullPath || typeof fullPath !== 'string') {
356
- return;
357
- }
358
- // Skip triggering when the value did not actually change
359
- if (Object.is(beforeValue, afterValue)) {
360
- return;
361
- }
362
- const pathSegments = fullPath.split('.');
363
- // Only trigger field actions for actual field changes (at least 3 levels deep: doctype.recordId.fieldname)
364
- // Skip triggering for doctype-level or record-level changes
365
- if (pathSegments.length < 3) {
366
- return;
367
- }
368
- const triggerEngine = getGlobalTriggerEngine();
369
- const fieldname = pathSegments.slice(2).join('.') || pathSegments[pathSegments.length - 1];
370
- // Determine the correct doctype for this path using the same logic as getNode()
371
- // The path should be in format: "doctype.recordId.fieldname"
372
- let doctype = this.doctype;
373
- // If we're at the root level and this is a StonecropStore, use the first path segment as the doctype
374
- if (this.doctype === 'StonecropStore' && pathSegments.length >= 1) {
375
- doctype = pathSegments[0];
376
- }
377
- let recordId;
378
- // Extract recordId from path if it follows the expected pattern
379
- if (pathSegments.length >= 2) {
380
- recordId = pathSegments[1];
381
- }
382
- const context = {
383
- path: fullPath,
384
- fieldname,
385
- beforeValue,
386
- afterValue,
387
- operation: 'set',
388
- doctype,
389
- recordId,
390
- timestamp: new Date(),
391
- store: this.rootNode || undefined, // Pass the root store for snapshot/rollback capabilities
392
- };
393
- await triggerEngine.executeFieldTriggers(context);
394
- }
395
- catch (error) {
396
- // Silently handle trigger errors to not break the main flow
397
- // In production, you might want to log this error
398
- if (error instanceof Error) {
399
- // eslint-disable-next-line no-console
400
- console.warn('Field trigger error:', error.message);
401
- // Optional: emit an event or call error handler
402
- }
403
- }
404
- }
405
- isVueReactive(obj) {
406
- return (obj &&
407
- typeof obj === 'object' &&
408
- '__v_isReactive' in obj &&
409
- obj.__v_isReactive === true);
410
- }
411
- isPiniaStore(obj) {
412
- return obj && typeof obj === 'object' && ('$state' in obj || '$patch' in obj || '$id' in obj);
413
- }
414
- isImmutable(obj) {
415
- if (!obj || typeof obj !== 'object') {
416
- return false;
417
- }
418
- const hasGetMethod = 'get' in obj && typeof obj.get === 'function';
419
- const hasSetMethod = 'set' in obj && typeof obj.set === 'function';
420
- const hasHasMethod = 'has' in obj && typeof obj.has === 'function';
421
- const hasImmutableMarkers = '__ownerID' in obj ||
422
- '_map' in obj ||
423
- '_list' in obj ||
424
- '_origin' in obj ||
425
- '_capacity' in obj ||
426
- '_defaultValues' in obj ||
427
- '_tail' in obj ||
428
- '_root' in obj ||
429
- ('size' in obj && hasGetMethod && hasSetMethod);
430
- let constructorName;
431
- try {
432
- const objWithConstructor = obj;
433
- if ('constructor' in objWithConstructor &&
434
- objWithConstructor.constructor &&
435
- typeof objWithConstructor.constructor === 'object' &&
436
- 'name' in objWithConstructor.constructor) {
437
- const nameValue = objWithConstructor.constructor.name;
438
- constructorName = typeof nameValue === 'string' ? nameValue : undefined;
439
- }
440
- }
441
- catch {
442
- constructorName = undefined;
443
- }
444
- const isImmutableConstructor = constructorName &&
445
- (constructorName.includes('Map') ||
446
- constructorName.includes('List') ||
447
- constructorName.includes('Set') ||
448
- constructorName.includes('Stack') ||
449
- constructorName.includes('Seq')) &&
450
- (hasGetMethod || hasSetMethod);
451
- return Boolean((hasGetMethod && hasSetMethod && hasHasMethod && hasImmutableMarkers) ||
452
- (hasGetMethod && hasSetMethod && isImmutableConstructor));
453
- }
454
- isPrimitive(value) {
455
- // Don't wrap primitive values, functions, or null/undefined
456
- return (value === null ||
457
- value === undefined ||
458
- typeof value === 'string' ||
459
- typeof value === 'number' ||
460
- typeof value === 'boolean' ||
461
- typeof value === 'function' ||
462
- typeof value === 'symbol' ||
463
- typeof value === 'bigint');
464
- }
465
- /**
466
- * Parse a path string into segments, handling both dot notation and array bracket notation
467
- * @param path - The path string to parse (e.g., "order.456.line_items[0].product")
468
- * @returns Array of path segments (e.g., ['order', '456', 'line_items', '0', 'product'])
469
- */
470
- parsePath(path) {
471
- if (!path)
472
- return [];
473
- // Replace array bracket notation with dot notation
474
- // items[0] → items.0
475
- // items[0][1] → items.0.1
476
- const normalizedPath = path.replace(/\[(\d+)\]/g, '.$1');
477
- return normalizedPath.split('.').filter(segment => segment.length > 0);
478
- }
479
- }
480
- /**
481
- * Factory function for HST creation
482
- * Creates a new HSTNode proxy for hierarchical state tree navigation.
483
- *
484
- * @param target - The target object to wrap with HST functionality
485
- * @param doctype - The document type identifier
486
- * @param parentDoctype - Optional parent document type identifier
487
- * @returns A new HSTNode proxy instance
488
- *
489
- * @public
490
- */
491
- function createHST(target, doctype, parentDoctype) {
492
- return new HSTProxy(target, doctype, '', null, parentDoctype);
493
- }
494
- // Export everything
495
- export { HSTProxy, HST, createHST };
@@ -1,12 +0,0 @@
1
- import { createPinia } from 'pinia';
2
- import { PiniaSharedState } from 'pinia-shared-state';
3
- import { HST } from './hst';
4
- const hst = HST.getInstance();
5
- const pinia = createPinia();
6
- // Pass the plugin to your application's pinia plugin
7
- pinia.use(PiniaSharedState({
8
- enable: true,
9
- initialize: true,
10
- }));
11
- export { hst, pinia };
12
- export { useOperationLogStore } from './operation-log';