@stonecrop/stonecrop 0.4.36 → 0.5.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 (81) hide show
  1. package/README.md +92 -3
  2. package/dist/src/composable.d.ts +74 -8
  3. package/dist/src/composable.d.ts.map +1 -1
  4. package/dist/src/composable.js +348 -0
  5. package/dist/src/composables/operation-log.d.ts +136 -0
  6. package/dist/src/composables/operation-log.d.ts.map +1 -0
  7. package/dist/src/composables/operation-log.js +221 -0
  8. package/dist/src/doctype.d.ts +9 -1
  9. package/dist/src/doctype.d.ts.map +1 -1
  10. package/dist/{doctype.js → src/doctype.js} +9 -3
  11. package/dist/src/exceptions.d.ts +2 -3
  12. package/dist/src/exceptions.d.ts.map +1 -1
  13. package/dist/{exceptions.js → src/exceptions.js} +5 -11
  14. package/dist/src/field-triggers.d.ts +178 -0
  15. package/dist/src/field-triggers.d.ts.map +1 -0
  16. package/dist/src/field-triggers.js +564 -0
  17. package/dist/src/index.d.ts +12 -4
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js +18 -0
  20. package/dist/src/plugins/index.d.ts +11 -13
  21. package/dist/src/plugins/index.d.ts.map +1 -1
  22. package/dist/src/plugins/index.js +90 -0
  23. package/dist/src/registry.d.ts +9 -3
  24. package/dist/src/registry.d.ts.map +1 -1
  25. package/dist/{registry.js → src/registry.js} +14 -1
  26. package/dist/src/stonecrop.d.ts +350 -114
  27. package/dist/src/stonecrop.d.ts.map +1 -1
  28. package/dist/src/stonecrop.js +251 -0
  29. package/dist/src/stores/hst.d.ts +157 -0
  30. package/dist/src/stores/hst.d.ts.map +1 -0
  31. package/dist/src/stores/hst.js +483 -0
  32. package/dist/src/stores/index.d.ts +5 -1
  33. package/dist/src/stores/index.d.ts.map +1 -1
  34. package/dist/{stores → src/stores}/index.js +4 -1
  35. package/dist/src/stores/operation-log.d.ts +268 -0
  36. package/dist/src/stores/operation-log.d.ts.map +1 -0
  37. package/dist/src/stores/operation-log.js +571 -0
  38. package/dist/src/types/field-triggers.d.ts +186 -0
  39. package/dist/src/types/field-triggers.d.ts.map +1 -0
  40. package/dist/src/types/field-triggers.js +4 -0
  41. package/dist/src/types/index.d.ts +13 -2
  42. package/dist/src/types/index.d.ts.map +1 -1
  43. package/dist/src/types/index.js +4 -0
  44. package/dist/src/types/operation-log.d.ts +165 -0
  45. package/dist/src/types/operation-log.d.ts.map +1 -0
  46. package/dist/src/types/registry.d.ts +11 -0
  47. package/dist/src/types/registry.d.ts.map +1 -0
  48. package/dist/src/types/registry.js +0 -0
  49. package/dist/stonecrop.d.ts +1555 -159
  50. package/dist/stonecrop.js +1974 -7035
  51. package/dist/stonecrop.js.map +1 -1
  52. package/dist/stonecrop.umd.cjs +4 -8
  53. package/dist/stonecrop.umd.cjs.map +1 -1
  54. package/dist/tests/setup.d.ts +5 -0
  55. package/dist/tests/setup.d.ts.map +1 -0
  56. package/dist/tests/setup.js +15 -0
  57. package/package.json +18 -16
  58. package/src/composable.ts +481 -33
  59. package/src/composables/operation-log.ts +254 -0
  60. package/src/doctype.ts +9 -3
  61. package/src/exceptions.ts +5 -12
  62. package/src/field-triggers.ts +671 -0
  63. package/src/index.ts +50 -4
  64. package/src/plugins/index.ts +70 -22
  65. package/src/registry.ts +18 -3
  66. package/src/stonecrop.ts +246 -151
  67. package/src/stores/hst.ts +703 -0
  68. package/src/stores/index.ts +6 -1
  69. package/src/stores/operation-log.ts +671 -0
  70. package/src/types/field-triggers.ts +201 -0
  71. package/src/types/index.ts +17 -6
  72. package/src/types/operation-log.ts +205 -0
  73. package/src/types/registry.ts +10 -0
  74. package/dist/composable.js +0 -51
  75. package/dist/index.js +0 -6
  76. package/dist/plugins/index.js +0 -49
  77. package/dist/src/stores/data.d.ts +0 -11
  78. package/dist/src/stores/data.d.ts.map +0 -1
  79. package/dist/stores/data.js +0 -7
  80. package/src/stores/data.ts +0 -8
  81. /package/dist/{types/index.js → src/types/operation-log.js} +0 -0
