@stonecrop/stonecrop 0.11.0 → 0.11.2

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 (56) hide show
  1. package/dist/registry.js +0 -37
  2. package/dist/src/registry.d.ts +1 -24
  3. package/dist/src/registry.d.ts.map +1 -1
  4. package/dist/stonecrop.d.ts +0 -24
  5. package/dist/stonecrop.js +0 -36
  6. package/dist/stonecrop.js.map +1 -1
  7. package/package.json +5 -5
  8. package/src/registry.ts +1 -39
  9. package/dist/composable.js +0 -1
  10. package/dist/composables/use-lazy-link-state.js +0 -125
  11. package/dist/composables/use-stonecrop.js +0 -476
  12. package/dist/operation-log-DB-dGNT9.js +0 -593
  13. package/dist/operation-log-DB-dGNT9.js.map +0 -1
  14. package/dist/src/composable.d.ts +0 -11
  15. package/dist/src/composable.d.ts.map +0 -1
  16. package/dist/src/composable.js +0 -477
  17. package/dist/src/composables/operation-log.js +0 -224
  18. package/dist/src/composables/stonecrop.js +0 -574
  19. package/dist/src/composables/use-lazy-link-state.d.ts +0 -25
  20. package/dist/src/composables/use-lazy-link-state.d.ts.map +0 -1
  21. package/dist/src/composables/use-stonecrop.d.ts +0 -93
  22. package/dist/src/composables/use-stonecrop.d.ts.map +0 -1
  23. package/dist/src/composables/useNestedSchema.d.ts +0 -110
  24. package/dist/src/composables/useNestedSchema.d.ts.map +0 -1
  25. package/dist/src/composables/useNestedSchema.js +0 -155
  26. package/dist/src/doctype.js +0 -234
  27. package/dist/src/exceptions.js +0 -16
  28. package/dist/src/field-triggers.js +0 -567
  29. package/dist/src/index.js +0 -23
  30. package/dist/src/plugins/index.js +0 -96
  31. package/dist/src/registry.js +0 -246
  32. package/dist/src/schema-validator.js +0 -315
  33. package/dist/src/stonecrop.js +0 -339
  34. package/dist/src/stores/data.d.ts +0 -11
  35. package/dist/src/stores/data.d.ts.map +0 -1
  36. package/dist/src/stores/hst.js +0 -495
  37. package/dist/src/stores/index.js +0 -12
  38. package/dist/src/stores/operation-log.js +0 -568
  39. package/dist/src/stores/xstate.d.ts +0 -31
  40. package/dist/src/stores/xstate.d.ts.map +0 -1
  41. package/dist/src/tsdoc-metadata.json +0 -11
  42. package/dist/src/types/field-triggers.js +0 -4
  43. package/dist/src/types/index.js +0 -4
  44. package/dist/src/types/operation-log.js +0 -0
  45. package/dist/src/types/registry.js +0 -0
  46. package/dist/src/utils.d.ts +0 -24
  47. package/dist/src/utils.d.ts.map +0 -1
  48. package/dist/stonecrop.css +0 -1
  49. package/dist/stonecrop.umd.cjs +0 -6
  50. package/dist/stonecrop.umd.cjs.map +0 -1
  51. package/dist/stores/data.js +0 -7
  52. package/dist/stores/xstate.js +0 -29
  53. package/dist/tests/setup.d.ts +0 -5
  54. package/dist/tests/setup.d.ts.map +0 -1
  55. package/dist/tests/setup.js +0 -15
  56. package/dist/utils.js +0 -46
