@stonecrop/stonecrop 0.12.8 → 0.13.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.
- package/dist/composable.js +1 -0
- package/dist/composables/lazy-link.js +125 -0
- package/dist/composables/operation-log.js +224 -0
- package/dist/composables/stonecrop.js +504 -0
- package/dist/composables/use-lazy-link-state.js +125 -0
- package/dist/composables/use-stonecrop.js +476 -0
- package/dist/doctype.js +242 -0
- package/dist/exceptions.js +16 -0
- package/dist/field-triggers.js +575 -0
- package/dist/index.js +27 -0
- package/dist/operation-log-DB-dGNT9.js +593 -0
- package/dist/operation-log-DB-dGNT9.js.map +1 -0
- package/dist/plugins/index.js +99 -0
- package/dist/registry.js +423 -0
- package/dist/schema-validator.js +407 -0
- package/dist/src/composable.d.ts +11 -0
- package/dist/src/composable.d.ts.map +1 -0
- package/dist/src/composable.js +477 -0
- package/dist/src/composables/use-lazy-link-state.d.ts +25 -0
- package/dist/src/composables/use-lazy-link-state.d.ts.map +1 -0
- package/dist/src/composables/use-stonecrop.d.ts +93 -0
- package/dist/src/composables/use-stonecrop.d.ts.map +1 -0
- package/dist/src/composables/useNestedSchema.d.ts +110 -0
- package/dist/src/composables/useNestedSchema.d.ts.map +1 -0
- package/dist/src/composables/useNestedSchema.js +155 -0
- package/dist/src/stores/data.d.ts +11 -0
- package/dist/src/stores/data.d.ts.map +1 -0
- package/dist/src/stores/xstate.d.ts +31 -0
- package/dist/src/stores/xstate.d.ts.map +1 -0
- package/dist/src/tsdoc-metadata.json +11 -0
- package/dist/src/utils.d.ts +24 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/stonecrop.css +1 -0
- package/dist/stonecrop.umd.cjs +6 -0
- package/dist/stonecrop.umd.cjs.map +1 -0
- package/dist/stores/data.js +7 -0
- package/dist/stores/hst.js +496 -0
- package/dist/stores/index.js +12 -0
- package/dist/stores/operation-log.js +580 -0
- package/dist/stores/xstate.js +29 -0
- package/dist/tests/setup.d.ts +5 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +15 -0
- package/dist/types/composable.js +0 -0
- package/dist/types/doctype.js +0 -0
- package/dist/types/field-triggers.js +4 -0
- package/dist/types/hst.js +0 -0
- package/dist/types/index.js +10 -0
- package/dist/types/operation-log.js +0 -0
- package/dist/types/plugin.js +0 -0
- package/dist/types/registry.js +0 -0
- package/dist/types/schema-validator.js +13 -0
- package/dist/types/stonecrop.js +0 -0
- package/dist/utils.js +46 -0
- package/package.json +4 -4
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validation Utilities
|
|
3
|
+
* Validates Stonecrop schemas for integrity and consistency
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { getGlobalTriggerEngine } from './field-triggers';
|
|
7
|
+
import { ValidationSeverity } from './types/schema-validator';
|
|
8
|
+
/**
|
|
9
|
+
* Schema validator class
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export class SchemaValidator {
|
|
13
|
+
options;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new SchemaValidator instance
|
|
16
|
+
* @param options - Validator configuration options
|
|
17
|
+
*/
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.options = {
|
|
20
|
+
registry: options.registry || null,
|
|
21
|
+
validateLinkTargets: options.validateLinkTargets ?? true,
|
|
22
|
+
validateLinks: options.validateLinks ?? true,
|
|
23
|
+
validateActions: options.validateActions ?? true,
|
|
24
|
+
validateWorkflows: options.validateWorkflows ?? true,
|
|
25
|
+
validateRequiredProperties: options.validateRequiredProperties ?? true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validates a complete doctype schema
|
|
30
|
+
* @param doctype - Doctype name
|
|
31
|
+
* @param schema - Schema fields (List or Array)
|
|
32
|
+
* @param workflow - Optional workflow configuration
|
|
33
|
+
* @param actions - Optional actions map
|
|
34
|
+
* @param links - Optional links object
|
|
35
|
+
* @returns Validation result
|
|
36
|
+
*/
|
|
37
|
+
validate(doctype, schema, workflow, actions, links) {
|
|
38
|
+
const issues = [];
|
|
39
|
+
// Convert schema to array for easier iteration
|
|
40
|
+
const schemaArray = schema ? (Array.isArray(schema) ? schema : schema.toArray()) : [];
|
|
41
|
+
// Validate required properties
|
|
42
|
+
if (this.options.validateRequiredProperties) {
|
|
43
|
+
issues.push(...this.validateRequiredProperties(doctype, schemaArray));
|
|
44
|
+
}
|
|
45
|
+
// Validate Link field targets
|
|
46
|
+
if (this.options.validateLinkTargets && this.options.registry) {
|
|
47
|
+
issues.push(...this.validateLinkFields(doctype, schemaArray, this.options.registry));
|
|
48
|
+
}
|
|
49
|
+
// Validate links object
|
|
50
|
+
if (this.options.validateLinks && this.options.registry && links) {
|
|
51
|
+
issues.push(...this.validateLinkDeclarations(doctype, links, schemaArray, this.options.registry));
|
|
52
|
+
}
|
|
53
|
+
// Validate workflow configuration
|
|
54
|
+
if (this.options.validateWorkflows && workflow) {
|
|
55
|
+
issues.push(...this.validateWorkflow(doctype, workflow));
|
|
56
|
+
}
|
|
57
|
+
// Validate action registration
|
|
58
|
+
if (this.options.validateActions && actions) {
|
|
59
|
+
const actionsMap = actions instanceof Map ? actions : actions.toObject();
|
|
60
|
+
issues.push(...this.validateActionRegistration(doctype, actionsMap));
|
|
61
|
+
}
|
|
62
|
+
// Calculate counts
|
|
63
|
+
const errorCount = issues.filter(i => i.severity === ValidationSeverity.ERROR).length;
|
|
64
|
+
const warningCount = issues.filter(i => i.severity === ValidationSeverity.WARNING).length;
|
|
65
|
+
const infoCount = issues.filter(i => i.severity === ValidationSeverity.INFO).length;
|
|
66
|
+
return {
|
|
67
|
+
valid: errorCount === 0,
|
|
68
|
+
issues,
|
|
69
|
+
errorCount,
|
|
70
|
+
warningCount,
|
|
71
|
+
infoCount,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validates that required schema properties are present
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
validateRequiredProperties(doctype, schema) {
|
|
79
|
+
const issues = [];
|
|
80
|
+
for (const field of schema) {
|
|
81
|
+
// Check for fieldname
|
|
82
|
+
if (!field.fieldname) {
|
|
83
|
+
issues.push({
|
|
84
|
+
severity: ValidationSeverity.ERROR,
|
|
85
|
+
rule: 'required-fieldname',
|
|
86
|
+
message: 'Field is missing required property: fieldname',
|
|
87
|
+
doctype,
|
|
88
|
+
context: { field },
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Check for component or fieldtype
|
|
93
|
+
if (!field.component && !('fieldtype' in field)) {
|
|
94
|
+
issues.push({
|
|
95
|
+
severity: ValidationSeverity.ERROR,
|
|
96
|
+
rule: 'required-component-or-fieldtype',
|
|
97
|
+
message: `Field "${field.fieldname}" must have either component or fieldtype property`,
|
|
98
|
+
doctype,
|
|
99
|
+
fieldname: field.fieldname,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// Validate nested schemas (recursively)
|
|
103
|
+
if ('schema' in field) {
|
|
104
|
+
const nestedSchema = field.schema;
|
|
105
|
+
const nestedArray = (Array.isArray(nestedSchema) ? nestedSchema : nestedSchema.toArray?.() || []);
|
|
106
|
+
issues.push(...this.validateRequiredProperties(doctype, nestedArray));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return issues;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Validates Link field targets exist in registry
|
|
113
|
+
* @internal
|
|
114
|
+
*/
|
|
115
|
+
validateLinkFields(doctype, schema, registry) {
|
|
116
|
+
const issues = [];
|
|
117
|
+
for (const field of schema) {
|
|
118
|
+
const fieldtype = 'fieldtype' in field ? field.fieldtype : undefined;
|
|
119
|
+
// Check Link fields
|
|
120
|
+
if (fieldtype === 'Link') {
|
|
121
|
+
const options = 'options' in field ? field.options : undefined;
|
|
122
|
+
if (!options) {
|
|
123
|
+
issues.push({
|
|
124
|
+
severity: ValidationSeverity.ERROR,
|
|
125
|
+
rule: 'link-missing-options',
|
|
126
|
+
message: `Link field "${field.fieldname}" is missing options property (target doctype)`,
|
|
127
|
+
doctype,
|
|
128
|
+
fieldname: field.fieldname,
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// Check if target doctype exists in registry
|
|
133
|
+
// Options should be a string representing the target doctype name
|
|
134
|
+
const targetDoctype = typeof options === 'string' ? options : '';
|
|
135
|
+
if (!targetDoctype) {
|
|
136
|
+
issues.push({
|
|
137
|
+
severity: ValidationSeverity.ERROR,
|
|
138
|
+
rule: 'link-invalid-options',
|
|
139
|
+
message: `Link field "${field.fieldname}" has invalid options format (expected string doctype name)`,
|
|
140
|
+
doctype,
|
|
141
|
+
fieldname: field.fieldname,
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const targetMeta = registry.registry[targetDoctype] || registry.registry[targetDoctype.toLowerCase()];
|
|
146
|
+
if (!targetMeta) {
|
|
147
|
+
issues.push({
|
|
148
|
+
severity: ValidationSeverity.ERROR,
|
|
149
|
+
rule: 'link-invalid-target',
|
|
150
|
+
message: `Link field "${field.fieldname}" references non-existent doctype: "${targetDoctype}"`,
|
|
151
|
+
doctype,
|
|
152
|
+
fieldname: field.fieldname,
|
|
153
|
+
context: { targetDoctype },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Recursively check nested schemas
|
|
158
|
+
if ('schema' in field) {
|
|
159
|
+
const nestedSchema = field.schema;
|
|
160
|
+
const nestedArray = (Array.isArray(nestedSchema) ? nestedSchema : nestedSchema.toArray?.() || []);
|
|
161
|
+
issues.push(...this.validateLinkFields(doctype, nestedArray, registry));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return issues;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Validates link declarations: target resolution, backlink consistency, Link field correspondence
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
validateLinkDeclarations(doctype, links, schema, registry) {
|
|
171
|
+
const issues = [];
|
|
172
|
+
// Build a map of Link fields by fieldname for quick lookup
|
|
173
|
+
const linkFieldsByFieldname = new Map();
|
|
174
|
+
for (const field of schema) {
|
|
175
|
+
if ('fieldtype' in field && field.fieldtype === 'Link') {
|
|
176
|
+
linkFieldsByFieldname.set(field.fieldname, field);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
for (const [fieldname, link] of Object.entries(links)) {
|
|
180
|
+
// Check target resolves in registry
|
|
181
|
+
const targetDoctype = registry.registry[link.target];
|
|
182
|
+
if (!targetDoctype) {
|
|
183
|
+
issues.push({
|
|
184
|
+
severity: ValidationSeverity.ERROR,
|
|
185
|
+
rule: 'link-invalid-target',
|
|
186
|
+
message: `Link "${fieldname}" references non-existent doctype: "${link.target}"`,
|
|
187
|
+
doctype,
|
|
188
|
+
fieldname,
|
|
189
|
+
context: { target: link.target },
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Warn on self-referential target
|
|
194
|
+
if (link.target === doctype) {
|
|
195
|
+
issues.push({
|
|
196
|
+
severity: ValidationSeverity.WARNING,
|
|
197
|
+
rule: 'link-self-referential',
|
|
198
|
+
message: `Link "${fieldname}" is self-referential (target: "${link.target}")`,
|
|
199
|
+
doctype,
|
|
200
|
+
fieldname,
|
|
201
|
+
context: { target: link.target },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// Check backlink consistency
|
|
205
|
+
if (link.backlink && targetDoctype.links) {
|
|
206
|
+
const reciprocalLink = targetDoctype.links[link.backlink];
|
|
207
|
+
if (!reciprocalLink) {
|
|
208
|
+
issues.push({
|
|
209
|
+
severity: ValidationSeverity.ERROR,
|
|
210
|
+
rule: 'link-backlink-missing',
|
|
211
|
+
message: `Backlink "${link.backlink}" not found on target doctype "${link.target}"`,
|
|
212
|
+
doctype,
|
|
213
|
+
fieldname,
|
|
214
|
+
context: { backlink: link.backlink, target: link.target },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else if (reciprocalLink.target !== doctype) {
|
|
218
|
+
issues.push({
|
|
219
|
+
severity: ValidationSeverity.WARNING,
|
|
220
|
+
rule: 'link-backlink-mismatch',
|
|
221
|
+
message: `Backlink "${link.backlink}" on "${link.target}" points to "${reciprocalLink.target}" instead of "${doctype}"`,
|
|
222
|
+
doctype,
|
|
223
|
+
fieldname,
|
|
224
|
+
context: { backlink: link.backlink, target: link.target, actualTarget: reciprocalLink.target },
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// If Link field exists with same fieldname, verify it has matching target
|
|
229
|
+
// Only check if link has fieldname set (otherwise it's a standalone link without a field)
|
|
230
|
+
if (link.fieldname) {
|
|
231
|
+
const linkField = linkFieldsByFieldname.get(link.fieldname);
|
|
232
|
+
if (linkField) {
|
|
233
|
+
const linkFieldOptions = 'options' in linkField ? linkField.options : undefined;
|
|
234
|
+
const linkFieldTarget = typeof linkFieldOptions === 'string' ? linkFieldOptions : undefined;
|
|
235
|
+
if (linkFieldTarget && linkFieldTarget !== link.target) {
|
|
236
|
+
issues.push({
|
|
237
|
+
severity: ValidationSeverity.ERROR,
|
|
238
|
+
rule: 'link-field-target-mismatch',
|
|
239
|
+
message: `Link field "${link.fieldname}" targets "${linkFieldTarget}" but link declaration targets "${link.target}"`,
|
|
240
|
+
doctype,
|
|
241
|
+
fieldname: link.fieldname,
|
|
242
|
+
context: { linkFieldTarget, linkTarget: link.target },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Check that every Link field has a corresponding link declaration
|
|
249
|
+
// A Link field corresponds to a link if the link's fieldname property matches the field's fieldname
|
|
250
|
+
for (const [fieldname, _field] of linkFieldsByFieldname) {
|
|
251
|
+
const hasCorrespondingLink = Object.values(links).some(link => link.fieldname === fieldname);
|
|
252
|
+
if (!hasCorrespondingLink) {
|
|
253
|
+
issues.push({
|
|
254
|
+
severity: ValidationSeverity.ERROR,
|
|
255
|
+
rule: 'link-field-without-declaration',
|
|
256
|
+
message: `Link field "${fieldname}" has no corresponding link declaration`,
|
|
257
|
+
doctype,
|
|
258
|
+
fieldname,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return issues;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Validates workflow state machine configuration
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
validateWorkflow(doctype, workflow) {
|
|
269
|
+
const issues = [];
|
|
270
|
+
// Check for initial state
|
|
271
|
+
if (!workflow.initial && !workflow.type) {
|
|
272
|
+
issues.push({
|
|
273
|
+
severity: ValidationSeverity.WARNING,
|
|
274
|
+
rule: 'workflow-missing-initial',
|
|
275
|
+
message: 'Workflow is missing initial state property',
|
|
276
|
+
doctype,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
// Check for states
|
|
280
|
+
if (!workflow.states || Object.keys(workflow.states).length === 0) {
|
|
281
|
+
issues.push({
|
|
282
|
+
severity: ValidationSeverity.WARNING,
|
|
283
|
+
rule: 'workflow-no-states',
|
|
284
|
+
message: 'Workflow has no states defined',
|
|
285
|
+
doctype,
|
|
286
|
+
});
|
|
287
|
+
return issues;
|
|
288
|
+
}
|
|
289
|
+
// Validate initial state exists
|
|
290
|
+
if (workflow.initial && typeof workflow.initial === 'string' && !workflow.states[workflow.initial]) {
|
|
291
|
+
issues.push({
|
|
292
|
+
severity: ValidationSeverity.ERROR,
|
|
293
|
+
rule: 'workflow-invalid-initial',
|
|
294
|
+
message: `Workflow initial state "${workflow.initial}" does not exist in states`,
|
|
295
|
+
doctype,
|
|
296
|
+
context: { initialState: workflow.initial },
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// Check state reachability (simple check - all states should have at least one incoming transition or be initial)
|
|
300
|
+
const stateNames = Object.keys(workflow.states);
|
|
301
|
+
const reachableStates = new Set();
|
|
302
|
+
// Initial state is always reachable
|
|
303
|
+
if (workflow.initial && typeof workflow.initial === 'string') {
|
|
304
|
+
reachableStates.add(workflow.initial);
|
|
305
|
+
}
|
|
306
|
+
// Find all target states from transitions
|
|
307
|
+
for (const [_stateName, stateConfig] of Object.entries(workflow.states)) {
|
|
308
|
+
const state = stateConfig;
|
|
309
|
+
if (state.on) {
|
|
310
|
+
for (const [_event, transition] of Object.entries(state.on)) {
|
|
311
|
+
if (typeof transition === 'string') {
|
|
312
|
+
reachableStates.add(transition);
|
|
313
|
+
}
|
|
314
|
+
else if (transition && typeof transition === 'object') {
|
|
315
|
+
const target = 'target' in transition ? transition.target : undefined;
|
|
316
|
+
if (typeof target === 'string') {
|
|
317
|
+
reachableStates.add(target);
|
|
318
|
+
}
|
|
319
|
+
else if (Array.isArray(target)) {
|
|
320
|
+
target.forEach((t) => {
|
|
321
|
+
if (typeof t === 'string') {
|
|
322
|
+
reachableStates.add(t);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Check for unreachable states
|
|
331
|
+
for (const stateName of stateNames) {
|
|
332
|
+
if (!reachableStates.has(stateName)) {
|
|
333
|
+
issues.push({
|
|
334
|
+
severity: ValidationSeverity.WARNING,
|
|
335
|
+
rule: 'workflow-unreachable-state',
|
|
336
|
+
message: `Workflow state "${stateName}" may not be reachable`,
|
|
337
|
+
doctype,
|
|
338
|
+
context: { stateName },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return issues;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Validates that actions are registered in the FieldTriggerEngine
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
348
|
+
validateActionRegistration(doctype, actions) {
|
|
349
|
+
const issues = [];
|
|
350
|
+
const triggerEngine = getGlobalTriggerEngine();
|
|
351
|
+
for (const [triggerName, actionNames] of Object.entries(actions)) {
|
|
352
|
+
if (!Array.isArray(actionNames)) {
|
|
353
|
+
issues.push({
|
|
354
|
+
severity: ValidationSeverity.ERROR,
|
|
355
|
+
rule: 'action-invalid-format',
|
|
356
|
+
message: `Action configuration for "${triggerName}" must be an array`,
|
|
357
|
+
doctype,
|
|
358
|
+
context: { triggerName, actionNames },
|
|
359
|
+
});
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
// Check each action name
|
|
363
|
+
for (const actionName of actionNames) {
|
|
364
|
+
// Check if action is registered globally
|
|
365
|
+
const engine = triggerEngine;
|
|
366
|
+
const isRegistered = engine.globalActions?.has(actionName) || engine.globalTransitionActions?.has(actionName);
|
|
367
|
+
if (!isRegistered) {
|
|
368
|
+
issues.push({
|
|
369
|
+
severity: ValidationSeverity.WARNING,
|
|
370
|
+
rule: 'action-not-registered',
|
|
371
|
+
message: `Action "${actionName}" referenced in "${triggerName}" is not registered in FieldTriggerEngine`,
|
|
372
|
+
doctype,
|
|
373
|
+
context: { triggerName, actionName },
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return issues;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Creates a validator with the given registry
|
|
383
|
+
* @param registry - Registry instance
|
|
384
|
+
* @param options - Additional validator options
|
|
385
|
+
* @returns SchemaValidator instance
|
|
386
|
+
* @public
|
|
387
|
+
*/
|
|
388
|
+
export function createValidator(registry, options) {
|
|
389
|
+
return new SchemaValidator({
|
|
390
|
+
registry,
|
|
391
|
+
...options,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Quick validation helper
|
|
396
|
+
* @param doctype - Doctype name
|
|
397
|
+
* @param schema - Schema fields
|
|
398
|
+
* @param registry - Registry instance
|
|
399
|
+
* @param workflow - Optional workflow configuration
|
|
400
|
+
* @param actions - Optional actions map
|
|
401
|
+
* @returns Validation result
|
|
402
|
+
* @public
|
|
403
|
+
*/
|
|
404
|
+
export function validateSchema(doctype, schema, registry, workflow, actions) {
|
|
405
|
+
const validator = createValidator(registry);
|
|
406
|
+
return validator.validate(doctype, schema, workflow, actions);
|
|
407
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export shim — the implementation has moved to `composables/use-stonecrop.ts`.
|
|
3
|
+
*
|
|
4
|
+
* This file is kept so that any existing relative imports of `./composable` or
|
|
5
|
+
* `../composable` continue to resolve without changes. All public consumers
|
|
6
|
+
* should import from `@stonecrop/stonecrop` (the package root), which
|
|
7
|
+
* re-exports everything from `./composables/use-stonecrop` via `index.ts`.
|
|
8
|
+
*/
|
|
9
|
+
export type { OperationLogAPI, BaseStonecropReturn, HSTStonecropReturn, HSTChangeData } from './composables/stonecrop';
|
|
10
|
+
export { useStonecrop } from './composables/stonecrop';
|
|
11
|
+
//# sourceMappingURL=composable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composable.d.ts","sourceRoot":"","sources":["../../src/composable.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACtH,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA"}
|