package/README.md CHANGED
@@ -1,6 +1,75 @@
1
1
  # Stonecrop
2
2
  _This package is under active development / design._
3
3
 
4
+ ## Features
5
+
6
+ - **Hierarchical State Tree (HST)**: Advanced state management with tree navigation
7
+ - **Operation Log**: Global undo/redo with time-travel debugging, automatic FSM transition tracking, and action execution tracking
8
+ - **Action Tracking**: Audit trail for stateless action executions (print, email, archive, etc.)
9
+ - **Field Triggers**: Event-driven field actions integrated with XState
10
+ - **VueUse Integration**: Leverages battle-tested VueUse composables for keyboard shortcuts and persistence
11
+
12
+ ## Installation & Usage
13
+
14
+ ### Vue Plugin Installation
15
+
16
+ ```typescript
17
+ import { createApp } from 'vue'
18
+ import Stonecrop from '@stonecrop/stonecrop'
19
+
20
+ const app = createApp(App)
21
+
22
+ // Install the Stonecrop plugin
23
+ app.use(Stonecrop, {
24
+ router,
25
+ components: {
26
+ // Register custom components
27
+ },
28
+ getMeta: async (doctype: string) => {
29
+ // Fetch doctype metadata from your API
30
+ return await fetchDoctypeMeta(doctype)
31
+ }
32
+ })
33
+
34
+ app.mount('#app')
35
+ ```
36
+
37
+ ### Available Imports
38
+
39
+ ```typescript
40
+ // Default export - Vue plugin
41
+ import Stonecrop from '@stonecrop/stonecrop'
42
+
43
+ // Named exports - utilities and classes
44
+ import {
45
+ Stonecrop as StonecropClass, // Core class
46
+ Registry, // Doctype registry
47
+ useStonecrop, // Vue composable
48
+ HST, // Hierarchical State Tree
49
+ createHST, // HST factory
50
+ DoctypeMeta // Doctype metadata class
51
+ } from '@stonecrop/stonecrop'
52
+ ```### Using the Composable
53
+
54
+ ```typescript
55
+ import { useStonecrop } from '@stonecrop/stonecrop'
56
+
57
+ export default {
58
+ setup() {
59
+ const { stonecrop } = useStonecrop()
60
+
61
+ // Access HST store
62
+ const store = stonecrop.value?.getStore()
63
+
64
+ // Work with records
65
+ const records = stonecrop.value?.records('doctype')
66
+ const record = stonecrop.value?.getRecordById('doctype', recordId)
67
+
68
+ return { stonecrop, records, record }
69
+ }
70
+ }
71
+ ```
72
+
4
73
  ## Design
5
74
  A context will define schema, workflow and actions.
6
75
  - Schema describes the data model and layout of the document.
@@ -27,11 +96,9 @@ app.doctype.schema.field.workflow <FSM>
27
96
  app.doctype.schema.field.actions <OrderedSet>
28
97
  app.doctype.schema.field.value <Store>
29
98
  app.doctype.schema.field.value.field.value <Store> // a "sub-form"
