@json-eval-rs/webcore 0.0.29

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 (4) hide show
  1. package/README.md +139 -0
  2. package/index.d.ts +368 -0
  3. package/index.js +715 -0
  4. package/package.json +27 -0
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # @json-eval-rs/webcore
2
+
3
+ High-level JavaScript API for JSON Eval RS WASM bindings.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Install bridge + your target WASM package
9
+ yarn install @json-eval-rs/webcore @json-eval-rs/bundler
10
+
11
+ # Or for direct browser use
12
+ yarn install @json-eval-rs/webcore @json-eval-rs/vanilla
13
+
14
+ # Or for Node.js
15
+ yarn install @json-eval-rs/webcore @json-eval-rs/node
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### With Bundler (Webpack, Vite, Next.js, etc.)
21
+
22
+ ```typescript
23
+ import { JSONEval } from '@json-eval-rs/webcore';
24
+ import * as wasmModule from '@json-eval-rs/bundler';
25
+
26
+ const evaluator = new JSONEval({
27
+ schema: {
28
+ type: 'object',
29
+ properties: {
30
+ name: {
31
+ type: 'string',
32
+ rules: {
33
+ required: { value: true, message: 'Name is required' },
34
+ minLength: { value: 3, message: 'Min 3 characters' }
35
+ }
36
+ }
37
+ }
38
+ },
39
+ wasmModule // Pass the WASM module explicitly
40
+ });
41
+
42
+ // Validate data
43
+ const result = await evaluator.validate({
44
+ data: { name: 'Jo' }
45
+ });
46
+
47
+ if (result.has_error) {
48
+ console.log('Errors:', result.errors);
49
+ }
50
+
51
+ // Don't forget to free resources
52
+ evaluator.free();
53
+ ```
54
+
55
+ ### Dynamic Import (for Next.js client components)
56
+
57
+ ```typescript
58
+ 'use client';
59
+
60
+ useEffect(() => {
61
+ Promise.all([
62
+ import('@json-eval-rs/webcore'),
63
+ import('@json-eval-rs/bundler')
64
+ ]).then(([{ JSONEval }, wasmModule]) => {
65
+ const evaluator = new JSONEval({ schema, wasmModule });
66
+ // Use evaluator...
67
+ });
68
+ }, []);
69
+ ```
70
+
71
+ ### API
72
+
73
+ #### `new JSONEval(options)`
74
+
75
+ Create a new evaluator instance.
76
+
77
+ **Options:**
78
+ - `schema` (required) - JSON schema object
79
+ - `context` (optional) - Context data object
80
+ - `data` (optional) - Initial data object
81
+ - `wasmModule` (optional) - Pre-loaded WASM module
82
+
83
+ #### `await evaluator.validate({ data, context? })`
84
+
85
+ Validate data against schema rules.
86
+
87
+ Returns: `{ has_error: boolean, errors: ValidationError[] }`
88
+
89
+ #### `await evaluator.evaluate({ data, context? })`
90
+
91
+ Evaluate schema with data.
92
+
93
+ Returns: Evaluated schema object
94
+
95
+ #### `await evaluator.evaluateDependents({ changedPaths, data, context?, nested? })`
96
+
97
+ Re-evaluate fields that depend on changed paths.
98
+
99
+ **Options:**
100
+ - `changedPaths` - Array of field paths that changed
101
+ - `data` - Current data
102
+ - `context` (optional) - Context data
103
+ - `nested` (optional, default: true) - Follow dependency chains
104
+
105
+ Returns: Updated evaluated schema
106
+
107
+ #### `evaluator.free()`
108
+
109
+ Free WASM resources. Call this when done.
110
+
111
+ ## Why Use the Core?
112
+
113
+ The core package provides:
114
+
115
+ 1. **Clean API** - Options objects instead of positional JSON strings
116
+ 2. **Type Safety** - Full TypeScript support
117
+ 3. **Auto-detection** - Automatically loads the right WASM target
118
+ 4. **Flexibility** - Works with bundler/web/node targets
119
+
120
+ ## Direct WASM Usage
121
+
122
+ If you prefer minimal overhead, you can use WASM packages directly:
123
+
124
+ ```typescript
125
+ import { JSONEvalWasm } from '@json-eval-rs/bundler';
126
+
127
+ const instance = new JSONEvalWasm(
128
+ JSON.stringify(schema),
129
+ JSON.stringify(context),
130
+ JSON.stringify(data)
131
+ );
132
+
133
+ const result = await instance.validate(JSON.stringify(data));
134
+ instance.free();
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,368 @@
1
+ /**
2
+ * @json-eval-rs/webcore - TypeScript definitions
3
+ */
4
+
5
+ /**
6
+ * Get the library version from the WASM module
7
+ * @param wasmModule - WASM module
8
+ * @returns Version string
9
+ */
10
+ export function getVersion(wasmModule: any): string;
11
+
12
+ /**
13
+ * Return format for path-based methods
14
+ */
15
+ export enum ReturnFormat {
16
+ /** Nested object preserving the path hierarchy (default) */
17
+ Nested = 0,
18
+ /** Flat object with dotted keys */
19
+ Flat = 1,
20
+ /** Array of values in the order of requested paths */
21
+ Array = 2
22
+ }
23
+
24
+ /**
25
+ * Validation error for a specific field
26
+ */
27
+ export interface ValidationError {
28
+ /** Field path with the error */
29
+ path: string;
30
+ /** Type of validation rule that failed (e.g., 'required', 'min', 'max', 'pattern') */
31
+ rule_type: string;
32
+ /** Error message */
33
+ message: string;
34
+ /** Optional error code */
35
+ code?: string;
36
+ /** Optional regex pattern (for pattern validation errors) */
37
+ pattern?: string;
38
+ /** Optional field value that failed validation (as string) */
39
+ field_value?: string;
40
+ /** Optional additional data context for the error */
41
+ data?: any;
42
+ }
43
+
44
+ /**
45
+ * Result of validation operation
46
+ */
47
+ export interface ValidationResult {
48
+ /** Whether any validation errors occurred */
49
+ has_error: boolean;
50
+ /** Array of validation errors */
51
+ errors: ValidationError[];
52
+ }
53
+
54
+ /**
55
+ * Dependent field change from evaluateDependents
56
+ */
57
+ export interface DependentChange {
58
+ /** Path of the dependent field (in dot notation) */
59
+ $ref: string;
60
+ /** Schema definition of the changed field */
61
+ $field?: any;
62
+ /** Schema definition of the parent field */
63
+ $parentField: any;
64
+ /** Whether this is a transitive dependency */
65
+ transitive: boolean;
66
+ /** If true, the field was cleared */
67
+ clear?: boolean;
68
+ /** New value of the field (if changed) */
69
+ value?: any;
70
+ }
71
+
72
+ /**
73
+ * Options for creating a JSONEval instance
74
+ */
75
+ export interface JSONEvalOptions {
76
+ /** JSON schema object */
77
+ schema: any;
78
+ /** Optional context data */
79
+ context?: any;
80
+ /** Optional initial data */
81
+ data?: any;
82
+ /** WASM module instance */
83
+ wasmModule?: any;
84
+ }
85
+
86
+ /**
87
+ * Options for validation
88
+ */
89
+ export interface ValidateOptions {
90
+ /** JSON data to validate */
91
+ data: any;
92
+ /** Optional context data */
93
+ context?: any;
94
+ }
95
+
96
+ /**
97
+ * Options for evaluation
98
+ */
99
+ export interface EvaluateOptions {
100
+ /** JSON data to evaluate */
101
+ data: any;
102
+ /** Optional context data */
103
+ context?: any;
104
+ }
105
+
106
+ /**
107
+ * Options for evaluating dependents
108
+ */
109
+ export interface EvaluateDependentsOptions {
110
+ /** Array of field paths that changed */
111
+ changedPaths: string[];
112
+ /** Updated JSON data */
113
+ data?: any;
114
+ /** Optional context data */
115
+ context?: any;
116
+ /** If true, performs full evaluation after processing dependents */
117
+ reEvaluate?: boolean;
118
+ }
119
+
120
+ /**
121
+ * Options for getting evaluated schema
122
+ */
123
+ export interface GetEvaluatedSchemaOptions {
124
+ /** Whether to skip layout resolution */
125
+ skipLayout?: boolean;
126
+ }
127
+
128
+ /**
129
+ * Options for getting a value by path from evaluated schema
130
+ */
131
+ export interface GetValueByPathOptions {
132
+ /** Dotted path to the value */
133
+ path: string;
134
+ /** Whether to skip layout resolution */
135
+ skipLayout?: boolean;
136
+ }
137
+
138
+ /**
139
+ * Options for getting values by multiple paths from evaluated schema
140
+ */
141
+ export interface GetValueByPathsOptions {
142
+ /** Array of dotted paths to retrieve */
143
+ paths: string[];
144
+ /** Whether to skip layout resolution */
145
+ skipLayout?: boolean;
146
+ /** Return format (Nested, Flat, or Array) */
147
+ format?: ReturnFormat;
148
+ }
149
+
150
+ /**
151
+ * Options for getting a value by path from schema
152
+ */
153
+ export interface GetSchemaByPathOptions {
154
+ /** Dotted path to the value */
155
+ path: string;
156
+ }
157
+
158
+ /**
159
+ * Options for getting values by multiple paths from schema
160
+ */
161
+ export interface GetSchemaByPathsOptions {
162
+ /** Array of dotted paths to retrieve */
163
+ paths: string[];
164
+ /** Return format (Nested, Flat, or Array) */
165
+ format?: ReturnFormat;
166
+ }
167
+
168
+ /**
169
+ * Options for reloading schema
170
+ */
171
+ export interface ReloadSchemaOptions {
172
+ /** New JSON schema */
173
+ schema: any;
174
+ /** Optional new context */
175
+ context?: any;
176
+ /** Optional new data */
177
+ data?: any;
178
+ }
179
+
180
+ /**
181
+ * Cache statistics
182
+ */
183
+ export interface CacheStats {
184
+ /** Number of cache hits */
185
+ hits: number;
186
+ /** Number of cache misses */
187
+ misses: number;
188
+ /** Number of cached entries */
189
+ entries: number;
190
+ }
191
+
192
+ /**
193
+ * Options for evaluating a subform
194
+ */
195
+ export interface EvaluateSubformOptions {
196
+ /** Path to the subform */
197
+ subformPath: string;
198
+ /** JSON data to evaluate */
199
+ data: any;
200
+ /** Optional context data */
201
+ context?: any;
202
+ }
203
+
204
+ /**
205
+ * Options for validating a subform
206
+ */
207
+ export interface ValidateSubformOptions {
208
+ /** Path to the subform */
209
+ subformPath: string;
210
+ /** JSON data to validate */
211
+ data: any;
212
+ /** Optional context data */
213
+ context?: any;
214
+ }
215
+
216
+ /**
217
+ * Options for evaluating dependents in a subform
218
+ */
219
+ export interface EvaluateDependentsSubformOptions {
220
+ /** Path to the subform */
221
+ subformPath: string;
222
+ /** Array of field paths that changed */
223
+ changedPaths: string[];
224
+ /** Updated JSON data */
225
+ data?: any;
226
+ /** Optional context data */
227
+ context?: any;
228
+ /** If true, performs full evaluation after processing dependents */
229
+ reEvaluate?: boolean;
230
+ }
231
+
232
+ /**
233
+ * Options for resolving layout in a subform
234
+ */
235
+ export interface ResolveLayoutSubformOptions {
236
+ /** Path to the subform */
237
+ subformPath: string;
238
+ /** Whether to evaluate after resolving layout */
239
+ evaluate?: boolean;
240
+ }
241
+
242
+ /**
243
+ * Options for getting evaluated schema from a subform
244
+ */
245
+ export interface GetEvaluatedSchemaSubformOptions {
246
+ /** Path to the subform */
247
+ subformPath: string;
248
+ /** Whether to resolve layout */
249
+ resolveLayout?: boolean;
250
+ }
251
+
252
+ /**
253
+ * Options for getting schema value from a subform
254
+ */
255
+ export interface GetSchemaValueSubformOptions {
256
+ /** Path to the subform */
257
+ subformPath: string;
258
+ }
259
+
260
+ /**
261
+ * Options for getting evaluated schema by path from a subform
262
+ */
263
+ export interface GetEvaluatedSchemaByPathSubformOptions {
264
+ /** Path to the subform */
265
+ subformPath: string;
266
+ /** Dotted path to the value within the subform */
267
+ schemaPath: string;
268
+ /** Whether to skip layout resolution */
269
+ skipLayout?: boolean;
270
+ }
271
+
272
+ /**
273
+ * Options for getting evaluated schema by multiple paths from a subform
274
+ */
275
+ export interface GetEvaluatedSchemaByPathsSubformOptions {
276
+ /** Path to the subform */
277
+ subformPath: string;
278
+ /** Array of dotted paths to retrieve within the subform */
279
+ schemaPaths: string[];
280
+ /** Whether to skip layout resolution */
281
+ skipLayout?: boolean;
282
+ /** Return format (Nested, Flat, or Array) */
283
+ format?: ReturnFormat;
284
+ }
285
+
286
+ /**
287
+ * Options for getting schema by path from a subform
288
+ */
289
+ export interface GetSchemaByPathSubformOptions {
290
+ /** Path to the subform */
291
+ subformPath: string;
292
+ /** Dotted path to the value within the subform */
293
+ schemaPath: string;
294
+ }
295
+
296
+ /**
297
+ * Options for getting schema by multiple paths from a subform
298
+ */
299
+ export interface GetSchemaByPathsSubformOptions {
300
+ /** Path to the subform */
301
+ subformPath: string;
302
+ /** Array of dotted paths to retrieve within the subform */
303
+ schemaPaths: string[];
304
+ /** Return format (Nested, Flat, or Array) */
305
+ format?: ReturnFormat;
306
+ }
307
+
308
+ /**
309
+ * Options for compiling and running logic
310
+ */
311
+ export interface CompileAndRunLogicOptions {
312
+ /** Logic expression as string or object */
313
+ logicStr: string | object;
314
+ /** Optional data context */
315
+ data?: any;
316
+ /** Optional context data */
317
+ context?: any;
318
+ }
319
+
320
+ export class JSONEval {
321
+ constructor(options: JSONEvalOptions);
322
+ static fromCache(cacheKey: string, context?: any, data?: any): JSONEval;
323
+ init(): Promise<void>;
324
+ validate(options: ValidateOptions): Promise<ValidationResult>;
325
+ evaluate(options: EvaluateOptions): Promise<any>;
326
+ evaluateDependents(options: EvaluateDependentsOptions): Promise<DependentChange[]>;
327
+ compileAndRunLogic(options: CompileAndRunLogicOptions): Promise<any>;
328
+ compileLogic(logicStr: string | object): Promise<number>;
329
+ runLogic(logicId: number, data?: any, context?: any): Promise<any>;
330
+ getEvaluatedSchema(options?: GetEvaluatedSchemaOptions): Promise<any>;
331
+ getSchemaValue(): Promise<any>;
332
+ getEvaluatedSchemaWithoutParams(options?: GetEvaluatedSchemaOptions): Promise<any>;
333
+ getValueByPath(options: GetValueByPathOptions): Promise<any | null>;
334
+ getEvaluatedSchemaByPath(options: GetValueByPathOptions): Promise<any | null>;
335
+ getEvaluatedSchemaByPaths(options: GetValueByPathsOptions): Promise<any>;
336
+ getSchemaByPath(options: GetSchemaByPathOptions): Promise<any | null>;
337
+ getSchemaByPaths(options: GetSchemaByPathsOptions): Promise<any>;
338
+ reloadSchema(options: ReloadSchemaOptions): Promise<void>;
339
+ reloadSchemaMsgpack(schemaMsgpack: Uint8Array, context?: any, data?: any): Promise<void>;
340
+ reloadSchemaFromCache(cacheKey: string, context?: any, data?: any): Promise<void>;
341
+ cacheStats(): Promise<CacheStats>;
342
+ clearCache(): Promise<void>;
343
+ cacheLen(): Promise<number>;
344
+ enableCache(): Promise<void>;
345
+ disableCache(): Promise<void>;
346
+ isCacheEnabled(): boolean;
347
+
348
+ // Subform methods
349
+ evaluateSubform(options: EvaluateSubformOptions): Promise<void>;
350
+ validateSubform(options: ValidateSubformOptions): Promise<ValidationResult>;
351
+ evaluateDependentsSubform(options: EvaluateDependentsSubformOptions): Promise<DependentChange[]>;
352
+ resolveLayoutSubform(options: ResolveLayoutSubformOptions): Promise<void>;
353
+ getEvaluatedSchemaSubform(options: GetEvaluatedSchemaSubformOptions): Promise<any>;
354
+ getSchemaValueSubform(options: GetSchemaValueSubformOptions): Promise<any>;
355
+ getEvaluatedSchemaWithoutParamsSubform(options: GetEvaluatedSchemaSubformOptions): Promise<any>;
356
+ getEvaluatedSchemaByPathSubform(options: GetEvaluatedSchemaByPathSubformOptions): Promise<any | null>;
357
+ getEvaluatedSchemaByPathsSubform(options: GetEvaluatedSchemaByPathsSubformOptions): Promise<any>;
358
+ getSchemaByPathSubform(options: GetSchemaByPathSubformOptions): Promise<any | null>;
359
+ getSchemaByPathsSubform(options: GetSchemaByPathsSubformOptions): Promise<any>;
360
+ getSubformPaths(): Promise<string[]>;
361
+ hasSubform(subformPath: string): Promise<boolean>;
362
+
363
+ free(): void;
364
+ }
365
+
366
+ export function version(wasmModule?: any): Promise<string>;
367
+
368
+ export default JSONEval;
package/index.js ADDED
@@ -0,0 +1,715 @@
1
+ /**
2
+ * @json-eval-rs/webcore
3
+ * High-level JavaScript API for JSON Eval RS WASM bindings
4
+ *
5
+ * This package provides a clean, ergonomic API that works with any WASM target:
6
+ */
7
+
8
+ /**
9
+ * Get the library version from the WASM module
10
+ * @param {any} wasmModule - WASM module
11
+ * @returns {string} Version string
12
+ */
13
+ export function getVersion(wasmModule) {
14
+ if (wasmModule && typeof wasmModule.getVersion === 'function') {
15
+ return wasmModule.getVersion();
16
+ }
17
+ return 'unknown';
18
+ }
19
+
20
+ /**
21
+ * JSONEval - High-level JavaScript API for JSON Eval RS
22
+ *
23
+ * This is an internal abstraction layer. Use specific packages instead:
24
+ * - @json-eval-rs/bundler (for bundlers like Webpack, Vite, Next.js)
25
+ * - @json-eval-rs/vanilla (for direct browser usage)
26
+ * - @json-eval-rs/node (for Node.js/SSR)
27
+ *
28
+ * @example
29
+ * ```js
30
+ * import { JSONEval } from '@json-eval-rs/webcore';
31
+ *
32
+ * const evaluator = new JSONEval({
33
+ * schema: { type: 'object', properties: { ... } }
34
+ * });
35
+ *
36
+ * await evaluator.init();
37
+ * const result = await evaluator.validate({ data: { name: 'John' } });
38
+ * ```
39
+ */
40
+ export class JSONEvalCore {
41
+ /**
42
+ * @param {any} wasmModule - WASM module (injected by wrapper package)
43
+ * @param {object} options
44
+ * @param {object|Uint8Array|string} options.schema - JSON schema, MessagePack bytes, or cache key
45
+ * @param {object} [options.context] - Optional context data
46
+ * @param {object} [options.data] - Optional initial data
47
+ * @param {boolean} [options.fromCache] - If true, schema is treated as a cache key
48
+ */
49
+ constructor(wasmModule, { schema, context, data, fromCache = false }) {
50
+ this._schema = schema;
51
+ this._wasmModule = wasmModule;
52
+ this._context = context;
53
+ this._data = data;
54
+ this._instance = null;
55
+ this._ready = false;
56
+ this._isMsgpackSchema = schema instanceof Uint8Array;
57
+ this._isFromCache = fromCache;
58
+ }
59
+
60
+ /**
61
+ * Initialize the WASM instance
62
+ * Call this before using other methods, or use the async methods which call it automatically
63
+ */
64
+ async init() {
65
+ if (this._ready) return;
66
+
67
+ // If WASM module not provided, throw error - user must provide it or install peer dependency
68
+ if (!this._wasmModule) {
69
+ throw new Error(
70
+ 'No WASM module provided. Please either:\n' +
71
+ '1. Pass wasmModule in constructor: new JSONEval({ schema, wasmModule: await import("@json-eval-rs/bundler") })\n' +
72
+ '2. Or install a peer dependency: yarn install @json-eval-rs/bundler (or @json-eval-rs/vanilla or @json-eval-rs/node)'
73
+ );
74
+ }
75
+
76
+ try {
77
+ const { JSONEvalWasm } = this._wasmModule;
78
+
79
+ // Create instance from cache, MessagePack, or JSON
80
+ if (this._isFromCache) {
81
+ this._instance = JSONEvalWasm.newFromCache(
82
+ this._schema, // cache key
83
+ this._context ? JSON.stringify(this._context) : null,
84
+ this._data ? JSON.stringify(this._data) : null
85
+ );
86
+ } else if (this._isMsgpackSchema) {
87
+ this._instance = JSONEvalWasm.newFromMsgpack(
88
+ this._schema,
89
+ this._context ? JSON.stringify(this._context) : null,
90
+ this._data ? JSON.stringify(this._data) : null
91
+ );
92
+ } else {
93
+ this._instance = new JSONEvalWasm(
94
+ JSON.stringify(this._schema),
95
+ this._context ? JSON.stringify(this._context) : null,
96
+ this._data ? JSON.stringify(this._data) : null
97
+ );
98
+ }
99
+ this._ready = true;
100
+ } catch (error) {
101
+ throw new Error(`Failed to create JSONEval instance: ${error.message || error}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Create a new JSONEval instance from a cached ParsedSchema
107
+ * Static factory method for convenience
108
+ *
109
+ * @param {any} wasmModule - WASM module
110
+ * @param {string} cacheKey - Cache key to lookup in ParsedSchemaCache
111
+ * @param {object} [context] - Optional context data
112
+ * @param {object} [data] - Optional initial data
113
+ * @returns {JSONEvalCore} New instance
114
+ */
115
+ static fromCache(wasmModule, cacheKey, context, data) {
116
+ return new JSONEvalCore(wasmModule, {
117
+ schema: cacheKey,
118
+ context,
119
+ data,
120
+ fromCache: true
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Validate data against schema (returns parsed JavaScript object)
126
+ * Uses validateJS for Worker-safe serialization
127
+ * @param {object} options
128
+ * @param {object} options.data - Data to validate
129
+ * @param {object} [options.context] - Optional context
130
+ * @returns {Promise<{has_error: boolean, errors: Array<{path: string, rule_type: string, message: string}>}>}
131
+ */
132
+ async validate({ data, context }) {
133
+ await this.init();
134
+ try {
135
+ // Use validateJS for proper serialization (Worker-safe)
136
+ return this._instance.validateJS(
137
+ JSON.stringify(data),
138
+ context ? JSON.stringify(context) : null
139
+ );
140
+ } catch (error) {
141
+ throw new Error(`Validation failed: ${error.message || error}`);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Evaluate schema with data (returns parsed JavaScript object)
147
+ * @param {object} options
148
+ * @param {object} options.data - Data to evaluate
149
+ * @param {object} [options.context] - Optional context
150
+ * @returns {Promise<any>}
151
+ */
152
+ async evaluate({ data, context }) {
153
+ await this.init();
154
+ try {
155
+ return this._instance.evaluateJS(
156
+ JSON.stringify(data),
157
+ context ? JSON.stringify(context) : null
158
+ );
159
+ } catch (error) {
160
+ throw new Error(`Evaluation failed: ${error.message || error}`);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Evaluate dependent fields (returns parsed JavaScript object, processes transitively)
166
+ * @param {object} options
167
+ * @param {string[]} options.changedPaths - Array of changed field paths (e.g., ["#/illustration/properties/field1", "field2"])
168
+ * @param {object} [options.data] - Optional updated data (null to use existing)
169
+ * @param {object} [options.context] - Optional context
170
+ * @param {boolean} [options.reEvaluate] - If true, performs full evaluation after processing dependents
171
+ * @returns {Promise<Array>} Array of dependent change objects
172
+ */
173
+ async evaluateDependents({ changedPaths, data, context, reEvaluate = false }) {
174
+ await this.init();
175
+ try {
176
+ return this._instance.evaluateDependentsJS(
177
+ JSON.stringify(changedPaths),
178
+ data ? JSON.stringify(data) : null,
179
+ context ? JSON.stringify(context) : null,
180
+ reEvaluate
181
+ );
182
+ } catch (error) {
183
+ throw new Error(`Dependent evaluation failed: ${error.message || error}`);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Get evaluated schema
189
+ * @param {object} [options]
190
+ * @param {boolean} [options.skipLayout=false] - Skip layout resolution
191
+ * @returns {Promise<any>}
192
+ */
193
+ async getEvaluatedSchema({ skipLayout = false } = {}) {
194
+ await this.init();
195
+ return this._instance.getEvaluatedSchemaJS(skipLayout);
196
+ }
197
+
198
+ /**
199
+ * Get evaluated schema as MessagePack binary data
200
+ * @param {object} [options]
201
+ * @param {boolean} [options.skipLayout=false] - Skip layout resolution
202
+ * @returns {Promise<Uint8Array>} MessagePack-encoded schema bytes
203
+ */
204
+ async getEvaluatedSchemaMsgpack({ skipLayout = false } = {}) {
205
+ await this.init();
206
+ return this._instance.getEvaluatedSchemaMsgpack(skipLayout);
207
+ }
208
+
209
+ /**
210
+ * Get schema values (evaluations ending with .value)
211
+ * @returns {Promise<object>}
212
+ */
213
+ async getSchemaValue() {
214
+ await this.init();
215
+ return this._instance.getSchemaValue();
216
+ }
217
+
218
+ /**
219
+ * Get evaluated schema without $params field
220
+ * @param {object} [options]
221
+ * @param {boolean} [options.skipLayout=false] - Skip layout resolution
222
+ * @returns {Promise<any>}
223
+ */
224
+ async getEvaluatedSchemaWithoutParams({ skipLayout = false } = {}) {
225
+ await this.init();
226
+ return this._instance.getEvaluatedSchemaWithoutParamsJS(skipLayout);
227
+ }
228
+
229
+ /**
230
+ * Get a value from the evaluated schema using dotted path notation
231
+ * @param {object} options
232
+ * @param {string} options.path - Dotted path to the value (e.g., "properties.field.value")
233
+ * @param {boolean} [options.skipLayout=false] - Skip layout resolution
234
+ * @returns {Promise<any|null>} Value at the path, or null if not found
235
+ */
236
+ async getEvaluatedSchemaByPath({ path, skipLayout = false }) {
237
+ await this.init();
238
+ return this._instance.getEvaluatedSchemaByPathJS(path, skipLayout);
239
+ }
240
+
241
+ /**
242
+ * Get values from the evaluated schema using multiple dotted path notations
243
+ * Returns data in the specified format (skips paths that are not found)
244
+ * @param {object} options
245
+ * @param {string[]} options.paths - Array of dotted paths to retrieve
246
+ * @param {boolean} [options.skipLayout=false] - Skip layout resolution
247
+ * @param {number} [options.format=0] - Return format (0=Nested, 1=Flat, 2=Array)
248
+ * @returns {Promise<any>} Data in specified format
249
+ */
250
+ async getEvaluatedSchemaByPaths({ paths, skipLayout = false, format = 0 }) {
251
+ await this.init();
252
+ return this._instance.getEvaluatedSchemaByPathsJS(JSON.stringify(paths), skipLayout, format);
253
+ }
254
+
255
+ /**
256
+ * Get a value from the schema using dotted path notation
257
+ * @param {object} options
258
+ * @param {string} options.path - Dotted path to the value (e.g., "properties.field.value")
259
+ * @returns {Promise<any|null>} Value at the path, or null if not found
260
+ */
261
+ async getSchemaByPath({ path }) {
262
+ await this.init();
263
+ return this._instance.getSchemaByPathJS(path);
264
+ }
265
+
266
+ /**
267
+ * Get values from the schema using multiple dotted path notations
268
+ * Returns data in the specified format (skips paths that are not found)
269
+ * @param {object} options
270
+ * @param {string[]} options.paths - Array of dotted paths to retrieve
271
+ * @param {number} [options.format=0] - Return format (0=Nested, 1=Flat, 2=Array)
272
+ * @returns {Promise<any>} Data in specified format
273
+ */
274
+ async getSchemaByPaths({ paths, format = 0 }) {
275
+ await this.init();
276
+ return this._instance.getSchemaByPathsJS(JSON.stringify(paths), format);
277
+ }
278
+
279
+ /**
280
+ * Reload schema with new data
281
+ * @param {object} options
282
+ * @param {object} options.schema - New JSON schema
283
+ * @param {object} [options.context] - Optional new context
284
+ * @param {object} [options.data] - Optional new data
285
+ * @returns {Promise<void>}
286
+ */
287
+ async reloadSchema({ schema, context, data }) {
288
+ if (!this._instance) {
289
+ throw new Error('Instance not initialized. Call init() first.');
290
+ }
291
+
292
+ try {
293
+ await this._instance.reloadSchema(
294
+ JSON.stringify(schema),
295
+ context ? JSON.stringify(context) : null,
296
+ data ? JSON.stringify(data) : null
297
+ );
298
+
299
+ // Update internal state
300
+ this._schema = schema;
301
+ this._context = context;
302
+ this._data = data;
303
+ } catch (error) {
304
+ throw new Error(`Failed to reload schema: ${error.message || error}`);
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Reload schema from MessagePack bytes
310
+ * @param {Uint8Array} schemaMsgpack - MessagePack-encoded schema bytes
311
+ * @param {object} [context] - Optional new context
312
+ * @param {object} [data] - Optional new data
313
+ * @returns {Promise<void>}
314
+ */
315
+ async reloadSchemaMsgpack(schemaMsgpack, context, data) {
316
+ if (!this._instance) {
317
+ throw new Error('Instance not initialized. Call init() first.');
318
+ }
319
+
320
+ if (!(schemaMsgpack instanceof Uint8Array)) {
321
+ throw new Error('schemaMsgpack must be a Uint8Array');
322
+ }
323
+
324
+ try {
325
+ await this._instance.reloadSchemaMsgpack(
326
+ schemaMsgpack,
327
+ context ? JSON.stringify(context) : null,
328
+ data ? JSON.stringify(data) : null
329
+ );
330
+
331
+ // Update internal state
332
+ this._schema = schemaMsgpack;
333
+ this._context = context;
334
+ this._data = data;
335
+ this._isMsgpackSchema = true;
336
+ } catch (error) {
337
+ throw new Error(`Failed to reload schema from MessagePack: ${error.message || error}`);
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Reload schema from ParsedSchemaCache using a cache key
343
+ * @param {string} cacheKey - Cache key to lookup in the global ParsedSchemaCache
344
+ * @param {object} [context] - Optional new context
345
+ * @param {object} [data] - Optional new data
346
+ * @returns {Promise<void>}
347
+ */
348
+ async reloadSchemaFromCache(cacheKey, context, data) {
349
+ if (!this._instance) {
350
+ throw new Error('Instance not initialized. Call init() first.');
351
+ }
352
+
353
+ if (typeof cacheKey !== 'string' || !cacheKey) {
354
+ throw new Error('cacheKey must be a non-empty string');
355
+ }
356
+
357
+ try {
358
+ await this._instance.reloadSchemaFromCache(
359
+ cacheKey,
360
+ context ? JSON.stringify(context) : null,
361
+ data ? JSON.stringify(data) : null
362
+ );
363
+
364
+ // Update internal state
365
+ this._context = context;
366
+ this._data = data;
367
+ // Note: schema is not updated as we don't have access to it from the cache key
368
+ } catch (error) {
369
+ throw new Error(`Failed to reload schema from cache: ${error.message || error}`);
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Get cache statistics
375
+ * @returns {Promise<{hits: number, misses: number, entries: number}>}
376
+ */
377
+ async cacheStats() {
378
+ await this.init();
379
+ return this._instance.cacheStats();
380
+ }
381
+
382
+ /**
383
+ * Clear the evaluation cache
384
+ * @returns {Promise<void>}
385
+ */
386
+ async clearCache() {
387
+ await this.init();
388
+ this._instance.clearCache();
389
+ }
390
+
391
+ /**
392
+ * Get the number of cached entries
393
+ * @returns {Promise<number>}
394
+ */
395
+ async cacheLen() {
396
+ await this.init();
397
+ return this._instance.cacheLen();
398
+ }
399
+
400
+ /**
401
+ * Enable evaluation caching
402
+ * Useful for reusing JSONEval instances with different data
403
+ * @returns {Promise<void>}
404
+ */
405
+ async enableCache() {
406
+ await this.init();
407
+ this._instance.enableCache();
408
+ }
409
+
410
+ /**
411
+ * Disable evaluation caching
412
+ * Useful for web API usage where each request creates a new JSONEval instance
413
+ * Improves performance by skipping cache operations that have no benefit for single-use instances
414
+ * @returns {Promise<void>}
415
+ */
416
+ async disableCache() {
417
+ await this.init();
418
+ this._instance.disableCache();
419
+ }
420
+
421
+ /**
422
+ * Check if evaluation caching is enabled
423
+ * @returns {boolean}
424
+ */
425
+ isCacheEnabled() {
426
+ if (!this._instance) return true; // Default is enabled
427
+ return this._instance.isCacheEnabled();
428
+ }
429
+
430
+ /**
431
+ * Resolve layout with optional evaluation
432
+ * @param {object} [options]
433
+ * @param {boolean} [options.evaluate=false] - If true, runs evaluation before resolving layout
434
+ * @returns {Promise<void>}
435
+ */
436
+ async resolveLayout({ evaluate = false } = {}) {
437
+ await this.init();
438
+ return this._instance.resolveLayout(evaluate);
439
+ }
440
+
441
+ /**
442
+ * Compile and run JSON logic from a JSON logic string
443
+ * @param {object} options - Options for compile and run logic
444
+ * @param {string|object} options.logicStr - JSON logic expression as a string or object
445
+ * @param {object} [options.data] - Optional data to evaluate against (uses existing data if not provided)
446
+ * @param {object} [options.context] - Optional context to use (uses existing context if not provided)
447
+ * @returns {Promise<any>} Result of the evaluation
448
+ */
449
+ async compileAndRunLogic({ logicStr, data, context }) {
450
+ await this.init();
451
+ const logic = typeof logicStr === 'string' ? logicStr : JSON.stringify(logicStr);
452
+ const result = await this._instance.compileAndRunLogic(
453
+ logic,
454
+ data ? JSON.stringify(data) : null,
455
+ context ? JSON.stringify(context) : null
456
+ );
457
+ // Parse result if it's a string
458
+ return typeof result === 'string' ? JSON.parse(result) : result;
459
+ }
460
+
461
+ /**
462
+ * Compile JSON logic and return a global ID
463
+ * @param {string|object} logicStr - JSON logic expression as a string or object
464
+ * @returns {Promise<number>} Compiled logic ID
465
+ */
466
+ async compileLogic(logicStr) {
467
+ await this.init();
468
+ const logic = typeof logicStr === 'string' ? logicStr : JSON.stringify(logicStr);
469
+ return this._instance.compileLogic(logic);
470
+ }
471
+
472
+ /**
473
+ * Run pre-compiled logic by ID
474
+ * @param {number} logicId - Compiled logic ID from compileLogic
475
+ * @param {object} [data] - Optional data to evaluate against (uses existing data if not provided)
476
+ * @param {object} [context] - Optional context to use (uses existing context if not provided)
477
+ * @returns {Promise<any>} Result of the evaluation
478
+ */
479
+ async runLogic(logicId, data, context) {
480
+ await this.init();
481
+ const result = await this._instance.runLogic(
482
+ logicId,
483
+ data ? JSON.stringify(data) : null,
484
+ context ? JSON.stringify(context) : null
485
+ );
486
+ // Parse result if it's a string
487
+ return typeof result === 'string' ? JSON.parse(result) : result;
488
+ }
489
+
490
+ /**
491
+ * Validate data against schema rules with optional path filtering
492
+ * @param {object} options
493
+ * @param {object} options.data - Data to validate
494
+ * @param {object} [options.context] - Optional context
495
+ * @param {Array<string>} [options.paths] - Optional array of paths to validate (null for all)
496
+ * @returns {Promise<{has_error: boolean, errors: Array<{path: string, rule_type: string, message: string}>}>}
497
+ */
498
+ async validatePaths({ data, context, paths }) {
499
+ await this.init();
500
+ try {
501
+ // Use validatePathsJS for proper serialization (Worker-safe)
502
+ return this._instance.validatePathsJS(
503
+ JSON.stringify(data),
504
+ context ? JSON.stringify(context) : null,
505
+ paths || null
506
+ );
507
+ } catch (error) {
508
+ throw new Error(`Validation failed: ${error.message || error}`);
509
+ }
510
+ }
511
+
512
+ // ============================================================================
513
+ // Subform Methods
514
+ // ============================================================================
515
+
516
+ /**
517
+ * Evaluate a subform with data
518
+ * @param {object} options
519
+ * @param {string} options.subformPath - Path to the subform (e.g., "#/riders")
520
+ * @param {object} options.data - Data for the subform
521
+ * @param {object} [options.context] - Optional context
522
+ * @returns {Promise<void>}
523
+ */
524
+ async evaluateSubform({ subformPath, data, context }) {
525
+ await this.init();
526
+ return this._instance.evaluateSubform(
527
+ subformPath,
528
+ JSON.stringify(data),
529
+ context ? JSON.stringify(context) : null
530
+ );
531
+ }
532
+
533
+ /**
534
+ * Validate subform data against its schema rules
535
+ * @param {object} options
536
+ * @param {string} options.subformPath - Path to the subform
537
+ * @param {object} options.data - Data for the subform
538
+ * @param {object} [options.context] - Optional context
539
+ * @returns {Promise<{has_error: boolean, errors: Array}>}
540
+ */
541
+ async validateSubform({ subformPath, data, context }) {
542
+ await this.init();
543
+ return this._instance.validateSubform(
544
+ subformPath,
545
+ JSON.stringify(data),
546
+ context ? JSON.stringify(context) : null
547
+ );
548
+ }
549
+
550
+ /**
551
+ * Evaluate dependent fields in subform
552
+ * @param {object} options
553
+ * @param {string} options.subformPath - Path to the subform
554
+ * @param {string[]} options.changedPaths - Array of field paths that changed
555
+ * @param {object} [options.data] - Optional updated data
556
+ * @param {object} [options.context] - Optional context
557
+ * @param {boolean} [options.reEvaluate=false] - If true, performs full evaluation after processing dependents
558
+ * @returns {Promise<any>}
559
+ */
560
+ async evaluateDependentsSubform({ subformPath, changedPaths, data, context, reEvaluate = false }) {
561
+ await this.init();
562
+
563
+ // For backward compatibility, accept single changedPath too
564
+ const paths = Array.isArray(changedPaths) ? changedPaths : [changedPaths];
565
+
566
+ return this._instance.evaluateDependentsSubformJS(
567
+ subformPath,
568
+ paths[0], // WASM still expects single path (wraps internally)
569
+ data ? JSON.stringify(data) : null,
570
+ context ? JSON.stringify(context) : null
571
+ );
572
+ }
573
+
574
+ /**
575
+ * Resolve layout for subform
576
+ * @param {object} options
577
+ * @param {string} options.subformPath - Path to the subform
578
+ * @param {boolean} [options.evaluate=false] - If true, runs evaluation before resolving layout
579
+ * @returns {Promise<void>}
580
+ */
581
+ async resolveLayoutSubform({ subformPath, evaluate = false }) {
582
+ await this.init();
583
+ return this._instance.resolveLayoutSubform(subformPath, evaluate);
584
+ }
585
+
586
+ /**
587
+ * Get evaluated schema from subform
588
+ * @param {object} options
589
+ * @param {string} options.subformPath - Path to the subform
590
+ * @param {boolean} [options.resolveLayout=false] - Whether to resolve layout
591
+ * @returns {Promise<any>}
592
+ */
593
+ async getEvaluatedSchemaSubform({ subformPath, resolveLayout = false }) {
594
+ await this.init();
595
+ return this._instance.getEvaluatedSchemaSubformJS(subformPath, resolveLayout);
596
+ }
597
+
598
+ /**
599
+ * Get schema value from subform (all .value fields)
600
+ * @param {object} options
601
+ * @param {string} options.subformPath - Path to the subform
602
+ * @returns {Promise<any>}
603
+ */
604
+ async getSchemaValueSubform({ subformPath }) {
605
+ await this.init();
606
+ return this._instance.getSchemaValueSubform(subformPath);
607
+ }
608
+
609
+ /**
610
+ * Get evaluated schema without $params from subform
611
+ * @param {object} options
612
+ * @param {string} options.subformPath - Path to the subform
613
+ * @param {boolean} [options.resolveLayout=false] - Whether to resolve layout
614
+ * @returns {Promise<any>}
615
+ */
616
+ async getEvaluatedSchemaWithoutParamsSubform({ subformPath, resolveLayout = false }) {
617
+ await this.init();
618
+ return this._instance.getEvaluatedSchemaWithoutParamsSubformJS(subformPath, resolveLayout);
619
+ }
620
+
621
+ /**
622
+ * Get evaluated schema by specific path from subform
623
+ * @param {object} options
624
+ * @param {string} options.subformPath - Path to the subform
625
+ * @param {string} options.schemaPath - Path within the subform
626
+ * @param {boolean} [options.skipLayout=false] - Whether to skip layout resolution
627
+ * @returns {Promise<any|null>}
628
+ */
629
+ async getEvaluatedSchemaByPathSubform({ subformPath, schemaPath, skipLayout = false }) {
630
+ await this.init();
631
+ return this._instance.getEvaluatedSchemaByPathSubformJS(subformPath, schemaPath, skipLayout);
632
+ }
633
+
634
+ /**
635
+ * Get evaluated schema by multiple paths from subform
636
+ * Returns data in the specified format (skips paths that are not found)
637
+ * @param {object} options
638
+ * @param {string} options.subformPath - Path to the subform
639
+ * @param {string[]} options.schemaPaths - Array of paths within the subform
640
+ * @param {boolean} [options.skipLayout=false] - Whether to skip layout resolution
641
+ * @param {number} [options.format=0] - Return format (0=Nested, 1=Flat, 2=Array)
642
+ * @returns {Promise<any>}
643
+ */
644
+ async getEvaluatedSchemaByPathsSubform({ subformPath, schemaPaths, skipLayout = false, format = 0 }) {
645
+ await this.init();
646
+ return this._instance.getEvaluatedSchemaByPathsSubformJS(subformPath, JSON.stringify(schemaPaths), skipLayout, format);
647
+ }
648
+
649
+ /**
650
+ * Get list of available subform paths
651
+ * @returns {Promise<Array<string>>}
652
+ */
653
+ async getSubformPaths() {
654
+ await this.init();
655
+ return this._instance.getSubformPaths();
656
+ }
657
+
658
+ /**
659
+ * Get schema by specific path from subform
660
+ * @param {object} options
661
+ * @param {string} options.subformPath - Path to the subform
662
+ * @param {string} options.schemaPath - Path within the subform
663
+ * @returns {Promise<any|null>}
664
+ */
665
+ async getSchemaByPathSubform({ subformPath, schemaPath }) {
666
+ await this.init();
667
+ return this._instance.getSchemaByPathSubformJS(subformPath, schemaPath);
668
+ }
669
+
670
+ /**
671
+ * Get schema by multiple paths from subform
672
+ * Returns data in the specified format (skips paths that are not found)
673
+ * @param {object} options
674
+ * @param {string} options.subformPath - Path to the subform
675
+ * @param {string[]} options.schemaPaths - Array of paths within the subform
676
+ * @param {number} [options.format=0] - Return format (0=Nested, 1=Flat, 2=Array)
677
+ * @returns {Promise<any>}
678
+ */
679
+ async getSchemaByPathsSubform({ subformPath, schemaPaths, format = 0 }) {
680
+ await this.init();
681
+ return this._instance.getSchemaByPathsSubformJS(subformPath, JSON.stringify(schemaPaths), format);
682
+ }
683
+
684
+ /**
685
+ * Check if a subform exists at the given path
686
+ * @param {string} subformPath - Path to check
687
+ * @returns {Promise<boolean>}
688
+ */
689
+ async hasSubform(subformPath) {
690
+ await this.init();
691
+ return this._instance.hasSubform(subformPath);
692
+ }
693
+
694
+ /**
695
+ * Free WASM resources
696
+ */
697
+ free() {
698
+ if (this._instance) {
699
+ this._instance.free();
700
+ this._instance = null;
701
+ this._ready = false;
702
+ }
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Get library version (internal - use from specific packages)
708
+ * @param {any} wasmModule - WASM module
709
+ * @returns {string}
710
+ */
711
+ export function getVersion(wasmModule) {
712
+ return wasmModule.version();
713
+ }
714
+
715
+ export default JSONEvalCore;
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@json-eval-rs/webcore",
3
+ "version": "0.0.29",
4
+ "description": "JSON Eval RS core JavaScript wrapper (internal package - not published)",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "index.js",
10
+ "index.d.ts"
11
+ ],
12
+ "keywords": [
13
+ "json",
14
+ "json-logic",
15
+ "schema",
16
+ "validation",
17
+ "wasm"
18
+ ],
19
+ "author": "Muhamad Rizki",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/byrizki/json-eval-rs.git",
24
+ "directory": "bindings/web/packages/core"
25
+ },
26
+ "sideEffects": false
27
+ }