@stonecrop/stonecrop 0.11.6 → 0.11.8
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.
- package/dist/{composables → src/composables}/lazy-link.js +1 -1
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/{registry.js → src/registry.js} +0 -2
- package/dist/src/stonecrop.d.ts.map +1 -1
- package/dist/src/stonecrop.js +565 -0
- package/dist/{stores → src/stores}/hst.js +1 -1
- package/dist/src/stores/operation-log.d.ts.map +1 -1
- package/dist/{stores → src/stores}/operation-log.js +1 -1
- package/dist/stonecrop.js +11 -9
- package/dist/stonecrop.js.map +1 -1
- package/dist/stonecrop.tsbuildinfo +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/package.json +22 -22
- package/src/composables/lazy-link.ts +1 -1
- package/src/composables/stonecrop.ts +1 -1
- package/src/field-triggers.ts +1 -1
- package/src/registry.ts +2 -4
- package/src/stonecrop.ts +0 -1
- package/src/stores/hst.ts +1 -1
- package/src/stores/operation-log.ts +11 -9
- /package/dist/{composables → src/composables}/operation-log.js +0 -0
- /package/dist/{composables → src/composables}/stonecrop.js +0 -0
- /package/dist/{doctype.js → src/doctype.js} +0 -0
- /package/dist/{exceptions.js → src/exceptions.js} +0 -0
- /package/dist/{field-triggers.js → src/field-triggers.js} +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{plugins → src/plugins}/index.js +0 -0
- /package/dist/{schema-validator.js → src/schema-validator.js} +0 -0
- /package/dist/{stores → src/stores}/index.js +0 -0
- /package/dist/{types → src/types}/composable.js +0 -0
- /package/dist/{types → src/types}/doctype.js +0 -0
- /package/dist/{types → src/types}/field-triggers.js +0 -0
- /package/dist/{types → src/types}/hst.js +0 -0
- /package/dist/{types → src/types}/index.js +0 -0
- /package/dist/{types → src/types}/operation-log.js +0 -0
- /package/dist/{types → src/types}/plugin.js +0 -0
- /package/dist/{types → src/types}/registry.js +0 -0
- /package/dist/{types → src/types}/schema-validator.js +0 -0
- /package/dist/{types → src/types}/stonecrop.js +0 -0
|
@@ -59,7 +59,7 @@ export function useLazyLink(doctype, recordId, linkFieldname) {
|
|
|
59
59
|
return await fn(stonecropInstance, getLinkPath(), hstStore);
|
|
60
60
|
}
|
|
61
61
|
catch (err) {
|
|
62
|
-
throw new Error(`Custom handler failed: ${err instanceof Error ? err.message : String(err)}
|
|
62
|
+
throw new Error(`Custom handler failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,OAAO,MAAM,WAAW,CAAA;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC5B;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAA;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAa;IAElC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAK;IAE/C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc,CAAqE;IAE3F;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB,CAAgB;IAE3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IAExB;;;;OAIG;gBACS,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IASjG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEpE;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO;IAuB3B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,OAAO,OAAO,MAAM,WAAW,CAAA;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC5B;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAA;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAa;IAElC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAK;IAE/C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc,CAAqE;IAE3F;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB,CAAgB;IAE3C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IAExB;;;;OAIG;gBACS,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IASjG;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEpE;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO;IAuB3B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,EAAE;IA2FrE;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAkDrB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IA6BxB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAqD5D;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAI7C;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAUvF;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAAC,eAAe,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBtG;;;OAGG;IACH,OAAO,CAAC,oBAAoB;CAgC5B"}
|
|
@@ -160,7 +160,6 @@ export default class Registry {
|
|
|
160
160
|
}
|
|
161
161
|
else {
|
|
162
162
|
// One relationship — embed form schema
|
|
163
|
-
// TODO: remove assertion once resolved link output has a dedicated type separate from input schema
|
|
164
163
|
resolvedFields.push({
|
|
165
164
|
...fieldRest,
|
|
166
165
|
label: fieldRest.label || field.fieldname,
|
|
@@ -207,7 +206,6 @@ export default class Registry {
|
|
|
207
206
|
resolved.push(this.buildTableConfig({ ...fieldRest, label: fieldRest.label || field.fieldname }, childSchema, link.component));
|
|
208
207
|
}
|
|
209
208
|
else {
|
|
210
|
-
// TODO: remove assertion once resolved link output has a dedicated type separate from input schema
|
|
211
209
|
resolved.push({
|
|
212
210
|
...fieldRest,
|
|
213
211
|
label: fieldRest.label || field.fieldname,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stonecrop.d.ts","sourceRoot":"","sources":["../../src/stonecrop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAGnD,OAAO,OAAO,MAAM,WAAW,CAAA;AAE/B,OAAO,QAAQ,MAAM,YAAY,CAAA;AACjC,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAGtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAEzD;;;GAGG;AACH,qBAAa,SAAS;IACrB;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,SAAS,CAAA;IAEvB,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,kBAAkB,CAAC,CAAyC;IACpE,OAAO,CAAC,mBAAmB,CAAC,CAA6B;IACzD,OAAO,CAAC,OAAO,CAAC,CAAY;IAE5B,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAG,QAAQ,CAAA;IAE5B;;;;;OAKG;gBACS,QAAQ,EAAE,QAAQ,EAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB;IAmB5G;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAInC;;;OAGG;IACH,SAAS,IAAI,UAAU,GAAG,SAAS;IAInC;;;OAGG;IACH,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAUpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;
|
|
1
|
+
{"version":3,"file":"stonecrop.d.ts","sourceRoot":"","sources":["../../src/stonecrop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
import { reactive } from 'vue';
|
|
2
|
+
import { getGlobalTriggerEngine } from './field-triggers';
|
|
3
|
+
import { createHST } from './stores/hst';
|
|
4
|
+
import { useOperationLogStore } from './stores/operation-log';
|
|
5
|
+
/**
|
|
6
|
+
* Main Stonecrop class with HST integration and built-in Operation Log
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export class Stonecrop {
|
|
10
|
+
/**
|
|
11
|
+
* Singleton instance of Stonecrop. Only one Stonecrop instance can exist
|
|
12
|
+
* per application, ensuring consistent HST state and registry access.
|
|
13
|
+
* Subsequent constructor calls return this instance instead of creating new ones.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
static _root;
|
|
17
|
+
/** The HST store instance for reactive state management */
|
|
18
|
+
hstStore;
|
|
19
|
+
_operationLogStore;
|
|
20
|
+
_operationLogConfig;
|
|
21
|
+
_client;
|
|
22
|
+
/** The registry instance containing all doctype definitions */
|
|
23
|
+
registry;
|
|
24
|
+
/**
|
|
25
|
+
* Creates a new Stonecrop instance with HST integration (singleton pattern)
|
|
26
|
+
* @param registry - The Registry instance containing doctype definitions
|
|
27
|
+
* @param operationLogConfig - Optional configuration for the operation log
|
|
28
|
+
* @param options - Options including the data client (can be set later via setClient)
|
|
29
|
+
*/
|
|
30
|
+
constructor(registry, operationLogConfig, options) {
|
|
31
|
+
if (Stonecrop._root) {
|
|
32
|
+
return Stonecrop._root;
|
|
33
|
+
}
|
|
34
|
+
Stonecrop._root = this;
|
|
35
|
+
this.registry = registry;
|
|
36
|
+
// Store config for lazy initialization
|
|
37
|
+
this._operationLogConfig = operationLogConfig;
|
|
38
|
+
// Store data client (can be set later via setClient)
|
|
39
|
+
this._client = options?.client;
|
|
40
|
+
// Initialize HST store with auto-sync to Registry
|
|
41
|
+
this.initializeHSTStore();
|
|
42
|
+
this.setupRegistrySync();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Set the data client for fetching doctype metadata and records.
|
|
46
|
+
* Use this for deferred configuration in Nuxt/Vue plugin setups.
|
|
47
|
+
*
|
|
48
|
+
* @param client - DataClient implementation (e.g., StonecropClient from \@stonecrop/graphql-client)
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const { setClient } = useStonecropRegistry()
|
|
53
|
+
* const client = new StonecropClient({ endpoint: '/graphql' })
|
|
54
|
+
* setClient(client)
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
setClient(client) {
|
|
58
|
+
this._client = client;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the current data client
|
|
62
|
+
* @returns The DataClient instance or undefined if not set
|
|
63
|
+
*/
|
|
64
|
+
getClient() {
|
|
65
|
+
return this._client;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get the operation log store (lazy initialization)
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
getOperationLogStore() {
|
|
72
|
+
if (!this._operationLogStore) {
|
|
73
|
+
this._operationLogStore = useOperationLogStore();
|
|
74
|
+
if (this._operationLogConfig) {
|
|
75
|
+
this._operationLogStore.configure(this._operationLogConfig);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return this._operationLogStore;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Initialize the HST store structure
|
|
82
|
+
*/
|
|
83
|
+
initializeHSTStore() {
|
|
84
|
+
const initialStoreStructure = {};
|
|
85
|
+
// Auto-populate from existing Registry doctypes
|
|
86
|
+
Object.keys(this.registry.registry).forEach(doctypeSlug => {
|
|
87
|
+
initialStoreStructure[doctypeSlug] = {};
|
|
88
|
+
});
|
|
89
|
+
// Wrap the store in Vue's reactive() for automatic change detection
|
|
90
|
+
// This enables Vue computed properties to track HST store changes
|
|
91
|
+
this.hstStore = createHST(reactive(initialStoreStructure), 'StonecropStore');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Setup automatic sync with Registry when doctypes are added
|
|
95
|
+
*/
|
|
96
|
+
setupRegistrySync() {
|
|
97
|
+
// Extend Registry.addDoctype to auto-create HST store sections
|
|
98
|
+
const originalAddDoctype = this.registry.addDoctype.bind(this.registry);
|
|
99
|
+
this.registry.addDoctype = (doctype) => {
|
|
100
|
+
// Call original method
|
|
101
|
+
originalAddDoctype(doctype);
|
|
102
|
+
// Auto-create HST store section for new doctype
|
|
103
|
+
if (!this.hstStore.has(doctype.slug)) {
|
|
104
|
+
this.hstStore.set(doctype.slug, {});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get records hash for a doctype
|
|
110
|
+
* @param doctype - The doctype to get records for
|
|
111
|
+
* @returns HST node containing records hash
|
|
112
|
+
*/
|
|
113
|
+
records(doctype) {
|
|
114
|
+
const slug = typeof doctype === 'string' ? doctype : doctype.slug;
|
|
115
|
+
this.ensureDoctypeExists(slug);
|
|
116
|
+
return this.hstStore.getNode(slug);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Add a record to the store
|
|
120
|
+
* @param doctype - The doctype
|
|
121
|
+
* @param recordId - The record ID
|
|
122
|
+
* @param recordData - The record data
|
|
123
|
+
*/
|
|
124
|
+
addRecord(doctype, recordId, recordData) {
|
|
125
|
+
const slug = typeof doctype === 'string' ? doctype : doctype.slug;
|
|
126
|
+
this.ensureDoctypeExists(slug);
|
|
127
|
+
// Store raw record data - let HST handle wrapping with proper hierarchy
|
|
128
|
+
this.hstStore.set(`${slug}.${recordId}`, recordData);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get a specific record
|
|
132
|
+
* @param doctype - The doctype
|
|
133
|
+
* @param recordId - The record ID
|
|
134
|
+
* @returns HST node for the record or undefined
|
|
135
|
+
*/
|
|
136
|
+
getRecordById(doctype, recordId) {
|
|
137
|
+
const slug = typeof doctype === 'string' ? doctype : doctype.slug;
|
|
138
|
+
this.ensureDoctypeExists(slug);
|
|
139
|
+
// First check if the record exists
|
|
140
|
+
const recordExists = this.hstStore.has(`${slug}.${recordId}`);
|
|
141
|
+
if (!recordExists) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
// Check if the actual value is undefined (i.e., record was removed)
|
|
145
|
+
const recordValue = this.hstStore.get(`${slug}.${recordId}`);
|
|
146
|
+
if (recordValue === undefined) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
// Use getNode to get the properly wrapped HST node with correct ancestor relationships
|
|
150
|
+
return this.hstStore.getNode(`${slug}.${recordId}`);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Remove a record from the store
|
|
154
|
+
* @param doctype - The doctype
|
|
155
|
+
* @param recordId - The record ID
|
|
156
|
+
*/
|
|
157
|
+
removeRecord(doctype, recordId) {
|
|
158
|
+
const slug = typeof doctype === 'string' ? doctype : doctype.slug;
|
|
159
|
+
this.ensureDoctypeExists(slug);
|
|
160
|
+
// Remove the specific record directly by setting to undefined
|
|
161
|
+
if (this.hstStore.has(`${slug}.${recordId}`)) {
|
|
162
|
+
this.hstStore.set(`${slug}.${recordId}`, undefined);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get all record IDs for a doctype
|
|
167
|
+
* @param doctype - The doctype
|
|
168
|
+
* @returns Array of record IDs
|
|
169
|
+
*/
|
|
170
|
+
getRecordIds(doctype) {
|
|
171
|
+
const slug = typeof doctype === 'string' ? doctype : doctype.slug;
|
|
172
|
+
this.ensureDoctypeExists(slug);
|
|
173
|
+
const doctypeNode = this.hstStore.get(slug);
|
|
174
|
+
if (!doctypeNode || typeof doctypeNode !== 'object') {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
return Object.keys(doctypeNode).filter(key => doctypeNode[key] !== undefined);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Clear all records for a doctype
|
|
181
|
+
* @param doctype - The doctype
|
|
182
|
+
*/
|
|
183
|
+
clearRecords(doctype) {
|
|
184
|
+
const slug = typeof doctype === 'string' ? doctype : doctype.slug;
|
|
185
|
+
this.ensureDoctypeExists(slug);
|
|
186
|
+
// Get all record IDs and remove them
|
|
187
|
+
const recordIds = this.getRecordIds(slug);
|
|
188
|
+
recordIds.forEach(recordId => {
|
|
189
|
+
this.hstStore.set(`${slug}.${recordId}`, undefined);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Setup method for doctype initialization
|
|
194
|
+
* @param doctype - The doctype to setup
|
|
195
|
+
*/
|
|
196
|
+
setup(doctype) {
|
|
197
|
+
// Ensure doctype exists in store
|
|
198
|
+
this.ensureDoctypeExists(doctype.slug);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Run action on doctype
|
|
202
|
+
* Executes the action and logs it to the operation log for audit tracking
|
|
203
|
+
* @param doctype - The doctype
|
|
204
|
+
* @param action - The action to run
|
|
205
|
+
* @param args - Action arguments (typically record IDs)
|
|
206
|
+
*/
|
|
207
|
+
runAction(doctype, action, args) {
|
|
208
|
+
const registry = this.registry.registry[doctype.slug];
|
|
209
|
+
const actions = registry?.actions?.get(action);
|
|
210
|
+
const recordIds = Array.isArray(args) ? args.filter((arg) => typeof arg === 'string') : undefined;
|
|
211
|
+
const recordId = recordIds?.[0];
|
|
212
|
+
// Check if workflow is ready (all blocked links have data)
|
|
213
|
+
const workflowStatus = recordId ? this.isWorkflowReady(doctype, recordId) : { ready: true };
|
|
214
|
+
if (!workflowStatus.ready) {
|
|
215
|
+
const opLogStore = this.getOperationLogStore();
|
|
216
|
+
opLogStore.logAction(doctype.doctype, action, recordIds, 'failure', `BLOCKED: missing data for links: ${workflowStatus.blockedLinks?.join(', ')}`);
|
|
217
|
+
throw new Error(`Workflow blocked: missing data for links: ${workflowStatus.blockedLinks?.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
// Log action execution start
|
|
220
|
+
const opLogStore = this.getOperationLogStore();
|
|
221
|
+
let actionResult = 'success';
|
|
222
|
+
let actionError;
|
|
223
|
+
try {
|
|
224
|
+
// Execute action functions
|
|
225
|
+
if (actions && actions.length > 0) {
|
|
226
|
+
const engine = getGlobalTriggerEngine();
|
|
227
|
+
actions.forEach(actionStr => {
|
|
228
|
+
try {
|
|
229
|
+
const actionFn = engine.getAction(actionStr);
|
|
230
|
+
if (!actionFn)
|
|
231
|
+
throw new Error(`Action "${actionStr}" is not registered in FieldTriggerEngine`);
|
|
232
|
+
const context = {
|
|
233
|
+
path: `${doctype.slug}.${recordIds?.[0] ?? ''}`,
|
|
234
|
+
fieldname: action,
|
|
235
|
+
beforeValue: undefined,
|
|
236
|
+
afterValue: args,
|
|
237
|
+
operation: 'set',
|
|
238
|
+
doctype: doctype.doctype,
|
|
239
|
+
recordId: recordId,
|
|
240
|
+
timestamp: new Date(),
|
|
241
|
+
};
|
|
242
|
+
void actionFn(context);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
actionResult = 'failure';
|
|
246
|
+
actionError = error instanceof Error ? error.message : 'Unknown error';
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Error already set in inner catch
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
// Log the action execution to operation log
|
|
257
|
+
opLogStore.logAction(doctype.doctype, action, recordIds, actionResult, actionError);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get the effective blockWorkflows value for a link.
|
|
262
|
+
* Returns true if blockWorkflows is explicitly true, or if it's absent and fetch method is 'sync'.
|
|
263
|
+
* @param link - The link declaration
|
|
264
|
+
* @returns Whether workflows should be blocked until this link is loaded
|
|
265
|
+
*/
|
|
266
|
+
getEffectiveBlockWorkflows(link) {
|
|
267
|
+
if (link.blockWorkflows !== undefined) {
|
|
268
|
+
return link.blockWorkflows;
|
|
269
|
+
}
|
|
270
|
+
// TODO: For custom fetch handlers, this returns false (not blocking), but the custom handler
|
|
271
|
+
// may still be invoked by useLazyLink. Future: custom handlers should be able to declare they
|
|
272
|
+
// satisfy blockWorkflows, or validation should reject custom + blockWorkflows: true.
|
|
273
|
+
// See: relationships.md Phase 6 "Open Question: blockWorkflows + custom fetch"
|
|
274
|
+
return link.fetch?.method === 'sync';
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Check if workflow actions are ready to run (all required link data is loaded).
|
|
278
|
+
* A link's data is considered loaded if it exists in HST at `slug.recordId.linkname`.
|
|
279
|
+
* @param doctype - The doctype to check
|
|
280
|
+
* @param recordId - The record ID
|
|
281
|
+
* @returns Object with `ready: true` if all blocked links are loaded, or `ready: false` with `blockedLinks` array
|
|
282
|
+
*/
|
|
283
|
+
isWorkflowReady(doctype, recordId) {
|
|
284
|
+
// New records don't block workflows - they haven't been saved yet
|
|
285
|
+
if (recordId === 'new') {
|
|
286
|
+
return { ready: true };
|
|
287
|
+
}
|
|
288
|
+
const links = this.registry.getDescendantLinks(doctype.slug);
|
|
289
|
+
const blockedLinks = [];
|
|
290
|
+
for (const link of links) {
|
|
291
|
+
if (this.getEffectiveBlockWorkflows(link)) {
|
|
292
|
+
const linkPath = `${doctype.slug}.${recordId}.${link.fieldname}`;
|
|
293
|
+
if (!this.hstStore.has(linkPath)) {
|
|
294
|
+
blockedLinks.push(link.fieldname);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (blockedLinks.length > 0) {
|
|
299
|
+
return { ready: false, blockedLinks };
|
|
300
|
+
}
|
|
301
|
+
return { ready: true };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get records from server using the configured data client.
|
|
305
|
+
* @param doctype - The doctype
|
|
306
|
+
* @throws Error if no data client has been configured
|
|
307
|
+
*/
|
|
308
|
+
async getRecords(doctype) {
|
|
309
|
+
if (!this._client) {
|
|
310
|
+
throw new Error('No data client configured. Call setClient() with a DataClient implementation ' +
|
|
311
|
+
'(e.g., StonecropClient from @stonecrop/graphql-client) before fetching records.');
|
|
312
|
+
}
|
|
313
|
+
const records = await this._client.getRecords(doctype);
|
|
314
|
+
// Store each record in HST
|
|
315
|
+
records.forEach(record => {
|
|
316
|
+
if (record.id) {
|
|
317
|
+
this.addRecord(doctype, record.id, record);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get single record from server using the configured data client.
|
|
323
|
+
* @param doctype - The doctype
|
|
324
|
+
* @param recordId - The record ID
|
|
325
|
+
* @throws Error if no data client has been configured
|
|
326
|
+
*/
|
|
327
|
+
async getRecord(doctype, recordId) {
|
|
328
|
+
if (!this._client) {
|
|
329
|
+
throw new Error('No data client configured. Call setClient() with a DataClient implementation ' +
|
|
330
|
+
'(e.g., StonecropClient from @stonecrop/graphql-client) before fetching records.');
|
|
331
|
+
}
|
|
332
|
+
const record = await this._client.getRecord(doctype, recordId);
|
|
333
|
+
if (record) {
|
|
334
|
+
this.addRecord(doctype, recordId, record);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Dispatch an action to the server via the configured data client.
|
|
339
|
+
* All state changes flow through this single mutation endpoint.
|
|
340
|
+
*
|
|
341
|
+
* @param doctype - The doctype
|
|
342
|
+
* @param action - Action name to execute (e.g., 'SUBMIT', 'APPROVE', 'save')
|
|
343
|
+
* @param args - Action arguments (typically record ID and/or form data)
|
|
344
|
+
* @returns Action result with success status, response data, and any error
|
|
345
|
+
* @throws Error if no data client has been configured
|
|
346
|
+
*/
|
|
347
|
+
async dispatchAction(doctype, action, args) {
|
|
348
|
+
if (!this._client) {
|
|
349
|
+
throw new Error('No data client configured. Call setClient() with a DataClient implementation ' +
|
|
350
|
+
'(e.g., StonecropClient from @stonecrop/graphql-client) before dispatching actions.');
|
|
351
|
+
}
|
|
352
|
+
return this._client.runAction(doctype, action, args);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Ensure doctype section exists in HST store
|
|
356
|
+
* @param slug - The doctype slug
|
|
357
|
+
*/
|
|
358
|
+
ensureDoctypeExists(slug) {
|
|
359
|
+
if (!this.hstStore.has(slug)) {
|
|
360
|
+
this.hstStore.set(slug, {});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Get doctype metadata from the registry
|
|
365
|
+
* @param context - The route context
|
|
366
|
+
* @returns The doctype metadata
|
|
367
|
+
*/
|
|
368
|
+
async getMeta(context) {
|
|
369
|
+
if (!this.registry.getMeta) {
|
|
370
|
+
throw new Error('No getMeta function provided to Registry');
|
|
371
|
+
}
|
|
372
|
+
return await this.registry.getMeta(context);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get the root HST store node for advanced usage
|
|
376
|
+
* @returns Root HST node
|
|
377
|
+
*/
|
|
378
|
+
getStore() {
|
|
379
|
+
return this.hstStore;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Determine the current workflow state for a record.
|
|
383
|
+
*
|
|
384
|
+
* Reads the record's `status` field from the HST store. If the field is absent or
|
|
385
|
+
* empty the doctype's declared `workflow.initial` state is used as the fallback,
|
|
386
|
+
* giving callers a reliable state name without having to duplicate that logic.
|
|
387
|
+
*
|
|
388
|
+
* @param doctype - The doctype slug or Doctype instance
|
|
389
|
+
* @param recordId - The record identifier
|
|
390
|
+
* @returns The current state name, or an empty string if the doctype has no workflow
|
|
391
|
+
*
|
|
392
|
+
* @public
|
|
393
|
+
*/
|
|
394
|
+
getRecordState(doctype, recordId) {
|
|
395
|
+
const slug = typeof doctype === 'string' ? doctype : doctype.slug;
|
|
396
|
+
const meta = this.registry.getDoctype(slug);
|
|
397
|
+
if (!meta?.workflow)
|
|
398
|
+
return '';
|
|
399
|
+
const record = this.getRecordById(slug, recordId);
|
|
400
|
+
const status = record?.get('status');
|
|
401
|
+
// Handle both XState format and WorkflowMeta format
|
|
402
|
+
const workflow = meta.workflow;
|
|
403
|
+
let initialState;
|
|
404
|
+
if (Array.isArray(workflow.states)) {
|
|
405
|
+
// WorkflowMeta format: states is a string array
|
|
406
|
+
initialState = workflow.states[0] ?? '';
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
// XState format: states is an object, use initial or first key
|
|
410
|
+
initialState =
|
|
411
|
+
typeof workflow.initial === 'string'
|
|
412
|
+
? workflow.initial
|
|
413
|
+
: Object.keys(workflow.states ?? {})[0] ?? '';
|
|
414
|
+
}
|
|
415
|
+
return status || initialState;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Collect a record payload with all nested doctype fields from HST
|
|
419
|
+
* @param doctype - The doctype metadata
|
|
420
|
+
* @param recordId - The record ID to collect
|
|
421
|
+
* @returns The complete record payload ready for API submission
|
|
422
|
+
* @public
|
|
423
|
+
*/
|
|
424
|
+
collectRecordPayload(doctype, recordId) {
|
|
425
|
+
const recordPath = `${doctype.slug}.${recordId}`;
|
|
426
|
+
const recordData = this.hstStore.get(recordPath) || {};
|
|
427
|
+
const payload = { ...recordData };
|
|
428
|
+
// Collect nested data from links
|
|
429
|
+
if (doctype.links) {
|
|
430
|
+
for (const [fieldname, link] of Object.entries(doctype.links)) {
|
|
431
|
+
const fieldPath = `${recordPath}.${fieldname}`;
|
|
432
|
+
const isMany = link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne';
|
|
433
|
+
if (isMany) {
|
|
434
|
+
const arrayData = this.hstStore.get(fieldPath);
|
|
435
|
+
if (Array.isArray(arrayData)) {
|
|
436
|
+
payload[fieldname] = arrayData;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const targetDoctype = this.registry.getDoctype(link.target);
|
|
441
|
+
if (targetDoctype?.links) {
|
|
442
|
+
payload[fieldname] = this.collectNestedData(fieldPath, targetDoctype);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
payload[fieldname] = this.hstStore.get(fieldPath) || {};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return payload;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Scaffold empty descendant records from defaults for all descendant links.
|
|
454
|
+
*
|
|
455
|
+
* Initializes all scalar and link fields at their HST paths with default values.
|
|
456
|
+
* For new records, call this after setting up the doctype to ensure all paths exist.
|
|
457
|
+
*
|
|
458
|
+
* @param path - HST path (e.g., "customer.new")
|
|
459
|
+
* @param doctype - The doctype to initialize
|
|
460
|
+
* @public
|
|
461
|
+
*/
|
|
462
|
+
initializeNestedData(path, doctype) {
|
|
463
|
+
const slug = doctype.slug;
|
|
464
|
+
this.ensureDoctypeExists(slug);
|
|
465
|
+
// Resolve schema and initialize with defaults
|
|
466
|
+
const resolvedSchema = this.registry.resolveSchema(doctype);
|
|
467
|
+
const record = this.registry.initializeRecord(resolvedSchema);
|
|
468
|
+
// Ensure the ancestor path exists in HST before setting descendant fields
|
|
469
|
+
const existingData = this.hstStore.get(path);
|
|
470
|
+
if (!existingData) {
|
|
471
|
+
this.hstStore.set(path, {}, 'system');
|
|
472
|
+
}
|
|
473
|
+
// Store each field at its own HST path
|
|
474
|
+
for (const [key, value] of Object.entries(record)) {
|
|
475
|
+
this.hstStore.set(`${path}.${key}`, value, 'system');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Fetch a record and its nested data from the server.
|
|
480
|
+
*
|
|
481
|
+
* Calls `_client.getRecord()` with nested sub-selections and stores each scalar field at its own HST path
|
|
482
|
+
* (`slug.recordId.fieldname`), descendants at the link-level path (`slug.recordId.linkname`).
|
|
483
|
+
*
|
|
484
|
+
* @param path - HST path (e.g., "recipe.r1")
|
|
485
|
+
* @param doctype - The doctype to fetch
|
|
486
|
+
* @param recordId - Record ID to fetch
|
|
487
|
+
* @param options - Query options (includeNested to control which links are fetched)
|
|
488
|
+
* @throws Error with code `"CLIENT_REQUIRED"` if no data client is configured
|
|
489
|
+
* @throws Error with code `"RECORD_NOT_FOUND"` if the server returns null
|
|
490
|
+
* @public
|
|
491
|
+
*/
|
|
492
|
+
async fetchNestedData(path, doctype, recordId, options) {
|
|
493
|
+
if (!this._client) {
|
|
494
|
+
throw createCodedError('No data client configured. Call setClient() with a DataClient implementation ' +
|
|
495
|
+
'(e.g., StonecropClient from @stonecrop/graphql-client) before fetching records.', 'CLIENT_REQUIRED');
|
|
496
|
+
}
|
|
497
|
+
const record = await this._client.getRecord({ name: doctype.doctype }, recordId, {
|
|
498
|
+
includeNested: options?.includeNested ?? true,
|
|
499
|
+
});
|
|
500
|
+
if (!record) {
|
|
501
|
+
throw createCodedError(`Record not found: ${doctype.doctype} ${recordId}`, 'RECORD_NOT_FOUND');
|
|
502
|
+
}
|
|
503
|
+
// Store each scalar field at its own HST path, descendants at link-level path
|
|
504
|
+
const slug = doctype.slug;
|
|
505
|
+
this.ensureDoctypeExists(slug);
|
|
506
|
+
// Ensure the ancestor path exists in HST before setting descendant fields
|
|
507
|
+
const existingData = this.hstStore.get(`${slug}.${recordId}`);
|
|
508
|
+
if (!existingData) {
|
|
509
|
+
this.hstStore.set(`${slug}.${recordId}`, {}, 'system');
|
|
510
|
+
}
|
|
511
|
+
for (const [key, value] of Object.entries(record)) {
|
|
512
|
+
this.hstStore.set(`${slug}.${recordId}.${key}`, value, 'system');
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Recursively collect nested data from HST
|
|
517
|
+
* @param basePath - The base path in HST (e.g., "customer.123.address")
|
|
518
|
+
* @param doctype - The doctype whose links drive the recursive traversal
|
|
519
|
+
* @returns The collected data object
|
|
520
|
+
*/
|
|
521
|
+
collectNestedData(basePath, doctype) {
|
|
522
|
+
const data = this.hstStore.get(basePath) || {};
|
|
523
|
+
const payload = { ...data };
|
|
524
|
+
if (!doctype.links)
|
|
525
|
+
return payload;
|
|
526
|
+
for (const [fieldname, link] of Object.entries(doctype.links)) {
|
|
527
|
+
const fieldPath = `${basePath}.${fieldname}`;
|
|
528
|
+
const isMany = link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne';
|
|
529
|
+
if (isMany) {
|
|
530
|
+
const arrayData = this.hstStore.get(fieldPath);
|
|
531
|
+
if (Array.isArray(arrayData)) {
|
|
532
|
+
payload[fieldname] = arrayData;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
const targetDoctype = this.registry.getDoctype(link.target);
|
|
537
|
+
if (targetDoctype?.links) {
|
|
538
|
+
payload[fieldname] = this.collectNestedData(fieldPath, targetDoctype);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
payload[fieldname] = this.hstStore.get(fieldPath) || {};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return payload;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Returns the global Stonecrop singleton instance, or `undefined` if no
|
|
550
|
+
* instance has been created yet.
|
|
551
|
+
*
|
|
552
|
+
* Use this when you need the Stonecrop instance outside a Vue component
|
|
553
|
+
* context (e.g., in workflow action handlers, plugin setup code, or
|
|
554
|
+
* non-component utilities). Inside a component, prefer `useStonecrop()`.
|
|
555
|
+
*
|
|
556
|
+
* @public
|
|
557
|
+
*/
|
|
558
|
+
export function getStonecrop() {
|
|
559
|
+
return Stonecrop._root;
|
|
560
|
+
}
|
|
561
|
+
function createCodedError(message, code) {
|
|
562
|
+
const error = new Error(message);
|
|
563
|
+
error.code = code;
|
|
564
|
+
return error;
|
|
565
|
+
}
|
|
@@ -91,7 +91,7 @@ class HSTProxy {
|
|
|
91
91
|
get(hst, prop) {
|
|
92
92
|
// Return HST methods directly
|
|
93
93
|
if (prop in hst)
|
|
94
|
-
return hst
|
|
94
|
+
return Reflect.get(hst, prop);
|
|
95
95
|
// Handle property access - return tree nodes for navigation
|
|
96
96
|
const path = String(prop);
|
|
97
97
|
return hst.getNode(path);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-log.d.ts","sourceRoot":"","sources":["../../../src/stores/operation-log.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACpC,OAAO,KAAK,EAEX,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,MAAM,wBAAwB,CAAA;AA4E/B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"operation-log.d.ts","sourceRoot":"","sources":["../../../src/stores/operation-log.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACpC,OAAO,KAAK,EAEX,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,MAAM,wBAAwB,CAAA;AA4E/B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA4DJ,OAAO,CAAC,kBAAkB,CAAC;8BAkBtB,iBAAiB,WAAU,eAAe;;gCA0DxC,MAAM,KAAG,MAAM,GAAG,IAAI;;kBA2EpC,OAAO,KAAG,OAAO;kBAmDjB,OAAO,KAAG,OAAO;;gCA2FH,MAAM,aAAa,MAAM,KAAG,YAAY,EAAE;uBA1BrD,oBAAoB;oCAmCL,MAAM,UAAU,MAAM;yBAmBnD,MAAM,cACH,MAAM,cACN,MAAM,EAAE,WACZ,SAAS,GAAG,SAAS,GAAG,SAAS,UACjC,MAAM,KACZ,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAtUmB,OAAO,CAAC,kBAAkB,CAAC;8BAkBtB,iBAAiB,WAAU,eAAe;;gCA0DxC,MAAM,KAAG,MAAM,GAAG,IAAI;;kBA2EpC,OAAO,KAAG,OAAO;kBAmDjB,OAAO,KAAG,OAAO;;gCA2FH,MAAM,aAAa,MAAM,KAAG,YAAY,EAAE;uBA1BrD,oBAAoB;oCAmCL,MAAM,UAAU,MAAM;yBAmBnD,MAAM,cACH,MAAM,cACN,MAAM,EAAE,WACZ,SAAS,GAAG,SAAS,GAAG,SAAS,UACjC,MAAM,KACZ,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAtUmB,OAAO,CAAC,kBAAkB,CAAC;8BAkBtB,iBAAiB,WAAU,eAAe;;gCA0DxC,MAAM,KAAG,MAAM,GAAG,IAAI;;kBA2EpC,OAAO,KAAG,OAAO;kBAmDjB,OAAO,KAAG,OAAO;;gCA2FH,MAAM,aAAa,MAAM,KAAG,YAAY,EAAE;uBA1BrD,oBAAoB;oCAmCL,MAAM,UAAU,MAAM;yBAmBnD,MAAM,cACH,MAAM,cACN,MAAM,EAAE,WACZ,SAAS,GAAG,SAAS,GAAG,SAAS,UACjC,MAAM,KACZ,MAAM;oLA0MR,CAAA"}
|
|
@@ -436,7 +436,7 @@ export const useOperationLogStore = defineStore('hst-operation-log', () => {
|
|
|
436
436
|
}
|
|
437
437
|
else if (message.type === 'operation' && message.operations) {
|
|
438
438
|
// Add batch operations from another tab
|
|
439
|
-
operations.value.push(...message.operations.map(op => ({ ...op, source: 'sync' })));
|
|
439
|
+
operations.value.push(...message.operations.map((op) => ({ ...op, source: 'sync' })));
|
|
440
440
|
currentIndex.value = operations.value.length - 1;
|
|
441
441
|
}
|
|
442
442
|
});
|