30
- app.doctype.schema.field.value.field[0].value <Store> // also a "sub-form", likely representing a table or list
99
+ app.doctype.schema.field.value.field['a:1'].value <Store> // also a "sub-form", representing a table
31
100
  ```
32
101
 
33
- It may make sense to use [automatic injection aliasing](https://vuejs.org/guide/components/provide-inject.html#inject) at the doctype level
34
-
35
102
  ## Base Classes
36
103
  The Doctype aligns with a row, record or object in a database. It is required to specify its schema, a Finite State Machine that informs its workflow and a set of functions that are triggered by that FSM's state transitions.
37
104
 
@@ -60,3 +127,25 @@ Stem is a composable singleton that wraps Registry and provides application leve
60
127
  - User can define `doctype` and schema from UI
61
128
  - Fields are shown as rows in a table
62
129
  - FSM is shown as an editable diagram that validates itself
130
+
131
+ ___
132
+
133
+ # Hierarchical State Tree (HST) Interface Requirements
134
+
135
+ ## Core Requirements
136
+
137
+ ### 1. Data Structure Compatibility
138
+ - **Vue Reactive Objects**: Must work seamlessly with `reactive()`, `ref()`, and `computed()` primitives
139
+ - **Pinia Store Integration**: Compatible with both Options API and Composition API Pinia stores
140
+ - **Immutable Objects**: Support for frozen/immutable configuration objects without breaking reactivity
141
+
142
+ ### 2. Path-Based Addressing System
143
+ - **Dot Notation**: Full support for dot-notation paths (e.g., `"users.123.profile.settings"`)
144
+ - **Dynamic Paths**: Support for programmatically generated path strings (particularly component to HST)
145
+
146
+ ### 3. Hierarchical Navigation
147
+ - **Parent/Child Relationships**: Maintain bidirectional parent-child references
148
+ - **Sibling Access**: Efficient navigation between sibling nodes
149
+ - **Root Access**: Always accessible reference to tree root from any node
150
+ - **Depth Tracking**: Know the depth level of any node in the hierarchy
151
+ - **Breadcrumb Generation**: Generate full path breadcrumbs for any node
@@ -1,19 +1,85 @@
1
- import { Ref } from 'vue';
1
+ import { Ref, ComputedRef } from 'vue';
2
2
  import Registry from './registry';
3
3
  import { Stonecrop } from './stonecrop';
4
+ import DoctypeMeta from './doctype';
5
+ import type { HSTNode } from './stores/hst';
6
+ import type { HSTOperation, OperationLogConfig, OperationLogSnapshot } from './types/operation-log';
4
7
  /**
5
- * Stonecrop composable return type
8
+ * Operation Log API - nested object containing all operation log functionality
6
9
  * @public
7
10
  */
8
- export type StonecropReturn = {
11
+ export type OperationLogAPI = {
12
+ operations: Ref<HSTOperation[]>;
13
+ currentIndex: Ref<number>;
14
+ undoRedoState: ComputedRef<{
15
+ canUndo: boolean;
16
+ canRedo: boolean;
17
+ undoCount: number;
18
+ redoCount: number;
19
+ currentIndex: number;
20
+ }>;
21
+ canUndo: ComputedRef<boolean>;
22
+ canRedo: ComputedRef<boolean>;
23
+ undoCount: ComputedRef<number>;
24
+ redoCount: ComputedRef<number>;
25
+ undo: (hstStore: HSTNode) => boolean;
26
+ redo: (hstStore: HSTNode) => boolean;
27
+ startBatch: () => void;
28
+ commitBatch: (description?: string) => string | null;
29
+ cancelBatch: () => void;
30
+ clear: () => void;
31
+ getOperationsFor: (doctype: string, recordId?: string) => HSTOperation[];
32
+ getSnapshot: () => OperationLogSnapshot;
33
+ markIrreversible: (operationId: string, reason: string) => void;
34
+ logAction: (doctype: string, actionName: string, recordIds?: string[], result?: 'success' | 'failure' | 'pending', error?: string) => string;
35
+ configure: (options: Partial<OperationLogConfig>) => void;
36
+ };
37
+ /**
38
+ * Base Stonecrop composable return type - includes operation log functionality
39
+ * @public
40
+ */
41
+ export type BaseStonecropReturn = {
9
42
  stonecrop: Ref<Stonecrop | undefined>;
43
+ operationLog: OperationLogAPI;
44
+ };
45
+ /**
46
+ * HST-enabled Stonecrop composable return type
47
+ * @public
48
+ */
49
+ export type HSTStonecropReturn = BaseStonecropReturn & {
50
+ provideHSTPath: (fieldname: string, recordId?: string) => string;
51
+ handleHSTChange: (changeData: HSTChangeData) => void;
52
+ hstStore: Ref<HSTNode | undefined>;
53
+ formData: Ref<Record<string, any>>;
10
54
  };
11
55
  /**
12
- * Stonecrop composable
13
- * @param registry - An existing Stonecrop Registry instance
14
- * @returns The Stonecrop instance and a boolean indicating if Stonecrop is setup and ready
15
- * @throws Error if the Stonecrop plugin is not enabled before using the composable
56
+ * HST Change data structure
57
+ * @public
58
+ */
59
+ export type HSTChangeData = {
60
+ path: string;
61
+ value: any;
62
+ fieldname: string;
63
+ recordId?: string;
64
+ };
65
+ /**
66
+ * Unified Stonecrop composable - handles both general operations and HST reactive integration
67
+ *
68
+ * @param options - Configuration options for the composable
69
+ * @returns Stonecrop instance and optional HST integration utilities
70
+ * @public
71
+ */
72
+ export declare function useStonecrop(): BaseStonecropReturn | HSTStonecropReturn;
73
+ /**
74
+ * Unified Stonecrop composable with HST integration for a specific doctype and record
75
+ *
76
+ * @param options - Configuration with doctype and optional recordId
77
+ * @returns Stonecrop instance with full HST integration utilities
16
78
  * @public
17
79
  */
18
- export declare function useStonecrop(registry?: Registry): StonecropReturn;
80
+ export declare function useStonecrop(options: {
81
+ registry?: Registry;
82
+ doctype: DoctypeMeta;
83
+ recordId?: string;
84
+ }): HSTStonecropReturn;
19
85
  //# sourceMappingURL=composable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"composable.d.ts","sourceRoot":"","sources":["../../src/composable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,GAAG,EAAO,MAAM,KAAK,CAAA;AAEjD,OAAO,QAAQ,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGvC;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC7B,SAAS,EAAE,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;CACrC,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,eAAe,CA8CjE"}
1
+ {"version":3,"file":"composable.d.ts","sourceRoot":"","sources":["../../src/composable.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,GAAG,EAAiC,WAAW,EAAE,MAAM,KAAK,CAAA;AAExF,OAAO,QAAQ,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,WAAW,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAG3C,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAEnG;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC7B,UAAU,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAA;IAC/B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,aAAa,EAAE,WAAW,CAAC;QAC1B,OAAO,EAAE,OAAO,CAAA;QAChB,OAAO,EAAE,OAAO,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,MAAM,CAAA;QACjB,YAAY,EAAE,MAAM,CAAA;KACpB,CAAC,CAAA;IACF,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC7B,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IAC7B,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC9B,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC9B,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAA;IACpC,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAA;IACpC,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,WAAW,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACpD,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,YAAY,EAAE,CAAA;IACxE,WAAW,EAAE,MAAM,oBAAoB,CAAA;IACvC,gBAAgB,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/D,SAAS,EAAE,CACV,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,EAC1C,KAAK,CAAC,EAAE,MAAM,KACV,MAAM,CAAA;IACX,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAA;CACzD,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IACjC,SAAS,EAAE,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;IACrC,YAAY,EAAE,eAAe,CAAA;CAC7B,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG;IACtD,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAChE,eAAe,EAAE,CAAC,UAAU,EAAE,aAAa,KAAK,IAAI,CAAA;IACpD,QAAQ,EAAE,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IAClC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;CAClC,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,GAAG,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,mBAAmB,GAAG,kBAAkB,CAAA;AACxE;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACrC,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,OAAO,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB,GAAG,kBAAkB,CAAA"}
@@ -0,0 +1,348 @@
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
+ // Operation log state and methods - will be populated after stonecrop instance is created
20
+ const operations = ref([]);
21
+ const currentIndex = ref(-1);
22
+ const canUndo = computed(() => stonecrop.value?.getOperationLogStore().canUndo ?? false);
23
+ const canRedo = computed(() => stonecrop.value?.getOperationLogStore().canRedo ?? false);
24
+ const undoCount = computed(() => stonecrop.value?.getOperationLogStore().undoCount ?? 0);
25
+ const redoCount = computed(() => stonecrop.value?.getOperationLogStore().redoCount ?? 0);
26
+ const undoRedoState = computed(() => stonecrop.value?.getOperationLogStore().undoRedoState ?? {
27
+ canUndo: false,
28
+ canRedo: false,
29
+ undoCount: 0,
30
+ redoCount: 0,
31
+ currentIndex: -1,
32
+ });
33
+ // Operation log methods
34
+ const undo = (hstStore) => {
35
+ return stonecrop.value?.getOperationLogStore().undo(hstStore) ?? false;
36
+ };
37
+ const redo = (hstStore) => {
38
+ return stonecrop.value?.getOperationLogStore().redo(hstStore) ?? false;
39
+ };
40
+ const startBatch = () => {
41
+ stonecrop.value?.getOperationLogStore().startBatch();
42
+ };
43
+ const commitBatch = (description) => {
44
+ return stonecrop.value?.getOperationLogStore().commitBatch(description) ?? null;
45
+ };
46
+ const cancelBatch = () => {
47
+ stonecrop.value?.getOperationLogStore().cancelBatch();
48
+ };
49
+ const clear = () => {
50
+ stonecrop.value?.getOperationLogStore().clear();
51
+ };
52
+ const getOperationsFor = (doctype, recordId) => {
53
+ return stonecrop.value?.getOperationLogStore().getOperationsFor(doctype, recordId) ?? [];
54
+ };
55
+ const getSnapshot = () => {
56
+ return (stonecrop.value?.getOperationLogStore().getSnapshot() ?? {
57
+ operations: [],
58
+ currentIndex: -1,
59
+ totalOperations: 0,
60
+ reversibleOperations: 0,
61
+ irreversibleOperations: 0,
62
+ });
63
+ };
64
+ const markIrreversible = (operationId, reason) => {
65
+ stonecrop.value?.getOperationLogStore().markIrreversible(operationId, reason);
66
+ };
67
+ const logAction = (doctype, actionName, recordIds, result = 'success', error) => {
68
+ return stonecrop.value?.getOperationLogStore().logAction(doctype, actionName, recordIds, result, error) ?? '';
69
+ };
70
+ const configure = (config) => {
71
+ stonecrop.value?.getOperationLogStore().configure(config);
72
+ };
73
+ // Initialize Stonecrop instance
74
+ onMounted(async () => {
75
+ if (!registry) {
76
+ return;
77
+ }
78
+ stonecrop.value = providedStonecrop || new Stonecrop(registry);
79
+ // Set up reactive refs from operation log store - only if Pinia is available
80
+ try {
81
+ const opLogStore = stonecrop.value.getOperationLogStore();
82
+ const opLogRefs = storeToRefs(opLogStore);
83
+ operations.value = opLogRefs.operations.value;
84
+ currentIndex.value = opLogRefs.currentIndex.value;
85
+ // Watch for changes in operation log state
86
+ watch(() => opLogRefs.operations.value, newOps => {
87
+ operations.value = newOps;
88
+ });
89
+ watch(() => opLogRefs.currentIndex.value, newIndex => {
90
+ currentIndex.value = newIndex;
91
+ });
92
+ }
93
+ catch {
94
+ // Pinia not available (e.g., in tests) - operation log features will not be available
95
+ // Silently fail - operation log is optional
96
+ }
97
+ // Handle router-based setup if no specific doctype provided
98
+ if (!options.doctype && registry.router) {
99
+ const route = registry.router.currentRoute.value;
100
+ // Parse route path - let the application determine the doctype from the route
101
+ if (!route.path)
102
+ return; // Early return if no path available
103
+ const pathSegments = route.path.split('/').filter(segment => segment.length > 0);
104
+ const recordId = pathSegments[1]?.toLowerCase();
105
+ if (pathSegments.length > 0) {
106
+ // Create route context for getMeta function
107
+ const routeContext = {
108
+ path: route.path,
109
+ segments: pathSegments,
110
+ };
111
+ const doctype = await registry.getMeta?.(routeContext);
112
+ if (doctype) {
113
+ registry.addDoctype(doctype);
114
+ stonecrop.value.setup(doctype);
115
+ // Set reactive refs for router-based doctype
116
+ routerDoctype.value = doctype;
117
+ routerRecordId.value = recordId;
118
+ hstStore.value = stonecrop.value.getStore();
119
+ if (recordId && recordId !== 'new') {
120
+ const existingRecord = stonecrop.value.getRecordById(doctype, recordId);
121
+ if (existingRecord) {
122
+ formData.value = existingRecord.get('') || {};
123
+ }
124
+ else {
125
+ try {
126
+ await stonecrop.value.getRecord(doctype, recordId);
127
+ const loadedRecord = stonecrop.value.getRecordById(doctype, recordId);
128
+ if (loadedRecord) {
129
+ formData.value = loadedRecord.get('') || {};
130
+ }
131
+ }
132
+ catch {
133
+ formData.value = initializeNewRecord(doctype);
134
+ }
135
+ }
136
+ }
137
+ else {
138
+ formData.value = initializeNewRecord(doctype);
139
+ }
140
+ if (hstStore.value) {
141
+ setupDeepReactivity(doctype, recordId || 'new', formData, hstStore.value);
142
+ }
143
+ stonecrop.value.runAction(doctype, 'load', recordId ? [recordId] : undefined);
144
+ }
145
+ }
146
+ }
147
+ // Handle HST integration if doctype is provided explicitly
148
+ if (options.doctype) {
149
+ hstStore.value = stonecrop.value.getStore();
150
+ const doctype = options.doctype;
151
+ const recordId = options.recordId;
152
+ if (recordId && recordId !== 'new') {
153
+ const existingRecord = stonecrop.value.getRecordById(doctype, recordId);
154
+ if (existingRecord) {
155
+ formData.value = existingRecord.get('') || {};
156
+ }
157
+ else {
158
+ try {
159
+ await stonecrop.value.getRecord(doctype, recordId);
160
+ const loadedRecord = stonecrop.value.getRecordById(doctype, recordId);
161
+ if (loadedRecord) {
162
+ formData.value = loadedRecord.get('') || {};
163
+ }
164
+ }
165
+ catch {
166
+ formData.value = initializeNewRecord(doctype);
167
+ }
168
+ }
169
+ }
170
+ else {
171
+ formData.value = initializeNewRecord(doctype);
172
+ }
173
+ if (hstStore.value) {
174
+ setupDeepReactivity(doctype, recordId || 'new', formData, hstStore.value);
175
+ }
176
+ }
177
+ });
178
+ // HST integration functions - always created but only populated when HST is available
179
+ const provideHSTPath = (fieldname, customRecordId) => {
180
+ const doctype = options.doctype || routerDoctype.value;
181
+ if (!doctype)
182
+ return '';
183
+ const actualRecordId = customRecordId || options.recordId || routerRecordId.value || 'new';
184
+ return `${doctype.slug}.${actualRecordId}.${fieldname}`;
185
+ };
186
+ const handleHSTChange = (changeData) => {
187
+ const doctype = options.doctype || routerDoctype.value;
188
+ if (!hstStore.value || !stonecrop.value || !doctype) {
189
+ return;
190
+ }
191
+ try {
192
+ const pathParts = changeData.path.split('.');
193
+ if (pathParts.length >= 2) {
194
+ const doctypeSlug = pathParts[0];
195
+ const recordId = pathParts[1];
196
+ if (!hstStore.value.has(`${doctypeSlug}.${recordId}`)) {
197
+ stonecrop.value.addRecord(doctype, recordId, { ...formData.value });
198
+ }
199
+ if (pathParts.length > 3) {
200
+ const recordPath = `${doctypeSlug}.${recordId}`;
201
+ const nestedParts = pathParts.slice(2);
202
+ let currentPath = recordPath;
203
+ for (let i = 0; i < nestedParts.length - 1; i++) {
204
+ currentPath += `.${nestedParts[i]}`;
205
+ if (!hstStore.value.has(currentPath)) {
206
+ const nextPart = nestedParts[i + 1];
207
+ const isArray = !isNaN(Number(nextPart));
208
+ hstStore.value.set(currentPath, isArray ? [] : {});
209
+ }
210
+ }
211
+ }
212
+ }
213
+ hstStore.value.set(changeData.path, changeData.value);
214
+ const fieldParts = changeData.fieldname.split('.');
215
+ const newFormData = { ...formData.value };
216
+ if (fieldParts.length === 1) {
217
+ newFormData[fieldParts[0]] = changeData.value;
218
+ }
219
+ else {
220
+ updateNestedObject(newFormData, fieldParts, changeData.value);
221
+ }
222
+ formData.value = newFormData;
223
+ }
224
+ catch {
225
+ // Silently handle errors
226
+ }
227
+ };
228
+ // Provide injection tokens if HST will be available
229
+ if (options.doctype || registry?.router) {
230
+ provide('hstPathProvider', provideHSTPath);
231
+ provide('hstChangeHandler', handleHSTChange);
232
+ }
233
+ // Create operation log API object
234
+ const operationLog = {
235
+ operations,
236
+ currentIndex,
237
+ undoRedoState,
238
+ canUndo,
239
+ canRedo,
240
+ undoCount,
241
+ redoCount,
242
+ undo,
243
+ redo,
244
+ startBatch,
245
+ commitBatch,
246
+ cancelBatch,
247
+ clear,
248
+ getOperationsFor,
249
+ getSnapshot,
250
+ markIrreversible,
251
+ logAction,
252
+ configure,
253
+ };
254
+ // Always return HST functions if doctype is provided or will be loaded from router
255
+ if (options.doctype) {
256
+ // Explicit doctype - return HST immediately
257
+ return {
258
+ stonecrop,
259
+ operationLog,
260
+ provideHSTPath,
261
+ handleHSTChange,
262
+ hstStore,
263
+ formData,
264
+ };
265
+ }
266
+ else if (!options.doctype && registry?.router) {
267
+ // Router-based - return HST (will be populated after mount)
268
+ return {
269
+ stonecrop,
270
+ operationLog,
271
+ provideHSTPath,
272
+ handleHSTChange,
273
+ hstStore,
274
+ formData,
275
+ };
276
+ }
277
+ // No doctype and no router - basic mode
278
+ return {
279
+ stonecrop,
280
+ operationLog,
281
+ };
282
+ }
283
+ /**
284
+ * Initialize new record structure based on doctype schema
285
+ */
286
+ function initializeNewRecord(doctype) {
287
+ const initialData = {};
288
+ if (!doctype.schema) {
289
+ return initialData;
290
+ }
291
+ doctype.schema.forEach(field => {
292
+ const fieldtype = 'fieldtype' in field ? field.fieldtype : 'Data';
293
+ switch (fieldtype) {
294
+ case 'Data':
295
+ case 'Text':
296
+ initialData[field.fieldname] = '';
297
+ break;
298
+ case 'Check':
299
+ initialData[field.fieldname] = false;
300
+ break;
301
+ case 'Int':
302
+ case 'Float':
303
+ initialData[field.fieldname] = 0;
304
+ break;
305
+ case 'Table':
306
+ initialData[field.fieldname] = [];
307
+ break;
308
+ case 'JSON':
309
+ initialData[field.fieldname] = {};
310
+ break;
311
+ default:
312
+ initialData[field.fieldname] = null;
313
+ }
314
+ });
315
+ return initialData;
316
+ }
317
+ /**
318
+ * Setup deep reactivity between form data and HST store
319
+ */
320
+ function setupDeepReactivity(doctype, recordId, formData, hstStore) {
321
+ watch(formData, newData => {
322
+ const recordPath = `${doctype.slug}.${recordId}`;
323
+ Object.keys(newData).forEach(fieldname => {
324
+ const path = `${recordPath}.${fieldname}`;
325
+ try {
326
+ hstStore.set(path, newData[fieldname]);
327
+ }
328
+ catch {
329
+ // Silently handle errors
330
+ }
331
+ });
332
+ }, { deep: true });
333
+ }
334
+ /**
335
+ * Update nested object with dot-notation path
336
+ */
337
+ function updateNestedObject(obj, path, value) {
338
+ let current = obj;
339
+ for (let i = 0; i < path.length - 1; i++) {
340
+ const key = path[i];
341
+ if (!(key in current) || typeof current[key] !== 'object') {
342
+ current[key] = isNaN(Number(path[i + 1])) ? {} : [];
343
+ }
344
+ current = current[key];
345
+ }
346
+ const finalKey = path[path.length - 1];
347
+ current[finalKey] = value;
348
+ }