@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/README.md ADDED
@@ -0,0 +1,455 @@
1
+ # @json-eval-rs/react-native
2
+
3
+ High-performance JSON Logic evaluator with schema validation for React Native.
4
+
5
+ Built with Rust for maximum performance, with native Android (Kotlin + JNI) and iOS (Objective-C++) bindings. All operations run asynchronously on background threads to keep your UI responsive.
6
+
7
+ ## Features
8
+
9
+ - 🚀 **Native Performance** - Built with Rust for iOS and Android
10
+ - ✅ **Schema Validation** - Validate data against JSON schema rules
11
+ - 🔄 **Dependency Tracking** - Auto-update dependent fields
12
+ - 🎯 **Type Safe** - Full TypeScript support
13
+ - ⚛️ **React Hooks** - Built-in `useJSONEval` hook
14
+ - 📱 **Cross-Platform** - Works on iOS and Android
15
+ - 🔥 **Fast** - Native performance, not JavaScript
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ yarn install @json-eval-rs/react-native
21
+ ```
22
+
23
+ Or with Yarn:
24
+
25
+ ```bash
26
+ yarn add @json-eval-rs/react-native
27
+ ```
28
+
29
+ ### iOS
30
+
31
+ ```bash
32
+ cd ios && pod install
33
+ ```
34
+
35
+ ### Android
36
+
37
+ No additional steps required. The library uses autolinking.
38
+
39
+ ## Quick Start
40
+
41
+ ### Basic Usage
42
+
43
+ ```typescript
44
+ import { JSONEval } from '@json-eval-rs/react-native';
45
+
46
+ const schema = {
47
+ type: 'object',
48
+ properties: {
49
+ user: {
50
+ type: 'object',
51
+ properties: {
52
+ name: {
53
+ type: 'string',
54
+ rules: {
55
+ required: { value: true, message: 'Name is required' },
56
+ minLength: { value: 3, message: 'Min 3 characters' }
57
+ }
58
+ },
59
+ age: {
60
+ type: 'number',
61
+ rules: {
62
+ minValue: { value: 18, message: 'Must be 18+' }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ };
69
+
70
+ // Create evaluator
71
+ const eval = new JSONEval({ schema });
72
+
73
+ // Evaluate
74
+ const data = { user: { name: 'John', age: 25 } };
75
+ const result = await eval.evaluate({ data });
76
+ console.log('Evaluated:', result);
77
+
78
+ // Validate
79
+ const validation = await eval.validate({ data });
80
+ if (validation.hasError) {
81
+ validation.errors.forEach(error => {
82
+ console.error(`${error.path}: ${error.message}`);
83
+ });
84
+ }
85
+
86
+ // Clean up
87
+ await eval.dispose();
88
+ ```
89
+
90
+ ### Using React Hook
91
+
92
+ ```typescript
93
+ import React, { useState } from 'react';
94
+ import { View, TextInput, Button, Text } from 'react-native';
95
+ import { useJSONEval } from '@json-eval-rs/react-native';
96
+
97
+ const schema = {
98
+ type: 'object',
99
+ properties: {
100
+ user: {
101
+ type: 'object',
102
+ properties: {
103
+ name: {
104
+ type: 'string',
105
+ rules: {
106
+ required: { value: true, message: 'Name is required' },
107
+ minLength: { value: 3, message: 'Min 3 characters' }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ };
114
+
115
+ function MyForm() {
116
+ const eval = useJSONEval({ schema });
117
+ const [name, setName] = useState('');
118
+ const [errors, setErrors] = useState<string[]>([]);
119
+
120
+ const handleValidate = async () => {
121
+ if (!eval) return;
122
+
123
+ const data = { user: { name } };
124
+ const validation = await eval.validate({ data });
125
+
126
+ if (validation.hasError) {
127
+ setErrors(validation.errors.map(e => e.message));
128
+ } else {
129
+ setErrors([]);
130
+ console.log('Valid!');
131
+ }
132
+ };
133
+
134
+ return (
135
+ <View>
136
+ <TextInput
137
+ value={name}
138
+ onChangeText={setName}
139
+ placeholder="Enter name"
140
+ />
141
+ <Button title="Validate" onPress={handleValidate} />
142
+
143
+ {errors.map((error, i) => (
144
+ <Text key={i} style={{ color: 'red' }}>{error}</Text>
145
+ ))}
146
+ </View>
147
+ );
148
+ }
149
+ ```
150
+
151
+ ### Advanced: Dependent Fields
152
+
153
+ ```typescript
154
+ import React, { useState, useEffect } from 'react';
155
+ import { View, TextInput, Text } from 'react-native';
156
+ import { useJSONEval } from '@json-eval-rs/react-native';
157
+
158
+ const schema = {
159
+ type: 'object',
160
+ properties: {
161
+ quantity: { type: 'number' },
162
+ price: { type: 'number' },
163
+ total: {
164
+ type: 'number',
165
+ $evaluation: {
166
+ '*': [{ var: 'quantity' }, { var: 'price' }]
167
+ }
168
+ }
169
+ }
170
+ };
171
+
172
+ function Calculator() {
173
+ const eval = useJSONEval({ schema });
174
+ const [quantity, setQuantity] = useState(1);
175
+ const [price, setPrice] = useState(10);
176
+ const [total, setTotal] = useState(0);
177
+
178
+ useEffect(() => {
179
+ if (!eval) return;
180
+
181
+ const updateTotal = async () => {
182
+ const data = { quantity, price };
183
+ const result = await eval.evaluateDependents({
184
+ changedPaths: ['quantity'], // Array of changed field paths
185
+ data,
186
+ reEvaluate: false // Optional: re-evaluate entire schema after dependents
187
+ });
188
+
189
+ setTotal(result.total);
190
+ };
191
+
192
+ updateTotal();
193
+ }, [eval, quantity, price]);
194
+
195
+ return (
196
+ <View>
197
+ <TextInput
198
+ value={String(quantity)}
199
+ onChangeText={(val) => setQuantity(Number(val))}
200
+ keyboardType="numeric"
201
+ />
202
+ <TextInput
203
+ value={String(price)}
204
+ onChangeText={(val) => setPrice(Number(val))}
205
+ keyboardType="numeric"
206
+ />
207
+ <Text>Total: {total}</Text>
208
+ </View>
209
+ );
210
+ }
211
+ ```
212
+
213
+ ## API Reference
214
+
215
+ ### JSONEval Class
216
+
217
+ #### Constructor
218
+
219
+ ```typescript
220
+ constructor(options: {
221
+ schema: string | object;
222
+ context?: string | object;
223
+ data?: string | object;
224
+ })
225
+ ```
226
+
227
+ Creates a new evaluator instance.
228
+
229
+ #### Methods
230
+
231
+ ##### evaluate(options)
232
+
233
+ Evaluates the schema with provided data.
234
+
235
+ ```typescript
236
+ async evaluate(options: {
237
+ data: string | object;
238
+ context?: string | object;
239
+ }): Promise<any>
240
+ ```
241
+
242
+ ##### validate(options)
243
+
244
+ Validates data against schema rules.
245
+
246
+ ```typescript
247
+ async validate(options: {
248
+ data: string | object;
249
+ context?: string | object;
250
+ }): Promise<ValidationResult>
251
+ ```
252
+
253
+ Returns:
254
+ ```typescript
255
+ interface ValidationResult {
256
+ hasError: boolean;
257
+ errors: ValidationError[];
258
+ }
259
+
260
+ interface ValidationError {
261
+ path: string;
262
+ ruleType: string;
263
+ message: string;
264
+ }
265
+ ```
266
+
267
+ ##### evaluateDependents(options)
268
+
269
+ Re-evaluates fields that depend on changed paths (processes transitively).
270
+
271
+ ```typescript
272
+ async evaluateDependents(options: {
273
+ changedPaths: string[]; // Array of field paths that changed
274
+ data?: string | object; // Optional updated data
275
+ context?: string | object; // Optional context
276
+ reEvaluate?: boolean; // If true, performs full evaluation after dependents
277
+ }): Promise<any[]>
278
+ ```
279
+
280
+ **Parameters:**
281
+ - `changedPaths`: Array of field paths that changed (e.g., `['field1', 'nested.field2']`)
282
+ - `data`: Optional JSON data (string or object). If provided, replaces current data
283
+ - `context`: Optional context data
284
+ - `reEvaluate`: If `true`, performs full schema evaluation after processing dependents (default: `false`)
285
+
286
+ **Returns:** Array of dependent field change objects with `$ref`, `value`, `$field`, `$parentField`, and `transitive` properties.
287
+
288
+ **Example:**
289
+ ```typescript
290
+ // Update multiple fields and get their dependents
291
+ const result = await eval.evaluateDependents({
292
+ changedPaths: ['illustration.insured.ins_dob', 'illustration.product_code'],
293
+ data: updatedData,
294
+ reEvaluate: true // Re-run full evaluation after dependents
295
+ });
296
+
297
+ // Process the changes
298
+ result.forEach(change => {
299
+ console.log(`Field ${change.$ref} changed:`, change.value);
300
+ });
301
+ ```
302
+
303
+ ##### dispose()
304
+
305
+ Frees native resources. Must be called when done.
306
+
307
+ ```typescript
308
+ async dispose(): Promise<void>
309
+ ```
310
+
311
+ ##### static version()
312
+
313
+ Gets the library version.
314
+
315
+ ```typescript
316
+ static async version(): Promise<string>
317
+ ```
318
+
319
+ ### useJSONEval Hook
320
+
321
+ React hook for automatic lifecycle management.
322
+
323
+ ```typescript
324
+ function useJSONEval(options: JSONEvalOptions): JSONEval | null
325
+ ```
326
+
327
+ Returns `null` until initialized, then returns the `JSONEval` instance.
328
+ Automatically disposes on unmount.
329
+
330
+ ## Validation Rules
331
+
332
+ Supported validation rules:
333
+
334
+ - **required** - Field must have a value
335
+ - **minLength** / **maxLength** - String/array length validation
336
+ - **minValue** / **maxValue** - Numeric range validation
337
+ - **pattern** - Regex pattern matching
338
+
339
+ ## Platform Support
340
+
341
+ - **iOS**: 11.0+
342
+ - **Android**: API 21+ (Android 5.0)
343
+ - **React Native**: 0.64+
344
+
345
+ ## Performance
346
+
347
+ Typical performance on modern devices:
348
+ - Schema parsing: < 5ms
349
+ - Evaluation: < 10ms for complex schemas
350
+ - Validation: < 5ms
351
+
352
+ Native performance beats JavaScript-only solutions by 10-50x.
353
+
354
+ ### Sequential Processing
355
+
356
+ This library uses **sequential processing** by default, which is optimal for mobile devices. The Rust core supports an optional `parallel` feature using Rayon, but:
357
+ - Mobile devices have limited cores and power constraints
358
+ - Sequential processing is faster for typical mobile use cases (small to medium datasets)
359
+ - Parallel overhead exceeds benefits for arrays < 1000 items
360
+ - Battery life is better with sequential processing
361
+
362
+ The default configuration is optimized for mobile performance and battery efficiency.
363
+
364
+ ## Error Handling
365
+
366
+ All async methods can throw errors:
367
+
368
+ ```typescript
369
+ try {
370
+ const result = await eval.evaluate({ data });
371
+ } catch (error) {
372
+ console.error('Evaluation error:', error.message);
373
+ }
374
+ ```
375
+
376
+ ## Memory Management
377
+
378
+ Always dispose of instances when done:
379
+
380
+ ```typescript
381
+ const eval = new JSONEval({ schema });
382
+ try {
383
+ // Use eval
384
+ } finally {
385
+ await eval.dispose(); // Important!
386
+ }
387
+ ```
388
+
389
+ Or use the hook for automatic management:
390
+
391
+ ```typescript
392
+ function MyComponent() {
393
+ const eval = useJSONEval({ schema }); // Auto-disposed on unmount
394
+ // Use eval
395
+ }
396
+ ```
397
+
398
+ ## TypeScript
399
+
400
+ Full TypeScript support included. All types are exported:
401
+
402
+ ```typescript
403
+ import type {
404
+ JSONEval,
405
+ ValidationError,
406
+ ValidationResult,
407
+ JSONEvalOptions,
408
+ EvaluateOptions,
409
+ EvaluateDependentsOptions
410
+ } from '@json-eval-rs/react-native';
411
+ ```
412
+
413
+ ## Troubleshooting
414
+
415
+ ### iOS Build Errors
416
+
417
+ If you encounter build errors on iOS:
418
+
419
+ ```bash
420
+ cd ios
421
+ rm -rf Pods Podfile.lock
422
+ pod install --repo-update
423
+ ```
424
+
425
+ ### Android Build Errors
426
+
427
+ If you encounter build errors on Android:
428
+
429
+ ```bash
430
+ cd android
431
+ ./gradlew clean
432
+ cd ..
433
+ ```
434
+
435
+ Then rebuild your app.
436
+
437
+ ### "Module not found" Error
438
+
439
+ Make sure you've:
440
+ 1. Installed the package
441
+ 2. Run `pod install` on iOS
442
+ 3. Rebuilt the app completely
443
+
444
+ ## Contributing
445
+
446
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
447
+
448
+ ## License
449
+
450
+ MIT
451
+
452
+ ## Support
453
+
454
+ - GitHub Issues: https://github.com/byrizki/json-eval-rs/issues
455
+ - Documentation: https://github.com/byrizki/json-eval-rs
@@ -0,0 +1,78 @@
1
+ cmake_minimum_required(VERSION 3.9.0)
2
+
3
+ project(json-eval-rn)
4
+
5
+ set(CMAKE_VERBOSE_MAKEFILE ON)
6
+ set(CMAKE_CXX_STANDARD 17)
7
+
8
+ # Find React Native
9
+ find_package(ReactAndroid REQUIRED CONFIG)
10
+
11
+ # Pre-built Rust library (bundled with npm package)
12
+ # The library is located in src/main/jniLibs/[abi]/libjson_eval_rs.so
13
+ set(RUST_PREBUILT_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
14
+ set(RUST_LIB_PATH ${RUST_PREBUILT_DIR}/libjson_eval_rs.so)
15
+
16
+ message(STATUS "Looking for pre-built Rust library at: ${RUST_LIB_PATH}")
17
+
18
+ # Check if pre-built library exists
19
+ if(NOT EXISTS ${RUST_LIB_PATH})
20
+ message(FATAL_ERROR
21
+ "Pre-built Rust library not found at: ${RUST_LIB_PATH}\n"
22
+ "\nThis package requires pre-built native libraries.\n"
23
+ "If you're developing this package, build the libraries first:\n"
24
+ " cd ${CMAKE_SOURCE_DIR}/../..\n"
25
+ " ./build-android.sh all\n"
26
+ "\nIf you're a user and seeing this error, the package may be corrupted.\n"
27
+ "Try reinstalling: npm install @json-eval-rs/react-native\n"
28
+ "\nCurrent ANDROID_ABI: ${ANDROID_ABI}")
29
+ endif()
30
+
31
+ message(STATUS "Using pre-built Rust library: ${RUST_LIB_PATH}")
32
+
33
+ # Add library search path for the Rust library
34
+ link_directories(${RUST_PREBUILT_DIR})
35
+
36
+ # C++ Bridge files
37
+ add_library(
38
+ json_eval_rn
39
+ SHARED
40
+ src/main/cpp/json-eval-rn.cpp
41
+ ../cpp/json-eval-bridge.cpp
42
+ )
43
+
44
+ # Include directories
45
+ target_include_directories(
46
+ json_eval_rn
47
+ PRIVATE
48
+ ../cpp
49
+ src/main/cpp
50
+ )
51
+
52
+ # Link libraries
53
+ # Note: React Native provides JSI and other libraries at runtime
54
+ # We link against them for compilation, but they should not be packaged
55
+ target_link_libraries(
56
+ json_eval_rn
57
+ json_eval_rs
58
+ android
59
+ log
60
+ ReactAndroid::jsi
61
+ ReactAndroid::reactnativejni
62
+ )
63
+
64
+ # Modern React Native (0.71+) uses prefab which properly handles shared libraries
65
+ # The libraries are marked as shared dependencies, not to be packaged in our module
66
+ # If you still see duplicate library warnings, ensure your React Native version is up to date
67
+
68
+ # Optimize binary size
69
+ set_target_properties(json_eval_rn PROPERTIES
70
+ CXX_VISIBILITY_PRESET hidden
71
+ C_VISIBILITY_PRESET hidden
72
+ VISIBILITY_INLINES_HIDDEN ON
73
+ )
74
+
75
+ # Strip debug symbols in release builds
76
+ if(CMAKE_BUILD_TYPE STREQUAL "Release")
77
+ target_link_options(json_eval_rn PRIVATE -Wl,--strip-all)
78
+ endif()
@@ -0,0 +1,90 @@
1
+ buildscript {
2
+ ext.kotlin_version = '1.8.0'
3
+ repositories {
4
+ google()
5
+ mavenCentral()
6
+ }
7
+ dependencies {
8
+ classpath 'com.android.tools.build:gradle:7.4.0'
9
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
10
+ }
11
+ }
12
+
13
+ apply plugin: 'com.android.library'
14
+ apply plugin: 'kotlin-android'
15
+
16
+ def reactNative = rootProject.allprojects
17
+ .find { it.name == 'ReactAndroid' }
18
+
19
+ def safeExtGet(prop, fallback) {
20
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
21
+ }
22
+
23
+ android {
24
+ compileSdkVersion safeExtGet('compileSdkVersion', 33)
25
+
26
+ namespace "com.jsonevalrs"
27
+
28
+ defaultConfig {
29
+ minSdkVersion safeExtGet('minSdkVersion', 21)
30
+ targetSdkVersion safeExtGet('targetSdkVersion', 33)
31
+
32
+ externalNativeBuild {
33
+ cmake {
34
+ cppFlags "-std=c++17 -fexceptions -frtti"
35
+ arguments "-DANDROID_STL=c++_shared"
36
+ abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
37
+ }
38
+ }
39
+ }
40
+
41
+ buildFeatures {
42
+ prefab true
43
+ }
44
+
45
+ externalNativeBuild {
46
+ cmake {
47
+ path "CMakeLists.txt"
48
+ }
49
+ }
50
+
51
+ buildTypes {
52
+ release {
53
+ minifyEnabled false
54
+ }
55
+ }
56
+
57
+ sourceSets {
58
+ main {
59
+ java.srcDirs = ['src/main/java']
60
+ jniLibs.srcDirs = ['src/main/jniLibs']
61
+ }
62
+ }
63
+
64
+ lintOptions {
65
+ abortOnError false
66
+ }
67
+
68
+ packagingOptions {
69
+ // Exclude React Native libraries - they're provided by the app
70
+ // This prevents duplicate .so files and eliminates the need for pickFirst in the app
71
+ exclude 'lib/**/libjsi.so'
72
+ exclude 'lib/**/libc++_shared.so'
73
+ exclude 'lib/**/libreactnativejni.so'
74
+ exclude 'lib/**/libfbjni.so'
75
+
76
+ // Keep only our module's native library
77
+ // pickFirst is used as fallback for any remaining conflicts
78
+ pickFirst 'META-INF/**'
79
+ }
80
+ }
81
+
82
+ repositories {
83
+ google()
84
+ mavenCentral()
85
+ }
86
+
87
+ dependencies {
88
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
89
+ implementation 'com.facebook.react:react-native:+'
90
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>