@stonecrop/stonecrop 0.12.8 → 0.13.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 (60) hide show
  1. package/README.md +0 -1
  2. package/dist/composable.js +1 -0
  3. package/dist/composables/lazy-link.js +125 -0
  4. package/dist/composables/operation-log.js +224 -0
  5. package/dist/composables/stonecrop.js +504 -0
  6. package/dist/composables/use-lazy-link-state.js +125 -0
  7. package/dist/composables/use-stonecrop.js +476 -0
  8. package/dist/doctype.js +242 -0
  9. package/dist/exceptions.js +16 -0
  10. package/dist/field-triggers.js +575 -0
  11. package/dist/index.js +27 -0
  12. package/dist/operation-log-DB-dGNT9.js +593 -0
  13. package/dist/operation-log-DB-dGNT9.js.map +1 -0
  14. package/dist/plugins/index.js +99 -0
  15. package/dist/registry.js +423 -0
  16. package/dist/schema-validator.js +407 -0
  17. package/dist/src/composable.d.ts +11 -0
  18. package/dist/src/composable.d.ts.map +1 -0
  19. package/dist/src/composable.js +477 -0
  20. package/dist/src/composables/use-lazy-link-state.d.ts +25 -0
  21. package/dist/src/composables/use-lazy-link-state.d.ts.map +1 -0
  22. package/dist/src/composables/use-stonecrop.d.ts +93 -0
  23. package/dist/src/composables/use-stonecrop.d.ts.map +1 -0
  24. package/dist/src/composables/useNestedSchema.d.ts +110 -0
  25. package/dist/src/composables/useNestedSchema.d.ts.map +1 -0
  26. package/dist/src/composables/useNestedSchema.js +155 -0
  27. package/dist/src/stores/data.d.ts +11 -0
  28. package/dist/src/stores/data.d.ts.map +1 -0
  29. package/dist/src/stores/xstate.d.ts +31 -0
  30. package/dist/src/stores/xstate.d.ts.map +1 -0
  31. package/dist/src/tsdoc-metadata.json +11 -0
  32. package/dist/src/types/doctype.d.ts +0 -2
  33. package/dist/src/types/doctype.d.ts.map +1 -1
  34. package/dist/src/utils.d.ts +24 -0
  35. package/dist/src/utils.d.ts.map +1 -0
  36. package/dist/stonecrop.css +1 -0
  37. package/dist/stonecrop.d.ts +0 -2
  38. package/dist/stonecrop.umd.cjs +6 -0
  39. package/dist/stonecrop.umd.cjs.map +1 -0
  40. package/dist/stores/data.js +7 -0
  41. package/dist/stores/hst.js +496 -0
  42. package/dist/stores/index.js +12 -0
  43. package/dist/stores/operation-log.js +580 -0
  44. package/dist/stores/xstate.js +29 -0
  45. package/dist/tests/setup.d.ts +5 -0
  46. package/dist/tests/setup.d.ts.map +1 -0
  47. package/dist/tests/setup.js +15 -0
  48. package/dist/types/composable.js +0 -0
  49. package/dist/types/doctype.js +0 -0
  50. package/dist/types/field-triggers.js +4 -0
  51. package/dist/types/hst.js +0 -0
  52. package/dist/types/index.js +10 -0
  53. package/dist/types/operation-log.js +0 -0
  54. package/dist/types/plugin.js +0 -0
  55. package/dist/types/registry.js +0 -0
  56. package/dist/types/schema-validator.js +13 -0
  57. package/dist/types/stonecrop.js +0 -0
  58. package/dist/utils.js +46 -0
  59. package/package.json +4 -4
  60. package/src/types/doctype.ts +0 -2
