@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,567 +0,0 @@
1
- import { useOperationLogStore } from './stores/operation-log';
2
- /**
3
- * Field trigger execution engine integrated with Registry
4
- * Singleton pattern following Registry implementation
5
- * @public
6
- */
7
- export class FieldTriggerEngine {
8
- /**
9
- * The root FieldTriggerEngine instance
10
- */
11
- static _root;
12
- options = {
13
- defaultTimeout: 5000,
14
- debug: false,
15
- enableRollback: true,
16
- };
17
- doctypeActions = new Map(); // doctype -> action/field -> functions
18
- doctypeTransitions = new Map(); // doctype -> transition -> functions
19
- fieldRollbackConfig = new Map(); // doctype -> field -> rollback enabled
20
- globalActions = new Map(); // action name -> function
21
- globalTransitionActions = new Map(); // transition action name -> function
22
- /**
23
- * Creates a new FieldTriggerEngine instance (singleton pattern)
24
- * @param options - Configuration options for the field trigger engine
25
- */
26
- constructor(options = {}) {
27
- if (FieldTriggerEngine._root) {
28
- return FieldTriggerEngine._root;
29
- }
30
- FieldTriggerEngine._root = this;
31
- this.options = {
32
- defaultTimeout: options.defaultTimeout ?? 5000,
33
- debug: options.debug ?? false,
34
- enableRollback: options.enableRollback ?? true,
35
- errorHandler: options.errorHandler,
36
- };
37
- }
38
- /**
39
- * Register a global action function
40
- * @param name - The name of the action
41
- * @param fn - The action function
42
- */
43
- registerAction(name, fn) {
44
- this.globalActions.set(name, fn);
45
- }
46
- /**
47
- * Register a global XState transition action function
48
- * @param name - The name of the transition action
49
- * @param fn - The transition action function
50
- */
51
- registerTransitionAction(name, fn) {
52
- this.globalTransitionActions.set(name, fn);
53
- }
54
- /**
55
- * Configure rollback behavior for a specific field trigger
56
- * @param doctype - The doctype name
57
- * @param fieldname - The field name
58
- * @param enableRollback - Whether to enable rollback
59
- */
60
- setFieldRollback(doctype, fieldname, enableRollback) {
61
- if (!this.fieldRollbackConfig.has(doctype)) {
62
- this.fieldRollbackConfig.set(doctype, new Map());
63
- }
64
- this.fieldRollbackConfig.get(doctype).set(fieldname, enableRollback);
65
- }
66
- /**
67
- * Get rollback configuration for a specific field trigger
68
- */
69
- getFieldRollback(doctype, fieldname) {
70
- return this.fieldRollbackConfig.get(doctype)?.get(fieldname);
71
- }
72
- /**
73
- * Register actions from a doctype - both regular actions and field triggers
74
- * Separates XState transitions (uppercase) from field triggers (lowercase)
75
- * @param doctype - The doctype name
76
- * @param actions - The actions to register (supports Immutable Map, Map, or plain object)
77
- */
78
- registerDoctypeActions(doctype, actions) {
79
- if (!actions)
80
- return;
81
- const actionMap = new Map();
82
- const transitionMap = new Map();
83
- // Convert from different Map types to regular Map
84
- // Check for Immutable.js Map first (has entrySeq method)
85
- const immutableActions = actions;
86
- if (typeof immutableActions.entrySeq === 'function') {
87
- // Immutable Map
88
- immutableActions.entrySeq().forEach(([key, value]) => {
89
- this.categorizeAction(key, value, actionMap, transitionMap);
90
- });
91
- }
92
- else if (actions instanceof Map) {
93
- // Regular Map
94
- for (const [key, value] of actions) {
95
- this.categorizeAction(key, value, actionMap, transitionMap);
96
- }
97
- }
98
- else if (actions && typeof actions === 'object') {
99
- // Plain object
100
- Object.entries(actions).forEach(([key, value]) => {
101
- this.categorizeAction(key, value, actionMap, transitionMap);
102
- });
103
- }
104
- // Always set the maps, even if empty
105
- this.doctypeActions.set(doctype, actionMap);
106
- this.doctypeTransitions.set(doctype, transitionMap);
107
- }
108
- /**
109
- * Categorize an action as either a field trigger or XState transition
110
- * Uses uppercase convention: UPPERCASE = transition, lowercase/mixed = field trigger
111
- */
112
- categorizeAction(key, value, actionMap, transitionMap) {
113
- // Check if the key is all uppercase (XState transition convention)
114
- if (this.isTransitionKey(key)) {
115
- transitionMap.set(key, value);
116
- }
117
- else {
118
- actionMap.set(key, value);
119
- }
120
- }
121
- /**
122
- * Determine if a key represents an XState transition
123
- * Transitions are identified by being all uppercase
124
- */
125
- isTransitionKey(key) {
126
- // Must be all uppercase letters/numbers/underscores
127
- return /^[A-Z0-9_]+$/.test(key) && key.length > 0;
128
- }
129
- /**
130
- * Execute field triggers for a changed field
131
- * @param context - The field change context
132
- * @param options - Execution options (timeout and enableRollback)
133
- */
134
- async executeFieldTriggers(context, options = {}) {
135
- const { doctype, fieldname } = context;
136
- const triggers = this.findFieldTriggers(doctype, fieldname);
137
- if (triggers.length === 0) {
138
- return {
139
- path: context.path,
140
- actionResults: [],
141
- totalExecutionTime: 0,
142
- allSucceeded: true,
143
- stoppedOnError: false,
144
- rolledBack: false,
145
- };
146
- }
147
- const startTime = performance.now();
148
- const actionResults = [];
149
- let stoppedOnError = false;
150
- let rolledBack = false;
151
- let snapshot = undefined;
152
- // Determine if rollback is enabled (priority: execution option > field config > global setting)
153
- const fieldRollbackConfig = this.getFieldRollback(doctype, fieldname);
154
- const rollbackEnabled = options.enableRollback ?? fieldRollbackConfig ?? this.options.enableRollback;
155
- // Capture snapshot before executing actions if rollback is enabled
156
- if (rollbackEnabled && context.store) {
157
- snapshot = this.captureSnapshot(context);
158
- }
159
- // Execute actions sequentially
160
- for (const actionName of triggers) {
161
- try {
162
- const actionResult = await this.executeAction(actionName, context, options.timeout);
163
- actionResults.push(actionResult);
164
- if (!actionResult.success) {
165
- stoppedOnError = true;
166
- break;
167
- }
168
- }
169
- catch (error) {
170
- const actionError = error instanceof Error ? error : new Error(String(error));
171
- const errorResult = {
172
- success: false,
173
- error: actionError,
174
- executionTime: 0,
175
- action: actionName,
176
- };
177
- actionResults.push(errorResult);
178
- stoppedOnError = true;
179
- break;
180
- }
181
- }
182
- // Perform rollback if enabled, errors occurred, and we have a snapshot
183
- if (rollbackEnabled && stoppedOnError && snapshot && context.store) {
184
- try {
185
- this.restoreSnapshot(context, snapshot);
186
- rolledBack = true;
187
- }
188
- catch (rollbackError) {
189
- // eslint-disable-next-line no-console
190
- console.error('[FieldTriggers] Rollback failed:', rollbackError);
191
- }
192
- }
193
- const totalExecutionTime = performance.now() - startTime;
194
- // Call global error handler if configured and errors occurred
195
- const failedResults = actionResults.filter(r => !r.success);
196
- if (failedResults.length > 0 && this.options.errorHandler) {
197
- for (const failedResult of failedResults) {
198
- try {
199
- this.options.errorHandler(failedResult.error, context, failedResult.action);
200
- }
201
- catch (handlerError) {
202
- // eslint-disable-next-line no-console
203
- console.error('[FieldTriggers] Error in global error handler:', handlerError);
204
- }
205
- }
206
- }
207
- const result = {
208
- path: context.path,
209
- actionResults,
210
- totalExecutionTime,
211
- allSucceeded: actionResults.every(r => r.success),
212
- stoppedOnError,
213
- rolledBack,
214
- snapshot: this.options.debug && rollbackEnabled ? snapshot : undefined, // Only include snapshot in debug mode if rollback is enabled
215
- };
216
- return result;
217
- }
218
- /**
219
- * Execute XState transition actions
220
- * Similar to field triggers but specifically for FSM state transitions
221
- * @param context - The transition change context
222
- * @param options - Execution options (timeout)
223
- */
224
- async executeTransitionActions(context, options = {}) {
225
- const { doctype, transition } = context;
226
- const transitionActions = this.findTransitionActions(doctype, transition);
227
- if (transitionActions.length === 0) {
228
- return [];
229
- }
230
- const results = [];
231
- // Execute transition actions sequentially
232
- for (const actionName of transitionActions) {
233
- try {
234
- const actionResult = await this.executeTransitionAction(actionName, context, options.timeout);
235
- results.push(actionResult);
236
- if (!actionResult.success) {
237
- // Stop on first error for transitions
238
- break;
239
- }
240
- }
241
- catch (error) {
242
- const actionError = error instanceof Error ? error : new Error(String(error));
243
- const errorResult = {
244
- success: false,
245
- error: actionError,
246
- executionTime: 0,
247
- action: actionName,
248
- transition,
249
- };
250
- results.push(errorResult);
251
- break;
252
- }
253
- }
254
- // Call global error handler if configured and errors occurred
255
- const failedResults = results.filter(r => !r.success);
256
- if (failedResults.length > 0 && this.options.errorHandler) {
257
- for (const failedResult of failedResults) {
258
- try {
259
- // Call with FieldChangeContext (base context type)
260
- this.options.errorHandler(failedResult.error, context, failedResult.action);
261
- }
262
- catch (handlerError) {
263
- // eslint-disable-next-line no-console
264
- console.error('[FieldTriggers] Error in global error handler:', handlerError);
265
- }
266
- }
267
- }
268
- return results;
269
- }
270
- /**
271
- * Find transition actions for a specific doctype and transition
272
- */
273
- findTransitionActions(doctype, transition) {
274
- const doctypeTransitions = this.doctypeTransitions.get(doctype);
275
- if (!doctypeTransitions)
276
- return [];
277
- return doctypeTransitions.get(transition) || [];
278
- }
279
- /**
280
- * Execute a single transition action by name
281
- */
282
- async executeTransitionAction(actionName, context, timeout) {
283
- const startTime = performance.now();
284
- const actionTimeout = timeout ?? this.options.defaultTimeout;
285
- try {
286
- // Look up action in transition-specific registry first, then fall back to global
287
- let actionFn = this.globalTransitionActions.get(actionName);
288
- // If not found in transition registry, try regular action registry
289
- // This allows sharing actions between field triggers and transitions
290
- if (!actionFn) {
291
- const regularActionFn = this.globalActions.get(actionName);
292
- if (regularActionFn) {
293
- // Wrap regular action to accept TransitionChangeContext
294
- actionFn = regularActionFn;
295
- }
296
- }
297
- if (!actionFn) {
298
- throw new Error(`Transition action "${actionName}" not found in registry`);
299
- }
300
- await this.executeWithTimeout(actionFn, context, actionTimeout);
301
- const executionTime = performance.now() - startTime;
302
- return {
303
- success: true,
304
- executionTime,
305
- action: actionName,
306
- transition: context.transition,
307
- };
308
- }
309
- catch (error) {
310
- const executionTime = performance.now() - startTime;
311
- const actionError = error instanceof Error ? error : new Error(String(error));
312
- return {
313
- success: false,
314
- error: actionError,
315
- executionTime,
316
- action: actionName,
317
- transition: context.transition,
318
- };
319
- }
320
- }
321
- /**
322
- * Find field triggers for a specific doctype and field
323
- * Field triggers are identified by keys that look like field paths (contain dots or match field names)
324
- */
325
- findFieldTriggers(doctype, fieldname) {
326
- const doctypeActions = this.doctypeActions.get(doctype);
327
- if (!doctypeActions)
328
- return [];
329
- const triggers = [];
330
- for (const [key, actionNames] of doctypeActions) {
331
- // Check if this key is a field trigger pattern
332
- if (this.isFieldTriggerKey(key, fieldname)) {
333
- triggers.push(...actionNames);
334
- }
335
- }
336
- return triggers;
337
- }
338
- /**
339
- * Determine if an action key represents a field trigger
340
- * Field triggers can be:
341
- * - Exact field name match: "emailAddress"
342
- * - Wildcard patterns: "emailAddress.*", "*.is_primary"
343
- * - Nested field paths: "address.street", "contact.email"
344
- */
345
- isFieldTriggerKey(key, fieldname) {
346
- // Exact match
347
- if (key === fieldname)
348
- return true;
349
- // Contains dots - likely a field path pattern
350
- if (key.includes('.')) {
351
- return this.matchFieldPattern(key, fieldname);
352
- }
353
- // Contains wildcards
354
- if (key.includes('*')) {
355
- return this.matchFieldPattern(key, fieldname);
356
- }
357
- return false;
358
- }
359
- /**
360
- * Match a field pattern against a field name
361
- * Supports wildcards (*) for dynamic segments
362
- */
363
- matchFieldPattern(pattern, fieldname) {
364
- const patternParts = pattern.split('.');
365
- const fieldParts = fieldname.split('.');
366
- if (patternParts.length !== fieldParts.length) {
367
- return false;
368
- }
369
- for (let i = 0; i < patternParts.length; i++) {
370
- const patternPart = patternParts[i];
371
- const fieldPart = fieldParts[i];
372
- if (patternPart === '*') {
373
- // Wildcard matches any segment
374
- continue;
375
- }
376
- else if (patternPart !== fieldPart) {
377
- // Exact match required
378
- return false;
379
- }
380
- }
381
- return true;
382
- }
383
- /**
384
- * Execute a single action by name
385
- */
386
- async executeAction(actionName, context, timeout) {
387
- const startTime = performance.now();
388
- const actionTimeout = timeout ?? this.options.defaultTimeout;
389
- try {
390
- // Look up action in global registry
391
- const actionFn = this.globalActions.get(actionName);
392
- if (!actionFn) {
393
- throw new Error(`Action "${actionName}" not found in registry`);
394
- }
395
- await this.executeWithTimeout(actionFn, context, actionTimeout);
396
- const executionTime = performance.now() - startTime;
397
- return {
398
- success: true,
399
- executionTime,
400
- action: actionName,
401
- };
402
- }
403
- catch (error) {
404
- const executionTime = performance.now() - startTime;
405
- const actionError = error instanceof Error ? error : new Error(String(error));
406
- return {
407
- success: false,
408
- error: actionError,
409
- executionTime,
410
- action: actionName,
411
- };
412
- }
413
- }
414
- /**
415
- * Execute a function with timeout
416
- */
417
- async executeWithTimeout(fn, context, timeout) {
418
- return new Promise((resolve, reject) => {
419
- const timeoutId = setTimeout(() => {
420
- reject(new Error(`Action timeout after ${timeout}ms`));
421
- }, timeout);
422
- Promise.resolve(fn(context))
423
- .then(result => {
424
- clearTimeout(timeoutId);
425
- resolve(result);
426
- })
427
- .catch(error => {
428
- clearTimeout(timeoutId);
429
- reject(error instanceof Error ? error : new Error(String(error)));
430
- });
431
- });
432
- }
433
- /**
434
- * Capture a snapshot of the record state before executing actions
435
- * This creates a deep copy of the record data for potential rollback
436
- */
437
- captureSnapshot(context) {
438
- if (!context.store || !context.doctype || !context.recordId) {
439
- return undefined;
440
- }
441
- try {
442
- // Get the record path (doctype.recordId)
443
- const recordPath = `${context.doctype}.${context.recordId}`;
444
- // Get the current record data
445
- const recordData = context.store.get(recordPath);
446
- if (!recordData || typeof recordData !== 'object') {
447
- return undefined;
448
- }
449
- // Create a deep copy to avoid reference issues
450
- return JSON.parse(JSON.stringify(recordData));
451
- }
452
- catch (error) {
453
- if (this.options.debug) {
454
- // eslint-disable-next-line no-console
455
- console.warn('[FieldTriggers] Failed to capture snapshot:', error);
456
- }
457
- return undefined;
458
- }
459
- }
460
- /**
461
- * Restore a previously captured snapshot
462
- * This reverts the record to its state before actions were executed
463
- */
464
- restoreSnapshot(context, snapshot) {
465
- if (!context.store || !context.doctype || !context.recordId || !snapshot) {
466
- return;
467
- }
468
- try {
469
- // Get the record path (doctype.recordId)
470
- const recordPath = `${context.doctype}.${context.recordId}`;
471
- // Restore the entire record from snapshot
472
- context.store.set(recordPath, snapshot);
473
- if (this.options.debug) {
474
- // eslint-disable-next-line no-console
475
- console.log(`[FieldTriggers] Rolled back ${recordPath} to previous state`);
476
- }
477
- }
478
- catch (error) {
479
- // eslint-disable-next-line no-console
480
- console.error('[FieldTriggers] Failed to restore snapshot:', error);
481
- throw error;
482
- }
483
- }
484
- }
485
- /**
486
- * Get or create the global field trigger engine singleton
487
- * @param options - Optional configuration for the field trigger engine
488
- * @public
489
- */
490
- export function getGlobalTriggerEngine(options) {
491
- return new FieldTriggerEngine(options);
492
- }
493
- /**
494
- * Register a global action function that can be used in field triggers
495
- * @param name - The name of the action to register
496
- * @param fn - The action function to execute when the trigger fires
497
- * @public
498
- */
499
- export function registerGlobalAction(name, fn) {
500
- const engine = getGlobalTriggerEngine();
501
- engine.registerAction(name, fn);
502
- }
503
- /**
504
- * Register a global XState transition action function
505
- * @param name - The name of the transition action to register
506
- * @param fn - The transition action function to execute
507
- * @public
508
- */
509
- export function registerTransitionAction(name, fn) {
510
- const engine = getGlobalTriggerEngine();
511
- engine.registerTransitionAction(name, fn);
512
- }
513
- /**
514
- * Configure rollback behavior for a specific field trigger
515
- * @param doctype - The doctype name
516
- * @param fieldname - The field name
517
- * @param enableRollback - Whether to enable automatic rollback for this field
518
- * @public
519
- */
520
- export function setFieldRollback(doctype, fieldname, enableRollback) {
521
- const engine = getGlobalTriggerEngine();
522
- engine.setFieldRollback(doctype, fieldname, enableRollback);
523
- }
524
- /**
525
- * Manually trigger an XState transition for a specific doctype/record
526
- * This can be called directly when you need to execute transition actions programmatically
527
- * @param doctype - The doctype name
528
- * @param transition - The XState transition name to trigger
529
- * @param options - Optional configuration for the transition
530
- * @public
531
- */
532
- export async function triggerTransition(doctype, transition, options) {
533
- const engine = getGlobalTriggerEngine();
534
- const context = {
535
- path: options?.path || (options?.recordId ? `${doctype}.${options.recordId}` : doctype),
536
- fieldname: '',
537
- beforeValue: undefined,
538
- afterValue: undefined,
539
- operation: 'set',
540
- doctype,
541
- recordId: options?.recordId,
542
- timestamp: new Date(),
543
- transition,
544
- currentState: options?.currentState,
545
- targetState: options?.targetState,
546
- fsmContext: options?.fsmContext,
547
- };
548
- return await engine.executeTransitionActions(context);
549
- }
550
- /**
551
- * Mark a specific operation as irreversible.
552
- * Used to prevent undo of critical operations like publishing or deletion.
553
- * @param operationId - The ID of the operation to mark as irreversible
554
- * @param reason - Human-readable reason why the operation cannot be undone
555
- * @public
556
- */
557
- export function markOperationIrreversible(operationId, reason) {
558
- if (!operationId)
559
- return;
560
- try {
561
- const store = useOperationLogStore();
562
- store.markIrreversible(operationId, reason);
563
- }
564
- catch {
565
- // Operation log is optional
566
- }
567
- }
package/dist/src/index.js DELETED
@@ -1,23 +0,0 @@
1
- import { useStonecrop } from './composables/stonecrop';
2
- import { useOperationLog, useUndoRedoShortcuts, withBatch } from './composables/operation-log';
3
- import Doctype from './doctype';
4
- import { getGlobalTriggerEngine, markOperationIrreversible, registerGlobalAction, registerTransitionAction, setFieldRollback, triggerTransition, } from './field-triggers';
5
- import plugin from './plugins';
6
- import Registry from './registry';
7
- import { Stonecrop } from './stonecrop';
8
- import { HST, createHST } from './stores/hst';
9
- import { useOperationLogStore } from './stores/operation-log';
10
- // Export schema validator
11
- import { SchemaValidator, createValidator, validateSchema } from './schema-validator';
12
- export { ValidationSeverity } from './schema-validator';
13
- export { Doctype, Registry, Stonecrop, useStonecrop,
14
- // HST exports for advanced usage
15
- HST, createHST,
16
- // Field trigger system exports
17
- getGlobalTriggerEngine, registerGlobalAction, registerTransitionAction, setFieldRollback, triggerTransition, markOperationIrreversible,
18
- // Schema validator exports
19
- SchemaValidator, createValidator, validateSchema,
20
- // Operation log exports
21
- useOperationLog, useOperationLogStore, useUndoRedoShortcuts, withBatch, };
22
- // Default export is the Vue plugin
23
- export default plugin;
@@ -1,96 +0,0 @@
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;