@stonecrop/stonecrop 0.6.3 → 0.7.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.
@@ -9,10 +9,13 @@ import Registry from './registry';
9
9
  import { Stonecrop } from './stonecrop';
10
10
  import { HST, createHST, type HSTNode } from './stores/hst';
11
11
  import { useOperationLogStore } from './stores/operation-log';
12
+ import { SchemaValidator, createValidator, validateSchema } from './schema-validator';
12
13
  export type * from './types';
13
14
  export type { BaseStonecropReturn, HSTChangeData, HSTStonecropReturn, OperationLogAPI } from './composable';
14
15
  export type { FieldTriggerEngine } from './field-triggers';
15
16
  export type { FieldChangeContext, TransitionChangeContext, FieldTriggerExecutionResult, ActionExecutionResult, TransitionExecutionResult, FieldActionFunction, TransitionActionFunction, } from './types/field-triggers';
16
- export { DoctypeMeta, Registry, Stonecrop, useStonecrop, HST, createHST, HSTNode, getGlobalTriggerEngine, registerGlobalAction, registerTransitionAction, setFieldRollback, triggerTransition, markOperationIrreversible, useOperationLog, useOperationLogStore, useUndoRedoShortcuts, withBatch, };
17
+ export type { ValidationIssue, ValidationResult, ValidatorOptions } from './schema-validator';
18
+ export { ValidationSeverity } from './schema-validator';
19
+ export { DoctypeMeta, Registry, Stonecrop, useStonecrop, HST, createHST, HSTNode, getGlobalTriggerEngine, registerGlobalAction, registerTransitionAction, setFieldRollback, triggerTransition, markOperationIrreversible, SchemaValidator, createValidator, validateSchema, useOperationLog, useOperationLogStore, useUndoRedoShortcuts, withBatch, };
17
20
  export default plugin;
18
21
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,wBAAwB,CAAA;AAC3C,mBAAmB,yBAAyB,CAAA;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC9F,OAAO,WAAW,MAAM,WAAW,CAAA;AACnC,OAAO,EACN,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,MAAM,MAAM,WAAW,CAAA;AAC9B,OAAO,QAAQ,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAC7D,mBAAmB,SAAS,CAAA;AAC5B,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC3G,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,YAAY,EACX,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,EAC3B,qBAAqB,EACrB,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,GACxB,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EACN,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,EAEZ,GAAG,EACH,SAAS,EACT,OAAO,EAEP,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EAEzB,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,SAAS,GACT,CAAA;AAGD,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,wBAAwB,CAAA;AAC3C,mBAAmB,yBAAyB,CAAA;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAC9F,OAAO,WAAW,MAAM,WAAW,CAAA;AACnC,OAAO,EACN,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,MAAM,MAAM,WAAW,CAAA;AAC9B,OAAO,QAAQ,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAE7D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACrF,mBAAmB,SAAS,CAAA;AAC5B,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC3G,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,YAAY,EACX,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,EAC3B,qBAAqB,EACrB,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,GACxB,MAAM,wBAAwB,CAAA;AAE/B,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAEvD,OAAO,EACN,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,EAEZ,GAAG,EACH,SAAS,EACT,OAAO,EAEP,sBAAsB,EACtB,oBAAoB,EACpB,wBAAwB,EACxB,gBAAgB,EAChB,iBAAiB,EACjB,yBAAyB,EAEzB,eAAe,EACf,eAAe,EACf,cAAc,EAEd,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,SAAS,GACT,CAAA;AAGD,eAAe,MAAM,CAAA"}
package/dist/src/index.js CHANGED
@@ -7,11 +7,16 @@ import Registry from './registry';
7
7
  import { Stonecrop } from './stonecrop';
8
8
  import { HST, createHST } from './stores/hst';
9
9
  import { useOperationLogStore } from './stores/operation-log';
10
+ // Export schema validator
11
+ import { SchemaValidator, createValidator, validateSchema } from './schema-validator';
12
+ export { ValidationSeverity } from './schema-validator';
10
13
  export { DoctypeMeta, Registry, Stonecrop, useStonecrop,
11
14
  // HST exports for advanced usage
12
15
  HST, createHST,
13
16
  // Field trigger system exports
14
17
  getGlobalTriggerEngine, registerGlobalAction, registerTransitionAction, setFieldRollback, triggerTransition, markOperationIrreversible,
18
+ // Schema validator exports
19
+ SchemaValidator, createValidator, validateSchema,
15
20
  // Operation log exports
16
21
  useOperationLog, useOperationLogStore, useUndoRedoShortcuts, withBatch, };