package/README.md CHANGED
@@ -74,7 +74,6 @@ function buildMetaMap(registry: Registry): Map<string, DoctypeMeta> {
74
74
  metaMap.set(slug, {
75
75
  name: doctype.doctype,
76
76
  slug,
77
- tableName: slug.replace(/-/g, '_'),
78
77
  fields: doctype.getSchemaArray(),
79
78
  links: doctype.links || {},
80
79
  })
@@ -0,0 +1 @@
1
+ export { useStonecrop } from './composables/stonecrop';
@@ -0,0 +1,125 @@
1
+ import { computed, inject, ref } from 'vue';
2
+ import { Stonecrop } from '../stonecrop';
3
+ /**
4
+ * Get the lazy link state for a specific link field on a doctype record.
5
+ *
6
+ * This composable provides reactive state for lazy-loaded links:
7
+ * - `loading`: true while fetching
8
+ * - `loaded`: true after successful fetch (permanent until reload)
9
+ * - `error`: error state if any
10
+ * - `reload()`: explicitly trigger a fetch
11
+ * - `data`: computed from HST, or undefined if not loaded
12
+ *
13
+ * The reload() function respects the link's fetch strategy:
14
+ * - `sync`: fetches via GraphQL query through fetchNestedData
15
+ * - `lazy`: fetches via GraphQL query through fetchNestedData
16
+ * - `custom`: invokes the serialized handler function directly
17
+ *
18
+ * @param doctype - The doctype instance
19
+ * @param recordId - The record ID
20
+ * @param linkFieldname - The link fieldname to load
21
+ * @returns LazyLink with loading, loaded, error, reload, and data
22
+ * @public
23
+ */
24
+ export function useLazyLink(doctype, recordId, linkFieldname) {
25
+ const stonecropInstance = inject('$stonecrop') || Stonecrop._root;
26
+ if (!stonecropInstance) {
27
+ throw new Error('Stonecrop instance not available. Ensure useStonecrop() has been called first.');
28
+ }
29
+ const loading = ref(false);
30
+ const loaded = ref(false);
31
+ const error = ref(null);
32
+ const hstStore = stonecropInstance.getStore();
33
+ /**
34
+ * Build the HST path for a lazy link field
35
+ */
36
+ const getLinkPath = () => {
37
+ const slug = doctype.slug || doctype.doctype;
38
+ return `${slug}.${recordId}.${linkFieldname}`;
39
+ };
40
+ /**
41
+ * Get the link declaration from the doctype schema
42
+ */
43
+ const getLinkDeclaration = () => {
44
+ return doctype.links?.[linkFieldname]?.fetch;
45
+ };
46
+ /**
47
+ * Invoke a custom fetch handler
48
+ * The handler is a serialized function string that we execute via new Function()
49
+ */
50
+ const invokeCustomHandler = async (handler) => {
51
+ try {
52
+ // Create function from serialized string and invoke it
53
+ // The function receives the stonecrop instance and path as parameters
54
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
55
+ const fn = new Function('stonecrop', 'path', 'hst', `
56
+ return (${handler})(stonecrop, path, hst)
57
+ `);
58
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
59
+ return await fn(stonecropInstance, getLinkPath(), hstStore);
60
+ }
61
+ catch (err) {
62
+ throw new Error(`Custom handler failed: ${err instanceof Error ? err.message : String(err)}`);
63
+ }
64
+ };
65
+ /**
66
+ * Fetch the link data using the appropriate strategy
67
+ */
68
+ const fetchLinkData = async () => {
69
+ const linkFetch = getLinkDeclaration();
70
+ const ancestorPath = `${doctype.slug || doctype.doctype}.${recordId}`;
71
+ if (linkFetch?.method === 'custom') {
72
+ // Ensure ancestor path exists before invoking custom handler
73
+ if (!hstStore.has(ancestorPath)) {
74
+ hstStore.set(ancestorPath, {}, 'system');
75
+ }
76
+ // Custom handler - invoke directly
77
+ const result = await invokeCustomHandler(linkFetch.handler);
78
+ // Store result in HST at the link path
79
+ hstStore.set(getLinkPath(), result, 'system');
80
+ }
81
+ else {
82
+ // sync or lazy (both use fetchNestedData but with different includeNested)
83
+ // For lazy links, we still use fetchNestedData but only for this specific link
84
+ await stonecropInstance.fetchNestedData(ancestorPath, doctype, recordId, { includeNested: [linkFieldname] });
85
+ }
86
+ };
87
+ /**
88
+ * Explicitly reload the lazy link data
89
+ */
90
+ const reload = async () => {
91
+ if (loading.value)
92
+ return;
93
+ loading.value = true;
94
+ error.value = null;
95
+ try {
96
+ await fetchLinkData();
97
+ loaded.value = true;
98
+ }
99
+ catch (err) {
100
+ error.value = err instanceof Error ? err : new Error(String(err));
101
+ throw err;
102
+ }
103
+ finally {
104
+ loading.value = false;
105
+ }
106
+ };
107
+ /**
108
+ * Computed property that returns the loaded data from HST
109
+ */
110
+ const data = computed(() => {
111
+ if (!loaded.value)
112
+ return undefined;
113
+ return hstStore.get(getLinkPath());
114
+ });
115
+ return {
116
+ // State
117
+ loading,
118
+ loaded,
119
+ error,
120
+ // Computed
121
+ data,
122
+ // Actions
123
+ reload,
124
+ };
125
+ }
@@ -0,0 +1,224 @@
1
+ import { useMagicKeys, whenever } from '@vueuse/core';
2
+ import { storeToRefs } from 'pinia';
3
+ import { getCurrentInstance, inject } from 'vue';
4
+ import { useOperationLogStore } from '../stores/operation-log';
5
+ /**
6
+ * Composable for operation log management
7
+ * Provides easy access to undo/redo functionality and operation history
8
+ *
9
+ * @param config - Optional configuration for the operation log
10
+ * @returns Operation log interface
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const { undo, redo, canUndo, canRedo, operations, configure } = useOperationLog()
15
+ *
16
+ * // Configure the log
17
+ * configure({
18
+ * maxOperations: 50,
19
+ * enableCrossTabSync: true,
20
+ * enablePersistence: true
21
+ * })
22
+ *
23
+ * // Undo/redo
24
+ * await undo(hstStore)
25
+ * await redo(hstStore)
26
+ * ```
27
+ *
28
+ * @public
29
+ */
30
+ export function useOperationLog(config) {
31
+ // inject() is only valid inside a component setup() context. When this
32
+ // composable is called outside one (e.g. directly in test bodies or plain
33
+ // scripts) skip the injection entirely and fall back to the Pinia store.
34
+ const injectedStore = getCurrentInstance()
35
+ ? inject('$operationLogStore', undefined)
36
+ : undefined;
37
+ const store = injectedStore || useOperationLogStore();
38
+ // Apply configuration if provided
39
+ if (config) {
40
+ store.configure(config);
41
+ }
42
+ // Extract reactive state
43
+ const { operations, currentIndex, undoRedoState, canUndo, canRedo, undoCount, redoCount } = storeToRefs(store);
44
+ /**
45
+ * Undo the last operation
46
+ */
47
+ function undo(hstStore) {
48
+ return store.undo(hstStore);
49
+ }
50
+ /**
51
+ * Redo the next operation
52
+ */
53
+ function redo(hstStore) {
54
+ return store.redo(hstStore);
55
+ }
56
+ /**
57
+ * Start a batch operation
58
+ */
59
+ function startBatch() {
60
+ store.startBatch();
61
+ }
62
+ /**
63
+ * Commit the current batch
64
+ */
65
+ function commitBatch(description) {
66
+ return store.commitBatch(description);
67
+ }
68
+ /**
69
+ * Cancel the current batch
70
+ */
71
+ function cancelBatch() {
72
+ store.cancelBatch();
73
+ }
74
+ /**
75
+ * Clear all operations
76
+ */
77
+ function clear() {
78
+ store.clear();
79
+ }
80
+ /**
81
+ * Get operations for a specific doctype/record
82
+ */
83
+ function getOperationsFor(doctype, recordId) {
84
+ return store.getOperationsFor(doctype, recordId);
85
+ }
86
+ /**
87
+ * Get a snapshot of the operation log
88
+ */
89
+ function getSnapshot() {
90
+ return store.getSnapshot();
91
+ }
92
+ /**
93
+ * Mark an operation as irreversible
94
+ * @param operationId - The ID of the operation to mark
95
+ * @param reason - The reason why the operation is irreversible
96
+ */
97
+ function markIrreversible(operationId, reason) {
98
+ store.markIrreversible(operationId, reason);
99
+ }
100
+ /**
101
+ * Log an action execution (stateless actions like print, email, etc.)
102
+ * @param doctype - The doctype the action was executed on
103
+ * @param actionName - The name of the action that was executed
104
+ * @param recordIds - Optional array of record IDs the action was executed on
105
+ * @param result - The result of the action execution
106
+ * @param error - Optional error message if action failed
107
+ * @returns The operation ID
108
+ */
109
+ function logAction(doctype, actionName, recordIds, result = 'success', error) {
110
+ return store.logAction(doctype, actionName, recordIds, result, error);
111
+ }
112
+ /**
113
+ * Update configuration
114
+ * @param options - Configuration options to update
115
+ */
116
+ function configure(options) {
117
+ store.configure(options);
118
+ }
119
+ return {
120
+ // State
121
+ operations,
122
+ currentIndex,
123
+ undoRedoState,
124
+ canUndo,
125
+ canRedo,
126
+ undoCount,
127
+ redoCount,
128
+ // Methods
129
+ undo,
130
+ redo,
131
+ startBatch,
132
+ commitBatch,
133
+ cancelBatch,
134
+ clear,
135
+ getOperationsFor,
136
+ getSnapshot,
137
+ markIrreversible,
138
+ logAction,
139
+ configure,
140
+ };
141
+ }
142
+ /**
143
+ * Keyboard shortcut handler for undo/redo
144
+ * Automatically binds Ctrl+Z (undo) and Ctrl+Shift+Z/Ctrl+Y (redo) using VueUse
145
+ *
146
+ * @param hstStore - The HST store to operate on
147
+ * @param enabled - Whether shortcuts are enabled (default: true)
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * import { onMounted } from 'vue'
152
+ *
153
+ * const stonecrop = useStonecrop({ doctype, recordId })
154
+ * useUndoRedoShortcuts(stonecrop.hstStore)
155
+ * ```
156
+ *
157
+ * @public
158
+ */
159
+ export function useUndoRedoShortcuts(hstStore, enabled = true) {
160
+ if (!enabled)
161
+ return;
162
+ const { undo, redo, canUndo, canRedo } = useOperationLog();
163
+ const keys = useMagicKeys();
164
+ // Undo shortcuts: Ctrl+Z or Cmd+Z (Mac)
165
+ whenever(keys['Ctrl+Z'], () => {
166
+ if (canUndo.value) {
167
+ undo(hstStore);
168
+ }
169
+ });
170
+ whenever(keys['Meta+Z'], () => {
171
+ if (canUndo.value) {
172
+ undo(hstStore);
173
+ }
174
+ });
175
+ // Redo shortcuts: Ctrl+Shift+Z, Cmd+Shift+Z (Mac), or Ctrl+Y
176
+ whenever(keys['Ctrl+Shift+Z'], () => {
177
+ if (canRedo.value) {
178
+ redo(hstStore);
179
+ }
180
+ });
181
+ whenever(keys['Meta+Shift+Z'], () => {
182
+ if (canRedo.value) {
183
+ redo(hstStore);
184
+ }
185
+ });
186
+ whenever(keys['Ctrl+Y'], () => {
187
+ if (canRedo.value) {
188
+ redo(hstStore);
189
+ }
190
+ });
191
+ }
192
+ /**
193
+ * Batch operation helper
194
+ * Wraps a function execution in a batch operation
195
+ *
196
+ * @param fn - The function to execute within a batch
197
+ * @param description - Optional description for the batch
198
+ * @returns The batch operation ID
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * const { withBatch } = useOperationLog()
203
+ *
204
+ * const batchId = await withBatch(() => {
205
+ * hstStore.set('task.123.title', 'New Title')
206
+ * hstStore.set('task.123.status', 'active')
207
+ * hstStore.set('task.123.priority', 'high')
208
+ * }, 'Update task details')
209
+ * ```
210
+ *
211
+ * @public
212
+ */
213
+ export async function withBatch(fn, description) {
214
+ const { startBatch, commitBatch, cancelBatch } = useOperationLog();
215
+ startBatch();
216
+ try {
217
+ await fn();
218
+ return commitBatch(description);
219
+ }
220
+ catch (error) {
221
+ cancelBatch();
222
+ throw error;
223
+ }
224
+ }