@json-eval-rs/react-native 0.0.28

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/src/index.tsx ADDED
@@ -0,0 +1,999 @@
1
+ import React from 'react';
2
+ import { NativeModules, Platform } from 'react-native';
3
+
4
+ const LINKING_ERROR =
5
+ `The package '@json-eval-rs/react-native' doesn't seem to be linked. Make sure: \n\n` +
6
+ Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
7
+ '- You rebuilt the app after installing the package\n' +
8
+ '- You are not using Expo managed workflow\n';
9
+
10
+ const JsonEvalRs = NativeModules.JsonEvalRs
11
+ ? NativeModules.JsonEvalRs
12
+ : new Proxy(
13
+ {},
14
+ {
15
+ get() {
16
+ throw new Error(LINKING_ERROR);
17
+ },
18
+ }
19
+ );
20
+
21
+ /**
22
+ * Validation error for a specific field
23
+ */
24
+ export interface ValidationError {
25
+ /** Field path with the error */
26
+ path: string;
27
+ /** Type of validation rule that failed */
28
+ ruleType: string;
29
+ /** Error message */
30
+ message: string;
31
+ }
32
+
33
+ /**
34
+ * Result of validation operation
35
+ */
36
+ export interface ValidationResult {
37
+ /** Whether any validation errors occurred */
38
+ hasError: boolean;
39
+ /** Array of validation errors */
40
+ errors: ValidationError[];
41
+ }
42
+
43
+ /**
44
+ * Dependent field change from evaluateDependents
45
+ */
46
+ export interface DependentChange {
47
+ /** Path of the dependent field that changed */
48
+ path: string;
49
+ /** New value of the dependent field */
50
+ value: any;
51
+ }
52
+
53
+ /**
54
+ * Options for creating a JSONEval instance
55
+ */
56
+ export interface JSONEvalOptions {
57
+ /** JSON schema string or object */
58
+ schema: string | object;
59
+ /** Optional context data (string or object) */
60
+ context?: string | object;
61
+ /** Optional initial data (string or object) */
62
+ data?: string | object;
63
+ }
64
+
65
+ /**
66
+ * Options for evaluation
67
+ */
68
+ export interface EvaluateOptions {
69
+ /** JSON data string or object */
70
+ data: string | object;
71
+ /** Optional context data (string or object) */
72
+ context?: string | object;
73
+ }
74
+
75
+ /**
76
+ * Options for validation with path filtering
77
+ */
78
+ export interface ValidatePathsOptions {
79
+ /** JSON data string or object */
80
+ data: string | object;
81
+ /** Optional context data (string or object) */
82
+ context?: string | object;
83
+ /** Optional array of paths to validate (if not provided, validates all) */
84
+ paths?: string[];
85
+ }
86
+
87
+ /**
88
+ * Cache statistics
89
+ */
90
+ export interface CacheStats {
91
+ /** Number of cache hits */
92
+ hits: number;
93
+ /** Number of cache misses */
94
+ misses: number;
95
+ /** Number of cached entries */
96
+ entries: number;
97
+ }
98
+
99
+ /**
100
+ * Options for evaluating dependents
101
+ */
102
+ export interface EvaluateDependentsOptions {
103
+ /** Array of field paths that changed */
104
+ changedPaths: string[];
105
+ /** Updated JSON data (string or object) */
106
+ data?: string | object;
107
+ /** Optional context data (string or object) */
108
+ context?: string | object;
109
+ /** If true, performs full evaluation after processing dependents */
110
+ reEvaluate?: boolean;
111
+ }
112
+
113
+ /**
114
+ * Options for evaluating a subform
115
+ */
116
+ export interface EvaluateSubformOptions {
117
+ /** Path to the subform (e.g., "#/riders") */
118
+ subformPath: string;
119
+ /** JSON data string or object */
120
+ data: string | object;
121
+ /** Optional context data (string or object) */
122
+ context?: string | object;
123
+ }
124
+
125
+ /**
126
+ * Options for validating a subform
127
+ */
128
+ export interface ValidateSubformOptions {
129
+ /** Path to the subform */
130
+ subformPath: string;
131
+ /** JSON data string or object */
132
+ data: string | object;
133
+ /** Optional context data (string or object) */
134
+ context?: string | object;
135
+ }
136
+
137
+ /**
138
+ * Options for evaluating dependents in a subform
139
+ */
140
+ export interface EvaluateDependentsSubformOptions {
141
+ /** Path to the subform */
142
+ subformPath: string;
143
+ /** Array of field paths that changed */
144
+ changedPaths: string[];
145
+ /** Optional updated JSON data (string or object) */
146
+ data?: string | object;
147
+ /** Optional context data (string or object) */
148
+ context?: string | object;
149
+ /** If true, performs full evaluation after processing dependents */
150
+ reEvaluate?: boolean;
151
+ }
152
+
153
+ /**
154
+ * Options for resolving layout in a subform
155
+ */
156
+ export interface ResolveLayoutSubformOptions {
157
+ /** Path to the subform */
158
+ subformPath: string;
159
+ /** If true, runs evaluation before resolving layout */
160
+ evaluate?: boolean;
161
+ }
162
+
163
+ /**
164
+ * Options for getting evaluated schema from a subform
165
+ */
166
+ export interface GetEvaluatedSchemaSubformOptions {
167
+ /** Path to the subform */
168
+ subformPath: string;
169
+ /** Whether to resolve layout */
170
+ resolveLayout?: boolean;
171
+ }
172
+
173
+ /**
174
+ * Options for getting schema value from a subform
175
+ */
176
+ export interface GetSchemaValueSubformOptions {
177
+ /** Path to the subform */
178
+ subformPath: string;
179
+ }
180
+
181
+ /**
182
+ * Options for getting evaluated schema by path from a subform
183
+ */
184
+ export interface GetEvaluatedSchemaByPathSubformOptions {
185
+ /** Path to the subform */
186
+ subformPath: string;
187
+ /** Dotted path to the value within the subform */
188
+ schemaPath: string;
189
+ /** Whether to skip layout resolution */
190
+ skipLayout?: boolean;
191
+ }
192
+
193
+ /**
194
+ * Options for getting evaluated schema by multiple paths from a subform
195
+ */
196
+ export interface GetEvaluatedSchemaByPathsSubformOptions {
197
+ /** Path to the subform */
198
+ subformPath: string;
199
+ /** Array of dotted paths to the values within the subform */
200
+ schemaPaths: string[];
201
+ /** Whether to skip layout resolution */
202
+ skipLayout?: boolean;
203
+ }
204
+
205
+ /**
206
+ * Options for getting schema by path from a subform
207
+ */
208
+ export interface GetSchemaByPathSubformOptions {
209
+ /** Path to the subform */
210
+ subformPath: string;
211
+ /** Dotted path to the value within the subform */
212
+ schemaPath: string;
213
+ }
214
+
215
+ /**
216
+ * Options for getting schema by multiple paths from a subform
217
+ */
218
+ export interface GetSchemaByPathsSubformOptions {
219
+ /** Path to the subform */
220
+ subformPath: string;
221
+ /** Array of dotted paths to the values within the subform */
222
+ schemaPaths: string[];
223
+ }
224
+
225
+ /**
226
+ * High-performance JSON Logic evaluator with schema validation for React Native
227
+ *
228
+ * ## Zero-Copy Architecture
229
+ *
230
+ * This binding is optimized for minimal memory copies:
231
+ * - **Rust FFI Layer**: Returns raw pointers (zero-copy)
232
+ * - **C++ Bridge**: Uses direct pointer access with single-copy string construction
233
+ * - **Native Platform**: Minimizes intermediate conversions
234
+ * - **JS Bridge**: React Native's architecture requires serialization (unavoidable)
235
+ *
236
+ * While true zero-copy across JS/Native boundary is not possible due to React Native's
237
+ * architecture, we minimize copies within the native layer to maximize performance.
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * import { JSONEval } from '@json-eval-rs/react-native';
242
+ *
243
+ * const schema = {
244
+ * type: 'object',
245
+ * properties: {
246
+ * user: {
247
+ * type: 'object',
248
+ * properties: {
249
+ * name: {
250
+ * type: 'string',
251
+ * rules: {
252
+ * required: { value: true, message: 'Name is required' }
253
+ * }
254
+ * }
255
+ * }
256
+ * }
257
+ * }
258
+ * };
259
+ *
260
+ * const eval = new JSONEval({ schema });
261
+ *
262
+ * const data = { user: { name: 'John' } };
263
+ * const result = await eval.evaluate({ data });
264
+ * console.log(result);
265
+ *
266
+ * const validation = await eval.validate({ data });
267
+ * if (validation.hasError) {
268
+ * console.error('Validation errors:', validation.errors);
269
+ * }
270
+ *
271
+ * await eval.dispose();
272
+ * ```
273
+ */
274
+ export class JSONEval {
275
+ private handle: string;
276
+ private disposed: boolean = false;
277
+
278
+ /**
279
+ * Creates a new JSON evaluator instance from a cached ParsedSchema
280
+ * @param cacheKey - Cache key to lookup in the global ParsedSchemaCache
281
+ * @param context - Optional context data
282
+ * @param data - Optional initial data
283
+ * @returns New JSONEval instance
284
+ * @throws {Error} If schema not found in cache or creation fails
285
+ */
286
+ static fromCache(
287
+ cacheKey: string,
288
+ context?: string | object | null,
289
+ data?: string | object | null
290
+ ): JSONEval {
291
+ const contextStr = context ? (typeof context === 'string' ? context : JSON.stringify(context)) : null;
292
+ const dataStr = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : null;
293
+
294
+ const handle = JsonEvalRs.createFromCache(cacheKey, contextStr, dataStr);
295
+ return new JSONEval({ schema: {}, _handle: handle });
296
+ }
297
+
298
+ /**
299
+ * Creates a new JSON evaluator instance
300
+ * @param options - Configuration options with schema, context, and data
301
+ * @throws {Error} If creation fails
302
+ */
303
+ constructor(options: JSONEvalOptions & { _handle?: string }) {
304
+ // If handle is provided (from static factory), use it directly
305
+ if (options._handle) {
306
+ this.handle = options._handle;
307
+ return;
308
+ }
309
+
310
+ const { schema, context, data } = options;
311
+
312
+ try {
313
+ const schemaStr = typeof schema === 'string' ? schema : JSON.stringify(schema);
314
+ const contextStr = context ? (typeof context === 'string' ? context : JSON.stringify(context)) : null;
315
+ const dataStr = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : null;
316
+
317
+ this.handle = JsonEvalRs.create(schemaStr, contextStr, dataStr);
318
+ } catch (error) {
319
+ const errorMessage = error instanceof Error ? error.message : String(error);
320
+ throw new Error(`Failed to create JSONEval instance: ${errorMessage}`);
321
+ }
322
+ }
323
+
324
+ private throwIfDisposed() {
325
+ if (this.disposed) {
326
+ throw new Error('JSONEval instance has been disposed');
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Convert value to JSON string
332
+ * Performance note: If you have a pre-serialized JSON string, pass it directly
333
+ * instead of an object to avoid the JSON.stringify overhead
334
+ */
335
+ private toJsonString(value: string | object): string {
336
+ return typeof value === 'string' ? value : JSON.stringify(value);
337
+ }
338
+
339
+ /**
340
+ * Evaluate schema with provided data
341
+ * @param options - Evaluation options
342
+ * @returns Promise resolving to evaluated schema object
343
+ * @throws {Error} If evaluation fails
344
+ */
345
+ async evaluate(options: EvaluateOptions): Promise<any> {
346
+ this.throwIfDisposed();
347
+
348
+ try {
349
+ const dataStr = this.toJsonString(options.data);
350
+ const contextStr = options.context ? this.toJsonString(options.context) : null;
351
+
352
+ const resultStr = await JsonEvalRs.evaluate(this.handle, dataStr, contextStr);
353
+ return JSON.parse(resultStr);
354
+ } catch (error) {
355
+ const errorMessage = error instanceof Error ? error.message : String(error);
356
+ throw new Error(`Evaluation failed: ${errorMessage}`);
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Validate data against schema rules
362
+ * @param options - Validation options
363
+ * @returns Promise resolving to ValidationResult
364
+ * @throws {Error} If validation operation fails
365
+ */
366
+ async validate(options: EvaluateOptions): Promise<ValidationResult> {
367
+ this.throwIfDisposed();
368
+
369
+ try {
370
+ const dataStr = this.toJsonString(options.data);
371
+ const contextStr = options.context ? this.toJsonString(options.context) : null;
372
+
373
+ const resultStr = await JsonEvalRs.validate(this.handle, dataStr, contextStr);
374
+ return JSON.parse(resultStr);
375
+ } catch (error) {
376
+ const errorMessage = error instanceof Error ? error.message : String(error);
377
+ throw new Error(`Validation failed: ${errorMessage}`);
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Re-evaluate fields that depend on a changed path
383
+ * @param options - Dependent evaluation options
384
+ * @returns Promise resolving to array of dependent field changes
385
+ * @throws {Error} If evaluation fails
386
+ */
387
+ async evaluateDependents(options: EvaluateDependentsOptions): Promise<DependentChange[]> {
388
+ this.throwIfDisposed();
389
+
390
+ try {
391
+ const { changedPaths, data, context, reEvaluate = false } = options;
392
+ const changedPathsJson = JSON.stringify(changedPaths);
393
+ const dataStr = data ? this.toJsonString(data) : null;
394
+ const contextStr = context ? this.toJsonString(context) : null;
395
+
396
+ const resultStr = await JsonEvalRs.evaluateDependents(
397
+ this.handle,
398
+ changedPathsJson,
399
+ dataStr,
400
+ contextStr,
401
+ reEvaluate
402
+ );
403
+ return JSON.parse(resultStr);
404
+ } catch (error) {
405
+ const errorMessage = error instanceof Error ? error.message : String(error);
406
+ throw new Error(`Dependent evaluation failed: ${errorMessage}`);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Get the evaluated schema with optional layout resolution
412
+ * @param skipLayout - Whether to skip layout resolution (default: false)
413
+ * @returns Promise resolving to evaluated schema object
414
+ * @throws {Error} If operation fails
415
+ */
416
+ async getEvaluatedSchema(skipLayout: boolean = false): Promise<any> {
417
+ this.throwIfDisposed();
418
+ const resultStr = await JsonEvalRs.getEvaluatedSchema(this.handle, skipLayout);
419
+ return JSON.parse(resultStr);
420
+ }
421
+
422
+ /**
423
+ * Get all schema values (evaluations ending with .value)
424
+ * @returns Promise resolving to map of path -> value
425
+ * @throws {Error} If operation fails
426
+ */
427
+ async getSchemaValue(): Promise<Record<string, any>> {
428
+ this.throwIfDisposed();
429
+ const resultStr = await JsonEvalRs.getSchemaValue(this.handle);
430
+ return JSON.parse(resultStr);
431
+ }
432
+
433
+ /**
434
+ * Get the evaluated schema without $params field
435
+ * @param skipLayout - Whether to skip layout resolution (default: false)
436
+ * @returns Promise resolving to evaluated schema object
437
+ * @throws {Error} If operation fails
438
+ */
439
+ async getEvaluatedSchemaWithoutParams(skipLayout: boolean = false): Promise<any> {
440
+ this.throwIfDisposed();
441
+ const resultStr = await JsonEvalRs.getEvaluatedSchemaWithoutParams(this.handle, skipLayout);
442
+ return JSON.parse(resultStr);
443
+ }
444
+
445
+ /**
446
+ * Get a value from the evaluated schema using dotted path notation
447
+ * @param path - Dotted path to the value (e.g., "properties.field.value")
448
+ * @param skipLayout - Whether to skip layout resolution
449
+ * @returns Promise resolving to the value at the path, or null if not found
450
+ * @throws {Error} If operation fails
451
+ */
452
+ async getEvaluatedSchemaByPath(path: string, skipLayout: boolean = false): Promise<any | null> {
453
+ this.throwIfDisposed();
454
+ const resultStr = await JsonEvalRs.getEvaluatedSchemaByPath(this.handle, path, skipLayout);
455
+ return resultStr ? JSON.parse(resultStr) : null;
456
+ }
457
+
458
+ /**
459
+ * Get values from the evaluated schema using multiple dotted path notations
460
+ * Returns a merged object containing all requested paths (skips paths that are not found)
461
+ * @param paths - Array of dotted paths to retrieve
462
+ * @param skipLayout - Whether to skip layout resolution
463
+ * @returns Promise resolving to merged object containing all found paths
464
+ * @throws {Error} If operation fails
465
+ */
466
+ async getEvaluatedSchemaByPaths(paths: string[], skipLayout: boolean = false): Promise<any> {
467
+ this.throwIfDisposed();
468
+ const pathsJson = JSON.stringify(paths);
469
+ const resultStr = await JsonEvalRs.getEvaluatedSchemaByPaths(this.handle, pathsJson, skipLayout);
470
+ return JSON.parse(resultStr);
471
+ }
472
+
473
+ /**
474
+ * Get a value from the schema using dotted path notation
475
+ * @param path - Dotted path to the value (e.g., "properties.field.value")
476
+ * @returns Promise resolving to the value at the path, or null if not found
477
+ * @throws {Error} If operation fails
478
+ */
479
+ async getSchemaByPath(path: string): Promise<any | null> {
480
+ this.throwIfDisposed();
481
+ const resultStr = await JsonEvalRs.getSchemaByPath(this.handle, path);
482
+ return resultStr ? JSON.parse(resultStr) : null;
483
+ }
484
+
485
+ /**
486
+ * Get values from the schema using multiple dotted path notations
487
+ * Returns a merged object containing all requested paths (skips paths that are not found)
488
+ * @param paths - Array of dotted paths to retrieve
489
+ * @returns Promise resolving to merged object containing all found paths
490
+ * @throws {Error} If operation fails
491
+ */
492
+ async getSchemaByPaths(paths: string[]): Promise<any> {
493
+ this.throwIfDisposed();
494
+ const pathsJson = JSON.stringify(paths);
495
+ const resultStr = await JsonEvalRs.getSchemaByPaths(this.handle, pathsJson);
496
+ return JSON.parse(resultStr);
497
+ }
498
+
499
+ /**
500
+ * Reload schema with new data
501
+ * @param options - Configuration options with new schema, context, and data
502
+ * @throws {Error} If reload fails
503
+ */
504
+ async reloadSchema(options: JSONEvalOptions): Promise<void> {
505
+ this.throwIfDisposed();
506
+
507
+ try {
508
+ const { schema, context, data } = options;
509
+ const schemaStr = typeof schema === 'string' ? schema : JSON.stringify(schema);
510
+ const contextStr = context ? (typeof context === 'string' ? context : JSON.stringify(context)) : null;
511
+ const dataStr = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : null;
512
+
513
+ await JsonEvalRs.reloadSchema(this.handle, schemaStr, contextStr, dataStr);
514
+ } catch (error) {
515
+ const errorMessage = error instanceof Error ? error.message : String(error);
516
+ throw new Error(`Failed to reload schema: ${errorMessage}`);
517
+ }
518
+ }
519
+
520
+ /**
521
+ * Reload schema from MessagePack bytes
522
+ * @param schemaMsgpack - MessagePack-encoded schema bytes (Uint8Array or number array)
523
+ * @param context - Optional context data
524
+ * @param data - Optional initial data
525
+ * @throws {Error} If reload fails
526
+ */
527
+ async reloadSchemaMsgpack(
528
+ schemaMsgpack: Uint8Array | number[],
529
+ context?: string | object | null,
530
+ data?: string | object | null
531
+ ): Promise<void> {
532
+ this.throwIfDisposed();
533
+
534
+ try {
535
+ // Convert Uint8Array to number array if needed
536
+ const msgpackArray = schemaMsgpack instanceof Uint8Array
537
+ ? Array.from(schemaMsgpack)
538
+ : schemaMsgpack;
539
+
540
+ const contextStr = context ? (typeof context === 'string' ? context : JSON.stringify(context)) : null;
541
+ const dataStr = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : null;
542
+
543
+ await JsonEvalRs.reloadSchemaMsgpack(this.handle, msgpackArray, contextStr, dataStr);
544
+ } catch (error) {
545
+ const errorMessage = error instanceof Error ? error.message : String(error);
546
+ throw new Error(`Failed to reload schema from MessagePack: ${errorMessage}`);
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Reload schema from ParsedSchemaCache using a cache key
552
+ * @param cacheKey - Cache key to lookup in the global ParsedSchemaCache
553
+ * @param context - Optional context data
554
+ * @param data - Optional initial data
555
+ * @throws {Error} If reload fails or schema not found in cache
556
+ */
557
+ async reloadSchemaFromCache(
558
+ cacheKey: string,
559
+ context?: string | object | null,
560
+ data?: string | object | null
561
+ ): Promise<void> {
562
+ this.throwIfDisposed();
563
+
564
+ try {
565
+ const contextStr = context ? (typeof context === 'string' ? context : JSON.stringify(context)) : null;
566
+ const dataStr = data ? (typeof data === 'string' ? data : JSON.stringify(data)) : null;
567
+
568
+ await JsonEvalRs.reloadSchemaFromCache(this.handle, cacheKey, contextStr, dataStr);
569
+ } catch (error) {
570
+ const errorMessage = error instanceof Error ? error.message : String(error);
571
+ throw new Error(`Failed to reload schema from cache: ${errorMessage}`);
572
+ }
573
+ }
574
+
575
+ /**
576
+ * Get cache statistics
577
+ * @returns Promise resolving to cache statistics
578
+ * @throws {Error} If operation fails
579
+ */
580
+ async cacheStats(): Promise<CacheStats> {
581
+ this.throwIfDisposed();
582
+ const resultStr = await JsonEvalRs.cacheStats(this.handle);
583
+ return JSON.parse(resultStr);
584
+ }
585
+
586
+ /**
587
+ * Clear the evaluation cache
588
+ * @returns Promise that resolves when cache is cleared
589
+ * @throws {Error} If operation fails
590
+ */
591
+ async clearCache(): Promise<void> {
592
+ this.throwIfDisposed();
593
+ await JsonEvalRs.clearCache(this.handle);
594
+ }
595
+
596
+ /**
597
+ * Get the number of cached entries
598
+ * @returns Promise resolving to number of cached entries
599
+ * @throws {Error} If operation fails
600
+ */
601
+ async cacheLen(): Promise<number> {
602
+ this.throwIfDisposed();
603
+ return await JsonEvalRs.cacheLen(this.handle);
604
+ }
605
+
606
+ /**
607
+ * Enable evaluation caching
608
+ * Useful for reusing JSONEval instances with different data
609
+ * @returns Promise that resolves when cache is enabled
610
+ * @throws {Error} If operation fails
611
+ */
612
+ async enableCache(): Promise<void> {
613
+ this.throwIfDisposed();
614
+ await JsonEvalRs.enableCache(this.handle);
615
+ }
616
+
617
+ /**
618
+ * Disable evaluation caching
619
+ * Useful for web API usage where each request creates a new JSONEval instance
620
+ * Improves performance by skipping cache operations that have no benefit for single-use instances
621
+ * @returns Promise that resolves when cache is disabled
622
+ * @throws {Error} If operation fails
623
+ */
624
+ async disableCache(): Promise<void> {
625
+ this.throwIfDisposed();
626
+ await JsonEvalRs.disableCache(this.handle);
627
+ }
628
+
629
+ /**
630
+ * Check if evaluation caching is enabled
631
+ * @returns Boolean indicating if caching is enabled
632
+ * @throws {Error} If operation fails
633
+ */
634
+ isCacheEnabled(): boolean {
635
+ this.throwIfDisposed();
636
+ return JsonEvalRs.isCacheEnabled(this.handle);
637
+ }
638
+
639
+ /**
640
+ * Resolve layout with optional evaluation
641
+ * @param evaluate - If true, runs evaluation before resolving layout (default: false)
642
+ * @returns Promise that resolves when layout resolution is complete
643
+ * @throws {Error} If operation fails
644
+ */
645
+ async resolveLayout(evaluate: boolean = false): Promise<void> {
646
+ this.throwIfDisposed();
647
+ await JsonEvalRs.resolveLayout(this.handle, evaluate);
648
+ }
649
+
650
+ /**
651
+ * Compile and run JSON logic from a JSON logic string
652
+ * @param logicStr - JSON logic expression as a string or object
653
+ * @param data - Optional JSON data string or object (null to use existing data)
654
+ * @param context - Optional context data string or object (null to use existing context)
655
+ * @returns Promise resolving to the result of the evaluation
656
+ * @throws {Error} If compilation or evaluation fails
657
+ */
658
+ async compileAndRunLogic(logicStr: string | object, data?: string | object, context?: string | object): Promise<any> {
659
+ this.throwIfDisposed();
660
+
661
+ const logic = this.toJsonString(logicStr);
662
+ const dataStr = data ? this.toJsonString(data) : null;
663
+ const contextStr = context ? this.toJsonString(context) : null;
664
+
665
+ const resultStr = await JsonEvalRs.compileAndRunLogic(this.handle, logic, dataStr, contextStr);
666
+ return JSON.parse(resultStr);
667
+ }
668
+
669
+ /**
670
+ * Compile JSON logic and return a global ID
671
+ * @param logicStr - JSON logic expression as a string or object
672
+ * @returns Promise resolving to the compiled logic ID
673
+ * @throws {Error} If compilation fails
674
+ */
675
+ async compileLogic(logicStr: string | object): Promise<number> {
676
+ this.throwIfDisposed();
677
+
678
+ const logic = this.toJsonString(logicStr);
679
+ return await JsonEvalRs.compileLogic(this.handle, logic);
680
+ }
681
+
682
+ /**
683
+ * Run pre-compiled logic by ID
684
+ * @param logicId - Compiled logic ID from compileLogic
685
+ * @param data - Optional JSON data string or object (null to use existing data)
686
+ * @param context - Optional context data string or object (null to use existing context)
687
+ * @returns Promise resolving to the result of the evaluation
688
+ * @throws {Error} If execution fails
689
+ */
690
+ async runLogic(logicId: number, data?: string | object, context?: string | object): Promise<any> {
691
+ this.throwIfDisposed();
692
+
693
+ const dataStr = data ? this.toJsonString(data) : null;
694
+ const contextStr = context ? this.toJsonString(context) : null;
695
+
696
+ const resultStr = await JsonEvalRs.runLogic(this.handle, logicId, dataStr, contextStr);
697
+ return JSON.parse(resultStr);
698
+ }
699
+
700
+ /**
701
+ * Validate data against schema rules with optional path filtering
702
+ * @param options - Validation options with optional path filtering
703
+ * @returns Promise resolving to ValidationResult
704
+ * @throws {Error} If validation operation fails
705
+ */
706
+ async validatePaths(options: ValidatePathsOptions): Promise<ValidationResult> {
707
+ this.throwIfDisposed();
708
+
709
+ const dataStr = this.toJsonString(options.data);
710
+ const contextStr = options.context ? this.toJsonString(options.context) : null;
711
+ const paths = options.paths || null;
712
+
713
+ const resultStr = await JsonEvalRs.validatePaths(this.handle, dataStr, contextStr, paths);
714
+ return JSON.parse(resultStr);
715
+ }
716
+
717
+ // ============================================================================
718
+ // Subform Methods
719
+ // ============================================================================
720
+
721
+ /**
722
+ * Evaluate a subform with data
723
+ * @param options - Evaluation options including subform path and data
724
+ * @returns Promise that resolves when evaluation is complete
725
+ * @throws {Error} If evaluation fails
726
+ */
727
+ async evaluateSubform(options: EvaluateSubformOptions): Promise<void> {
728
+ this.throwIfDisposed();
729
+
730
+ const dataStr = this.toJsonString(options.data);
731
+ const contextStr = options.context ? this.toJsonString(options.context) : null;
732
+
733
+ return JsonEvalRs.evaluateSubform(this.handle, options.subformPath, dataStr, contextStr);
734
+ }
735
+
736
+ /**
737
+ * Validate subform data against its schema rules
738
+ * @param options - Validation options including subform path and data
739
+ * @returns Promise resolving to ValidationResult
740
+ * @throws {Error} If validation fails
741
+ */
742
+ async validateSubform(options: ValidateSubformOptions): Promise<ValidationResult> {
743
+ this.throwIfDisposed();
744
+
745
+ const dataStr = this.toJsonString(options.data);
746
+ const contextStr = options.context ? this.toJsonString(options.context) : null;
747
+
748
+ const resultStr = await JsonEvalRs.validateSubform(this.handle, options.subformPath, dataStr, contextStr);
749
+ return JSON.parse(resultStr);
750
+ }
751
+
752
+ /**
753
+ * Evaluate dependents in a subform when fields change
754
+ * @param options - Options including subform path, changed paths array, and optional data
755
+ * @returns Promise resolving to dependent evaluation results
756
+ * @throws {Error} If evaluation fails
757
+ */
758
+ async evaluateDependentsSubform(options: EvaluateDependentsSubformOptions): Promise<DependentChange[]> {
759
+ this.throwIfDisposed();
760
+
761
+ const dataStr = options.data ? this.toJsonString(options.data) : null;
762
+ const contextStr = options.context ? this.toJsonString(options.context) : null;
763
+
764
+ // For now, pass the first path since native bridge expects single path (wraps internally)
765
+ const changedPath = options.changedPaths[0] || '';
766
+
767
+ const resultStr = await JsonEvalRs.evaluateDependentsSubform(
768
+ this.handle,
769
+ options.subformPath,
770
+ changedPath,
771
+ dataStr,
772
+ contextStr
773
+ );
774
+ return JSON.parse(resultStr);
775
+ }
776
+
777
+ /**
778
+ * Resolve layout for subform
779
+ * @param options - Options including subform path and evaluate flag
780
+ * @returns Promise that resolves when layout is resolved
781
+ * @throws {Error} If layout resolution fails
782
+ */
783
+ async resolveLayoutSubform(options: ResolveLayoutSubformOptions): Promise<void> {
784
+ this.throwIfDisposed();
785
+
786
+ return JsonEvalRs.resolveLayoutSubform(this.handle, options.subformPath, options.evaluate || false);
787
+ }
788
+
789
+ /**
790
+ * Get evaluated schema from subform
791
+ * @param options - Options including subform path and resolveLayout flag
792
+ * @returns Promise resolving to evaluated schema
793
+ * @throws {Error} If operation fails
794
+ */
795
+ async getEvaluatedSchemaSubform(options: GetEvaluatedSchemaSubformOptions): Promise<any> {
796
+ this.throwIfDisposed();
797
+
798
+ const resultStr = await JsonEvalRs.getEvaluatedSchemaSubform(
799
+ this.handle,
800
+ options.subformPath,
801
+ options.resolveLayout || false
802
+ );
803
+ return JSON.parse(resultStr);
804
+ }
805
+
806
+ /**
807
+ * Get schema value from subform (all .value fields)
808
+ * @param options - Options including subform path
809
+ * @returns Promise resolving to schema values
810
+ * @throws {Error} If operation fails
811
+ */
812
+ async getSchemaValueSubform(options: GetSchemaValueSubformOptions): Promise<any> {
813
+ this.throwIfDisposed();
814
+
815
+ const resultStr = await JsonEvalRs.getSchemaValueSubform(this.handle, options.subformPath);
816
+ return JSON.parse(resultStr);
817
+ }
818
+
819
+ /**
820
+ * Get evaluated schema without $params from subform
821
+ * @param options - Options including subform path and resolveLayout flag
822
+ * @returns Promise resolving to evaluated schema without $params
823
+ * @throws {Error} If operation fails
824
+ */
825
+ async getEvaluatedSchemaWithoutParamsSubform(options: GetEvaluatedSchemaSubformOptions): Promise<any> {
826
+ this.throwIfDisposed();
827
+
828
+ const resultStr = await JsonEvalRs.getEvaluatedSchemaWithoutParamsSubform(
829
+ this.handle,
830
+ options.subformPath,
831
+ options.resolveLayout || false
832
+ );
833
+ return JSON.parse(resultStr);
834
+ }
835
+
836
+ /**
837
+ * Get evaluated schema by specific path from subform
838
+ * @param options - Options including subform path, schema path, and skipLayout flag
839
+ * @returns Promise resolving to value at path or null if not found
840
+ * @throws {Error} If operation fails
841
+ */
842
+ async getEvaluatedSchemaByPathSubform(options: GetEvaluatedSchemaByPathSubformOptions): Promise<any | null> {
843
+ this.throwIfDisposed();
844
+
845
+ const resultStr = await JsonEvalRs.getEvaluatedSchemaByPathSubform(
846
+ this.handle,
847
+ options.subformPath,
848
+ options.schemaPath,
849
+ options.skipLayout || false
850
+ );
851
+ return resultStr ? JSON.parse(resultStr) : null;
852
+ }
853
+
854
+ /**
855
+ * Get evaluated schema by multiple paths from subform
856
+ * Returns a merged object containing all requested paths (skips paths that are not found)
857
+ * @param options - Options including subform path, array of schema paths, and skipLayout flag
858
+ * @returns Promise resolving to merged object containing all found paths
859
+ * @throws {Error} If operation fails
860
+ */
861
+ async getEvaluatedSchemaByPathsSubform(options: GetEvaluatedSchemaByPathsSubformOptions): Promise<any> {
862
+ this.throwIfDisposed();
863
+
864
+ const pathsJson = JSON.stringify(options.schemaPaths);
865
+ const resultStr = await JsonEvalRs.getEvaluatedSchemaByPathsSubform(
866
+ this.handle,
867
+ options.subformPath,
868
+ pathsJson,
869
+ options.skipLayout || false
870
+ );
871
+ return JSON.parse(resultStr);
872
+ }
873
+
874
+ /**
875
+ * Get list of available subform paths
876
+ * @returns Promise resolving to array of subform paths
877
+ * @throws {Error} If operation fails
878
+ */
879
+ async getSubformPaths(): Promise<string[]> {
880
+ this.throwIfDisposed();
881
+
882
+ const resultStr = await JsonEvalRs.getSubformPaths(this.handle);
883
+ return JSON.parse(resultStr);
884
+ }
885
+
886
+ /**
887
+ * Get schema value by specific path from subform
888
+ * @param options - Options including subform path and schema path
889
+ * @returns Promise resolving to value at path or null if not found
890
+ * @throws {Error} If operation fails
891
+ */
892
+ async getSchemaByPathSubform(options: GetSchemaByPathSubformOptions): Promise<any | null> {
893
+ this.throwIfDisposed();
894
+
895
+ const resultStr = await JsonEvalRs.getSchemaByPathSubform(
896
+ this.handle,
897
+ options.subformPath,
898
+ options.schemaPath
899
+ );
900
+ return resultStr ? JSON.parse(resultStr) : null;
901
+ }
902
+
903
+ /**
904
+ * Get schema values by multiple paths from subform
905
+ * Returns a merged object containing all requested paths (skips paths that are not found)
906
+ * @param options - Options including subform path and array of schema paths
907
+ * @returns Promise resolving to merged object containing all found paths
908
+ * @throws {Error} If operation fails
909
+ */
910
+ async getSchemaByPathsSubform(options: GetSchemaByPathsSubformOptions): Promise<any> {
911
+ this.throwIfDisposed();
912
+
913
+ const pathsJson = JSON.stringify(options.schemaPaths);
914
+ const resultStr = await JsonEvalRs.getSchemaByPathsSubform(
915
+ this.handle,
916
+ options.subformPath,
917
+ pathsJson
918
+ );
919
+ return JSON.parse(resultStr);
920
+ }
921
+
922
+ /**
923
+ * Check if a subform exists at the given path
924
+ * @param subformPath - Path to check
925
+ * @returns Promise resolving to true if subform exists, false otherwise
926
+ * @throws {Error} If operation fails
927
+ */
928
+ async hasSubform(subformPath: string): Promise<boolean> {
929
+ this.throwIfDisposed();
930
+
931
+ return JsonEvalRs.hasSubform(this.handle, subformPath);
932
+ }
933
+
934
+ /**
935
+ * Dispose of the native resources
936
+ * Must be called when done using the instance
937
+ * @returns Promise that resolves when disposal is complete
938
+ */
939
+ async dispose(): Promise<void> {
940
+ if (this.disposed) return;
941
+
942
+ await JsonEvalRs.dispose(this.handle);
943
+ this.disposed = true;
944
+ }
945
+
946
+ /**
947
+ * Get the library version
948
+ * @returns Promise resolving to version string
949
+ */
950
+ static async version(): Promise<string> {
951
+ return JsonEvalRs.version();
952
+ }
953
+ }
954
+
955
+ /**
956
+ * Hook for using JSONEval in React components with automatic cleanup
957
+ * @param options - Configuration options
958
+ * @returns JSONEval instance or null if not yet initialized
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * import { useJSONEval } from '@json-eval-rs/react-native';
963
+ *
964
+ * function MyComponent() {
965
+ * const eval = useJSONEval({ schema: mySchema });
966
+ *
967
+ * const handleValidate = async () => {
968
+ * if (!eval) return;
969
+ * const result = await eval.validate({ data: myData });
970
+ * console.log(result);
971
+ * };
972
+ *
973
+ * return <Button onPress={handleValidate} title="Validate" />;
974
+ * }
975
+ * ```
976
+ */
977
+ export function useJSONEval(options: JSONEvalOptions): JSONEval | null {
978
+ const [evalInstance, setEvalInstance] = React.useState<JSONEval | null>(null);
979
+
980
+ React.useEffect(() => {
981
+ const instance = new JSONEval(options);
982
+ setEvalInstance(instance);
983
+
984
+ return () => {
985
+ instance.dispose().catch(console.error);
986
+ };
987
+ // eslint-disable-next-line react-hooks/exhaustive-deps
988
+ }, []);
989
+
990
+ return evalInstance;
991
+ }
992
+
993
+ // Default export
994
+ export default JSONEval;
995
+
996
+ // For backwards compatibility
997
+ export const multiply = (a: number, b: number): Promise<number> => {
998
+ return JsonEvalRs.multiply(a, b);
999
+ };