17
22
  // Default export is the Vue plugin
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Schema Validation Utilities
3
+ * Validates Stonecrop schemas for integrity and consistency
4
+ * @packageDocumentation
5
+ */
6
+ import type { SchemaTypes } from '@stonecrop/aform';
7
+ import type { List, Map as ImmutableMap } from 'immutable';
8
+ import type { AnyStateNodeConfig } from 'xstate';
9
+ import type Registry from './registry';
10
+ /**
11
+ * Validation severity levels
12
+ * @public
13
+ */
14
+ export declare enum ValidationSeverity {
15
+ /** Blocking error that prevents save */
16
+ ERROR = "error",
17
+ /** Advisory warning that allows save */
18
+ WARNING = "warning",
19
+ /** Informational message */
20
+ INFO = "info"
21
+ }
22
+ /**
23
+ * Validation issue
24
+ * @public
25
+ */
26
+ export interface ValidationIssue {
27
+ /** Severity level */
28
+ severity: ValidationSeverity;
29
+ /** Validation rule that failed */
30
+ rule: string;
31
+ /** Human-readable message */
32
+ message: string;
33
+ /** Doctype name */
34
+ doctype?: string;
35
+ /** Field name if applicable */
36
+ fieldname?: string;
37
+ /** Additional context */
38
+ context?: Record<string, unknown>;
39
+ }
40
+ /**
41
+ * Validation result
42
+ * @public
43
+ */
44
+ export interface ValidationResult {
45
+ /** Whether validation passed (no blocking errors) */
46
+ valid: boolean;
47
+ /** List of validation issues */
48
+ issues: ValidationIssue[];
49
+ /** Count of errors */
50
+ errorCount: number;
51
+ /** Count of warnings */
52
+ warningCount: number;
53
+ /** Count of info messages */
54
+ infoCount: number;
55
+ }
56
+ /**
57
+ * Schema validator options
58
+ * @public
59
+ */
60
+ export interface ValidatorOptions {
61
+ /** Registry instance for doctype lookups */
62
+ registry?: Registry;
63
+ /** Whether to validate Link field targets */
64
+ validateLinkTargets?: boolean;
65
+ /** Whether to validate workflow reachability */
66
+ validateWorkflows?: boolean;
67
+ /** Whether to validate action registration */
68
+ validateActions?: boolean;
69
+ /** Whether to validate required schema properties */
70
+ validateRequiredProperties?: boolean;
71
+ }
72
+ /**
73
+ * Schema validator class
74
+ * @public
75
+ */
76
+ export declare class SchemaValidator {
77
+ private options;
78
+ /**
79
+ * Creates a new SchemaValidator instance
80
+ * @param options - Validator configuration options
81
+ */
82
+ constructor(options?: ValidatorOptions);
83
+ /**
84
+ * Validates a complete doctype schema
85
+ * @param doctype - Doctype name
86
+ * @param schema - Schema fields (List or Array)
87
+ * @param workflow - Optional workflow configuration
88
+ * @param actions - Optional actions map
89
+ * @returns Validation result
90
+ */
91
+ validate(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>): ValidationResult;
92
+ /**
93
+ * Validates that required schema properties are present
94
+ * @internal
95
+ */
96
+ private validateRequiredProperties;
97
+ /**
98
+ * Validates Link field targets exist in registry
99
+ * @internal
100
+ */
101
+ private validateLinkFields;
102
+ /**
103
+ * Validates workflow state machine configuration
104
+ * @internal
105
+ */
106
+ private validateWorkflow;
107
+ /**
108
+ * Validates that actions are registered in the FieldTriggerEngine
109
+ * @internal
110
+ */
111
+ private validateActionRegistration;
112
+ }
113
+ /**
114
+ * Creates a validator with the given registry
115
+ * @param registry - Registry instance
116
+ * @param options - Additional validator options
117
+ * @returns SchemaValidator instance
118
+ * @public
119
+ */
120
+ export declare function createValidator(registry: Registry, options?: Partial<ValidatorOptions>): SchemaValidator;
121
+ /**
122
+ * Quick validation helper
123
+ * @param doctype - Doctype name
124
+ * @param schema - Schema fields
125
+ * @param registry - Registry instance
126
+ * @param workflow - Optional workflow configuration
127
+ * @param actions - Optional actions map
128
+ * @returns Validation result
129
+ * @public
130
+ */
131
+ export declare function validateSchema(doctype: string, schema: List<SchemaTypes> | SchemaTypes[] | undefined, registry: Registry, workflow?: AnyStateNodeConfig, actions?: ImmutableMap<string, string[]> | Map<string, string[]>): ValidationResult;
132
+ //# sourceMappingURL=schema-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/schema-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,315 @@
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
+ }