@@ -1,246 +0,0 @@
1
- import { getGlobalTriggerEngine } from './field-triggers';
2
- /**
3
- * Stonecrop Registry class
4
- * @public
5
- */
6
- export default class Registry {
7
- /**
8
- * The root Registry instance
9
- */
10
- static _root;
11
- /**
12
- * The name of the Registry instance
13
- *
14
- * @defaultValue 'Registry'
15
- */
16
- name = 'Registry';
17
- /**
18
- * The registry property contains a collection of doctypes
19
- * @see {@link Doctype}
20
- */
21
- registry = {};
22
- /**
23
- * The Vue router instance
24
- * @see {@link https://router.vuejs.org/}
25
- */
26
- router;
27
- /**
28
- * Creates a new Registry instance (singleton pattern)
29
- * @param router - Optional Vue router instance for route management
30
- * @param getMeta - Optional function to fetch doctype metadata from an API
31
- */
32
- constructor(router, getMeta) {
33
- if (Registry._root) {
34
- return Registry._root;
35
- }
36
- Registry._root = this;
37
- this.router = router;
38
- this.getMeta = getMeta;
39
- }
40
- /**
41
- * The getMeta function fetches doctype metadata from an API based on route context
42
- * @see {@link Doctype}
43
- */
44
- getMeta;
45
- /**
46
- * Get doctype metadata
47
- * @param doctype - The doctype to fetch metadata for
48
- * @returns The doctype metadata
49
- * @see {@link Doctype}
50
- */
51
- addDoctype(doctype) {
52
- if (!(doctype.slug in this.registry)) {
53
- this.registry[doctype.slug] = doctype;
54
- }
55
- // Register actions (including field triggers) with the field trigger engine
56
- const triggerEngine = getGlobalTriggerEngine();
57
- // Register under both doctype name and slug to handle different lookup patterns
58
- triggerEngine.registerDoctypeActions(doctype.doctype, doctype.actions);
59
- if (doctype.slug !== doctype.doctype) {
60
- triggerEngine.registerDoctypeActions(doctype.slug, doctype.actions);
61
- }
62
- if (doctype.component && this.router && !this.router.hasRoute(doctype.doctype)) {
63
- this.router.addRoute({
64
- path: `/${doctype.slug}`,
65
- name: doctype.slug,
66
- component: doctype.component,
67
- });
68
- }
69
- }
70
- /**
71
- * Resolve nested Doctype fields in a schema by embedding child schemas inline.
72
- *
73
- * @remarks
74
- * Walks the schema array and for each field with `fieldtype: 'Doctype'` and a string
75
- * `options` value, looks up the referenced doctype in the registry and:
76
- *
77
- * - If `cardinality: 'many'`: auto-derives `columns` from the child doctype's schema,
78
- * sets `component: 'ATable'`, `config: { view: 'list' }`, and initializes `rows: []`.
79
- * - Otherwise (default/`cardinality: 'one'`): embeds the child schema as the field's
80
- * `schema` property for 1:1 nested forms.
81
- *
82
- * Recurses for deeply nested doctypes. Circular references are protected against.
83
- *
84
- * Returns a new array — does not mutate the original schema.
85
- *
86
- * @param schema - The schema array to resolve
87
- * @returns A new schema array with nested Doctype fields resolved
88
- *
89
- * @example
90
- * ```ts
91
- * registry.addDoctype(addressDoctype)
92
- * registry.addDoctype(customerDoctype)
93
- *
94
- * // Before: customer schema has { fieldname: 'address', fieldtype: 'Doctype', options: 'address' }
95
- * const resolved = registry.resolveSchema(customerSchema)
96
- * // After: address field now has schema: [...address fields...]
97
- * ```
98
- *
99
- * @public
100
- */
101
- resolveSchema(schema, visited) {
102
- const seen = visited || new Set();
103
- return schema.map(field => {
104
- // Check for Doctype fieldtype with a string options (slug reference)
105
- if ('fieldtype' in field &&
106
- field.fieldtype === 'Doctype' &&
107
- 'options' in field &&
108
- typeof field.options === 'string') {
109
- const doctypeSlug = field.options;
110
- // Circular reference protection
111
- if (seen.has(doctypeSlug)) {
112
- return { ...field };
113
- }
114
- const doctype = this.registry[doctypeSlug];
115
- if (doctype && doctype.schema) {
116
- // Convert Immutable.List to plain array if needed
117
- const childSchema = Array.isArray(doctype.schema) ? doctype.schema : Array.from(doctype.schema);
118
- // Check cardinality to determine handling
119
- const cardinality = 'cardinality' in field ? field.cardinality : undefined;
120
- if (cardinality === 'many') {
121
- // 1:many child table - derive columns, set component, config, rows
122
- const resolved = { ...field };
123
- // Auto-derive columns from child schema fields if not already provided
124
- if (!('columns' in field) || !field.columns) {
125
- resolved.columns = childSchema.map(childField => ({
126
- name: childField.fieldname,
127
- fieldname: childField.fieldname,
128
- label: ('label' in childField && childField.label) || childField.fieldname,
129
- fieldtype: 'fieldtype' in childField ? childField.fieldtype : 'Data',
130
- align: ('align' in childField && childField.align) || 'left',
131
- edit: 'edit' in childField ? childField.edit : true,
132
- width: ('width' in childField && childField.width) || '20ch',
133
- }));
134
- }
135
- // Set default component if not already specified
136
- if (!resolved.component) {
137
- resolved.component = 'ATable';
138
- }
139
- // Set default config if not already specified
140
- if (!('config' in field) || !field.config) {
141
- resolved.config = { view: 'list' };
142
- }
143
- // Initialize rows to empty array so componentProps fallback
144
- // routes data from the form's dataModel[fieldname]
145
- if (!('rows' in field) || !field.rows) {
146
- resolved.rows = [];
147
- }
148
- return resolved;
149
- }
150
- else {
151
- // 1:1 nested form (default cardinality: 'one')
152
- // Recurse into child schema to resolve deeply nested doctypes
153
- seen.add(doctypeSlug);
154
- const resolvedChild = this.resolveSchema(childSchema, seen);
155
- seen.delete(doctypeSlug);
156
- return { ...field, schema: resolvedChild };
157
- }
158
- }
159
- }
160
- return { ...field };
161
- });
162
- }
163
- /**
164
- * Initialize a new record with default values based on a schema.
165
- *
166
- * @remarks
167
- * Creates a plain object with keys from the schema's fieldnames and default values
168
- * derived from each field's `fieldtype`:
169
- * - Data, Text → `''`
170
- * - Check → `false`
171
- * - Int, Float, Decimal, Currency, Quantity → `0`
172
- * - JSON → `{}`
173
- * - Doctype with `cardinality: 'many'` → `[]`
174
- * - Doctype without `cardinality` or `cardinality: 'one'` → recursively initializes nested record
175
- * - All others → `null`
176
- *
177
- * For Doctype fields with a resolved `schema` array (cardinality: 'one'), recursively
178
- * initializes the nested record.
179
- *
180
- * @param schema - The schema array to derive defaults from
181
- * @returns A plain object with default values for each field
182
- *
183
- * @example
184
- * ```ts
185
- * const defaults = registry.initializeRecord(addressSchema)
186
- * // { street: '', city: '', state: '', zip_code: '' }
187
- * ```
188
- *
189
- * @public
190
- */
191
- initializeRecord(schema) {
192
- const record = {};
193
- schema.forEach(field => {
194
- const fieldtype = 'fieldtype' in field ? field.fieldtype : 'Data';
195
- switch (fieldtype) {
196
- case 'Data':
197
- case 'Text':
198
- case 'Code':
199
- record[field.fieldname] = '';
200
- break;
201
- case 'Check':
202
- record[field.fieldname] = false;
203
- break;
204
- case 'Int':
205
- case 'Float':
206
- case 'Decimal':
207
- case 'Currency':
208
- case 'Quantity':
209
- record[field.fieldname] = 0;
210
- break;
211
- case 'JSON':
212
- record[field.fieldname] = {};
213
- break;
214
- case 'Doctype': {
215
- // Check cardinality to determine initial value
216
- const cardinality = 'cardinality' in field ? field.cardinality : undefined;
217
- if (cardinality === 'many') {
218
- // 1:many child table - initialize as empty array
219
- record[field.fieldname] = [];
220
- }
221
- else if ('schema' in field && Array.isArray(field.schema)) {
222
- // 1:1 nested form with resolved schema - recursively initialize
223
- record[field.fieldname] = this.initializeRecord(field.schema);
224
- }
225
- else {
226
- // 1:1 without resolved schema - empty object
227
- record[field.fieldname] = {};
228
- }
229
- break;
230
- }
231
- default:
232
- record[field.fieldname] = null;
233
- }
234
- });
235
- return record;
236
- }
237
- /**
238
- * Get a registered doctype by slug
239
- * @param slug - The doctype slug to look up
240
- * @returns The Doctype instance if found, or undefined
241
- * @public
242
- */
243
- getDoctype(slug) {
244
- return this.registry[slug];
245
- }
246
- }
@@ -1,315 +0,0 @@
1
- /**
2
- * Schema Validation Utilities
3
- * Validates Stonecrop schemas for integrity and consistency
4
- * @packageDocumentation
5
- */
6
- import { getGlobalTriggerEngine } from './field-triggers';
7
- /**
8
- * Validation severity levels
9
- * @public
10
- */
11
- export var ValidationSeverity;
12
- (function (ValidationSeverity) {
13
- /** Blocking error that prevents save */
14
- ValidationSeverity["ERROR"] = "error";
15
- /** Advisory warning that allows save */
16
- ValidationSeverity["WARNING"] = "warning";
17
- /** Informational message */
18
- ValidationSeverity["INFO"] = "info";
19
- })(ValidationSeverity || (ValidationSeverity = {}));
20
- /**
21
- * Schema validator class
22
- * @public
23
- */
24
- export class SchemaValidator {
25
- options;
26
- /**
27
- * Creates a new SchemaValidator instance
28
- * @param options - Validator configuration options
29
- */
30
- constructor(options = {}) {
31
- this.options = {
32
- registry: options.registry || null,
33
- validateLinkTargets: options.validateLinkTargets ?? true,
34
- validateActions: options.validateActions ?? true,
35
- validateWorkflows: options.validateWorkflows ?? true,
36
- validateRequiredProperties: options.validateRequiredProperties ?? true,
37
- };
38
- }
39
- /**
40
- * Validates a complete doctype schema
41
- * @param doctype - Doctype name
42
- * @param schema - Schema fields (List or Array)
43
- * @param workflow - Optional workflow configuration
44
- * @param actions - Optional actions map
45
- * @returns Validation result
46
- */
47
- validate(doctype, schema, workflow, actions) {
48
- const issues = [];
49
- // Convert schema to array for easier iteration
50
- const schemaArray = schema ? (Array.isArray(schema) ? schema : schema.toArray()) : [];
51
- // Validate required properties
52
- if (this.options.validateRequiredProperties) {
53
- issues.push(...this.validateRequiredProperties(doctype, schemaArray));
54
- }
55
- // Validate Link field targets
56
- if (this.options.validateLinkTargets && this.options.registry) {
57
- issues.push(...this.validateLinkFields(doctype, schemaArray, this.options.registry));
58
- }
59
- // Validate workflow configuration
60
- if (this.options.validateWorkflows && workflow) {
61
- issues.push(...this.validateWorkflow(doctype, workflow));
62
- }
63
- // Validate action registration
64
- if (this.options.validateActions && actions) {
65
- const actionsMap = actions instanceof Map ? actions : actions.toObject();
66
- issues.push(...this.validateActionRegistration(doctype, actionsMap));
67
- }
68
- // Calculate counts
69
- const errorCount = issues.filter(i => i.severity === ValidationSeverity.ERROR).length;
70
- const warningCount = issues.filter(i => i.severity === ValidationSeverity.WARNING).length;
71
- const infoCount = issues.filter(i => i.severity === ValidationSeverity.INFO).length;
72
- return {
73
- valid: errorCount === 0,
74
- issues,
75
- errorCount,
76
- warningCount,
77
- infoCount,
78
- };
79
- }
80
- /**
81
- * Validates that required schema properties are present
82
- * @internal
83
- */
84
- validateRequiredProperties(doctype, schema) {
85
- const issues = [];
86
- for (const field of schema) {
87
- // Check for fieldname
88
- if (!field.fieldname) {
89
- issues.push({
90
- severity: ValidationSeverity.ERROR,
91
- rule: 'required-fieldname',
92
- message: 'Field is missing required property: fieldname',
93
- doctype,
94
- context: { field },
95
- });
96
- continue;
97
- }
98
- // Check for component or fieldtype
99
- if (!field.component && !('fieldtype' in field)) {
100
- issues.push({
101
- severity: ValidationSeverity.ERROR,
102
- rule: 'required-component-or-fieldtype',
103
- message: `Field "${field.fieldname}" must have either component or fieldtype property`,
104
- doctype,
105
- fieldname: field.fieldname,
106
- });
107
- }
108
- // Validate nested schemas (recursively)
109
- if ('schema' in field) {
110
- const nestedSchema = field.schema;
111
- const nestedArray = (Array.isArray(nestedSchema) ? nestedSchema : nestedSchema.toArray?.() || []);
112
- issues.push(...this.validateRequiredProperties(doctype, nestedArray));
113
- }
114
- }
115
- return issues;
116
- }
117
- /**
118
- * Validates Link field targets exist in registry
119
- * @internal
120
- */
121
- validateLinkFields(doctype, schema, registry) {
122
- const issues = [];
123
- for (const field of schema) {
124
- const fieldtype = 'fieldtype' in field ? field.fieldtype : undefined;
125
- // Check Link fields
126
- if (fieldtype === 'Link') {
127
- const options = 'options' in field ? field.options : undefined;
128
- if (!options) {
129
- issues.push({
130
- severity: ValidationSeverity.ERROR,
131
- rule: 'link-missing-options',
132
- message: `Link field "${field.fieldname}" is missing options property (target doctype)`,
133
- doctype,
134
- fieldname: field.fieldname,
135
- });
136
- continue;
137
- }
138
- // Check if target doctype exists in registry
139
- // Options should be a string representing the target doctype name
140
- const targetDoctype = typeof options === 'string' ? options : '';
141
- if (!targetDoctype) {
142
- issues.push({
143
- severity: ValidationSeverity.ERROR,
144
- rule: 'link-invalid-options',
145
- message: `Link field "${field.fieldname}" has invalid options format (expected string doctype name)`,
146
- doctype,
147
- fieldname: field.fieldname,
148
- });
149
- continue;
150
- }
151
- const targetMeta = registry.registry[targetDoctype] || registry.registry[targetDoctype.toLowerCase()];
152
- if (!targetMeta) {
153
- issues.push({
154
- severity: ValidationSeverity.ERROR,
155
- rule: 'link-invalid-target',
156
- message: `Link field "${field.fieldname}" references non-existent doctype: "${targetDoctype}"`,
157
- doctype,
158
- fieldname: field.fieldname,
159
- context: { targetDoctype },
160
- });
161
- }
162
- }
163
- // Recursively check nested schemas
164
- if ('schema' in field) {
165
- const nestedSchema = field.schema;
166
- const nestedArray = (Array.isArray(nestedSchema) ? nestedSchema : nestedSchema.toArray?.() || []);
167
- issues.push(...this.validateLinkFields(doctype, nestedArray, registry));
168
- }
169
- }
170
- return issues;
171
- }
172
- /**
173
- * Validates workflow state machine configuration
174
- * @internal
175
- */
176
- validateWorkflow(doctype, workflow) {
177
- const issues = [];
178
- // Check for initial state
179
- if (!workflow.initial && !workflow.type) {
180
- issues.push({
181
- severity: ValidationSeverity.WARNING,
182
- rule: 'workflow-missing-initial',
183
- message: 'Workflow is missing initial state property',
184
- doctype,
185
- });
186
- }
187
- // Check for states
188
- if (!workflow.states || Object.keys(workflow.states).length === 0) {
189
- issues.push({
190
- severity: ValidationSeverity.WARNING,
191
- rule: 'workflow-no-states',
192
- message: 'Workflow has no states defined',
193
- doctype,
194
- });
195
- return issues;
196
- }
197
- // Validate initial state exists
198
- if (workflow.initial && typeof workflow.initial === 'string' && !workflow.states[workflow.initial]) {
199
- issues.push({
200
- severity: ValidationSeverity.ERROR,
201
- rule: 'workflow-invalid-initial',
202
- message: `Workflow initial state "${workflow.initial}" does not exist in states`,
203
- doctype,
204
- context: { initialState: workflow.initial },
205
- });
206
- }
207
- // Check state reachability (simple check - all states should have at least one incoming transition or be initial)
208
- const stateNames = Object.keys(workflow.states);
209
- const reachableStates = new Set();
210
- // Initial state is always reachable
211
- if (workflow.initial && typeof workflow.initial === 'string') {
212
- reachableStates.add(workflow.initial);
213
- }
214
- // Find all target states from transitions
215
- for (const [_stateName, stateConfig] of Object.entries(workflow.states)) {
216
- const state = stateConfig;
217
- if (state.on) {
218
- for (const [_event, transition] of Object.entries(state.on)) {
219
- if (typeof transition === 'string') {
220
- reachableStates.add(transition);
221
- }
222
- else if (transition && typeof transition === 'object') {
223
- const target = 'target' in transition ? transition.target : undefined;
224
- if (typeof target === 'string') {
225
- reachableStates.add(target);
226
- }
227
- else if (Array.isArray(target)) {
228
- target.forEach((t) => {
229
- if (typeof t === 'string') {
230
- reachableStates.add(t);
231
- }
232
- });
233
- }
234
- }
235
- }
236
- }
237
- }
238
- // Check for unreachable states
239
- for (const stateName of stateNames) {
240
- if (!reachableStates.has(stateName)) {
241
- issues.push({
242
- severity: ValidationSeverity.WARNING,
243
- rule: 'workflow-unreachable-state',
244
- message: `Workflow state "${stateName}" may not be reachable`,
245
- doctype,
246
- context: { stateName },
247
- });
248
- }
249
- }
250
- return issues;
251
- }
252
- /**
253
- * Validates that actions are registered in the FieldTriggerEngine
254
- * @internal
255
- */
256
- validateActionRegistration(doctype, actions) {
257
- const issues = [];
258
- const triggerEngine = getGlobalTriggerEngine();
259
- for (const [triggerName, actionNames] of Object.entries(actions)) {
260
- if (!Array.isArray(actionNames)) {
261
- issues.push({
262
- severity: ValidationSeverity.ERROR,
263
- rule: 'action-invalid-format',
264
- message: `Action configuration for "${triggerName}" must be an array`,
265
- doctype,
266
- context: { triggerName, actionNames },
267
- });
268
- continue;
269
- }
270
- // Check each action name
271
- for (const actionName of actionNames) {
272
- // Check if action is registered globally
273
- const engine = triggerEngine;
274
- const isRegistered = engine.globalActions?.has(actionName) || engine.globalTransitionActions?.has(actionName);
275
- if (!isRegistered) {
276
- issues.push({
277
- severity: ValidationSeverity.WARNING,
278
- rule: 'action-not-registered',
279
- message: `Action "${actionName}" referenced in "${triggerName}" is not registered in FieldTriggerEngine`,
280
- doctype,
281
- context: { triggerName, actionName },
282
- });
283
- }
284
- }
285
- }
286
- return issues;
287
- }
288
- }
289
- /**
290
- * Creates a validator with the given registry
291
- * @param registry - Registry instance
292
- * @param options - Additional validator options
293
- * @returns SchemaValidator instance
294
- * @public
295
- */
296
- export function createValidator(registry, options) {
297
- return new SchemaValidator({
298
- registry,
299
- ...options,
300
- });
301
- }
302
- /**
303
- * Quick validation helper
304
- * @param doctype - Doctype name
305
- * @param schema - Schema fields
306
- * @param registry - Registry instance
307
- * @param workflow - Optional workflow configuration
308
- * @param actions - Optional actions map
309
- * @returns Validation result
310
- * @public
311
- */
312
- export function validateSchema(doctype, schema, registry, workflow, actions) {
313
- const validator = createValidator(registry);
314
- return validator.validate(doctype, schema, workflow, actions);
315
- }