@coherentglobal/spark-execute-sdk 0.8.3 → 0.8.4

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 CHANGED
@@ -1,10 +1,51 @@
1
1
  # Spark Execute SDK
2
2
 
3
- # About the Project
3
+ Execute [Spark](https://coherent.global/spark/) models both online and offline with a unified API. This SDK seamlessly switches between local WebAssembly execution and remote API calls, giving you the flexibility to run models wherever makes sense for your application.
4
4
 
5
- A Software Development Kit (SDK) that can run either [Spark](https://coherent.global/spark/) model web-assembly or Calling an HTTPS endpoint.
5
+ ## Features
6
6
 
7
- # How to's
7
+ - Run models locally using WebAssembly (offline)
8
+ - Call Spark API endpoints (online)
9
+ - Automatic fallback from offline to online on errors
10
+ - Cancel long-running executions mid-flight
11
+ - Automatic memory management with configurable limits
12
+ - Full support for cross-service calls (XCall)
13
+ - Works in Node.js and browser environments
14
+
15
+ ## Quick Start
16
+
17
+ Install the package:
18
+
19
+ ```bash
20
+ npm install @coherentglobal/spark-execute-sdk
21
+ ```
22
+
23
+ Basic usage:
24
+
25
+ ```js
26
+ // CommonJS
27
+ const Spark = require('@coherentglobal/spark-execute-sdk');
28
+
29
+ // ES Modules / TypeScript
30
+ import Spark from '@coherentglobal/spark-execute-sdk';
31
+
32
+ const spark = new Spark({
33
+ sparkEndpoint: {
34
+ url: "https://excel.uat.us.coherent.global",
35
+ tenant: "your-tenant",
36
+ authType: "public"
37
+ }
38
+ });
39
+
40
+ const result = await spark.execute({
41
+ request_data: { inputs: { Input: 1 } },
42
+ request_meta: { version_id: "model-uuid" }
43
+ });
44
+
45
+ await spark.destroy();
46
+ ```
47
+
48
+ # How-to Guides
8
49
 
9
50
  ## Installation
10
51
 
@@ -22,159 +63,443 @@ yarn add @coherentglobal/spark-execute-sdk
22
63
 
23
64
  ### Configuration
24
65
 
25
- #### Both Online and Offline Config is enabled
66
+ **Execution Modes:**
67
+ - Include both `sparkEndpoint` and `nodegenModels` for **automatic fallback** (offline → online)
68
+ - Include only `sparkEndpoint` for **online-only** execution
69
+ - Include only `nodegenModels` for **offline-only** execution
26
70
 
27
71
  ```js
28
72
  const config = {
29
- parallel: 1, // parallel (threads) to be used to run the model in parallel
73
+ // Online execution (optional - omit for offline-only)
30
74
  sparkEndpoint: {
31
- url: "https://excel.dev.coherent.global", // Spark Endpoint to be used to run model using Spark SaaS
32
- tenant: "tenant", // Spark tenant name
33
- authType: public | syntheticKey | bearerToken, // public endpoint don't require token / synthetic key
34
- // If syntheticKey
35
- syntheticKey: "apiKey" or generateSyntheticKey(), // value or a func - func call every time making API call, user will do cache
36
- // If bearerToken
37
- bearerToken: generateToken(), //a func - func call every time making API call, user will do cache
75
+ url: "https://excel.uat.us.coherent.global",
76
+ tenant: "tenant",
77
+ authType: "public" | "syntheticKey" | "bearerToken",
78
+
79
+ // Provide value OR function (called per request - you handle caching)
80
+ syntheticKey: "apiKey" || generateSyntheticKey(),
81
+ bearerToken: "token" || generateToken(),
38
82
  },
83
+
84
+ // Offline execution (optional - omit or use [] for online-only)
39
85
  nodegenModels: [
40
86
  {
41
87
  versionId: "uuid",
42
- type: "base64",
43
- binary: Blob | Base64 | BinaryUnit | Func(versionId), // a binary zipped file or a function the will get the zipped file
44
- preventCleanup: true | false, // prevent model from being unload (default: false)
45
- instance: int, // number of model instance (default: 1)
46
- metadata: {
47
- // Spark Model Metadata
88
+ type: "base64", // Always "base64"
89
+ binary: string | Blob | Buffer | ArrayBuffer | Function, // See below
90
+ preventCleanup: false, // Keep in memory even when limit reached
91
+ replica: 1, // Number of concurrent worker instances
92
+ metaData: {
93
+ EngineInformation: {
94
+ ProductName: "folder-name",
95
+ ServiceName: "service-name",
96
+ VersionId: "uuid"
97
+ }
48
98
  }
49
99
  }
50
- ]
51
- }
100
+ ],
101
+
102
+ // Advanced options
103
+ offlineMaxService: 20, // Max models in memory before LRU cleanup
104
+ timeout: 60000 // Request timeout (ms)
105
+ };
52
106
  ```
53
107
 
54
- #### Online Only Configuration
108
+ ### Understanding the `binary` Configuration
109
+
110
+ The `binary` field accepts different formats depending on your environment.
111
+
112
+ | Environment | Accepted Formats |
113
+ |-------------|------------------|
114
+ | **Browser** | Base64 string, `Blob`, `ArrayBuffer`, Function |
115
+ | **Node.js** | Base64 string, `Buffer`, `ArrayBuffer`, Function |
116
+
117
+ > **Important:** The `type` field should always be `"base64"` regardless of actual binary format.
55
118
 
119
+ #### Common Patterns
120
+
121
+ **Static values:**
56
122
  ```js
57
- const config = {
58
- sparkEndpoint: {
59
- url: "https://excel.dev.coherent.global", // Spark Endpoint to be used to run model using Spark SaaS
60
- tenant: "tenant", // Spark tenant name
61
- authType: public | syntheticKey | bearerToken, // public endpoint don't require token / synthetic key
62
- // If syntheticKey
63
- syntheticKey: "apiKey" || generateSyntheticKey(), // value or a func - func call every time making API call, user will do cache
64
- // If bearerToken
65
- bearerToken: "token" || generateToken(), //a func - func call every time making API call, user will do cache
66
- },
67
- nodegenModels: [],
68
- };
69
- ```
123
+ // Base64 string (most common)
124
+ const fs = require('fs');
125
+ binary: fs.readFileSync('./model.zip', 'base64')
126
+
127
+ // Buffer (Node.js)
128
+ binary: fs.readFileSync('./model.zip')
129
+
130
+ // ArrayBuffer (fetch in browser/Node.js)
131
+ const response = await fetch('/models/model.zip');
132
+ binary: await response.arrayBuffer()
70
133
 
71
- #### Offline Only Configuration
134
+ // Blob (browser file upload)
135
+ binary: new Blob([zipData], { type: 'application/zip' })
136
+ ```
72
137
 
138
+ **Dynamic loading (function):**
73
139
  ```js
74
- const config = {
75
- nodegenModels: [
76
- {
77
- versionId: "uuid",
78
- type: "base64",
79
- binary: Blob | Base64 | BinaryUnit | Func(versionId), // a binary zipped file or a function the will get the zipped file
80
- preventCleanup: true | false, // prevent model from being unload (default: false)
81
- replica: int, // number of model instance (default: 1)
82
- metadata: {
83
- // Spark Model Metadata
84
- },
85
- },
86
- ],
87
- };
140
+ // Lazy load from filesystem
141
+ binary: (versionId) => require('fs').readFileSync(`./models/${versionId}.zip`, 'base64')
142
+
143
+ // Fetch from remote storage
144
+ binary: async (versionId) => {
145
+ const response = await fetch(`https://storage.example.com/models/${versionId}.zip`);
146
+ return await response.arrayBuffer();
147
+ }
148
+
149
+ // With caching
150
+ const modelCache = new Map();
151
+ binary: async (versionId) => {
152
+ if (!modelCache.has(versionId)) {
153
+ modelCache.set(versionId, await fetchModelFromS3(versionId));
154
+ }
155
+ return modelCache.get(versionId);
156
+ }
88
157
  ```
89
158
 
90
- ## Methods
159
+ ## API Reference
91
160
 
92
- ### Initialize Spark Execute SDK
161
+ ### Initialization
93
162
 
94
- Instantiate the Model Class.
163
+ Create a Spark instance with your configuration:
95
164
 
96
165
  ```js
97
166
  const spark = new Spark(config);
98
167
  ```
99
168
 
100
- ### Execute
169
+ ### execute(input, version_id?)
101
170
 
102
- ### (async) execute(input)
103
-
104
- Perform the calculation. The sdk will use the request_meta.version_uuid of input argument to locate the model
171
+ Execute a model using the provided input payload.
105
172
 
106
173
  ```js
107
- const response = await spark.execute(input).catch((err) => {
108
- // Do something here
109
- });
174
+ // Basic usage
175
+ const response = await spark.execute(input);
176
+
177
+ // With explicit version ID
178
+ const response = await spark.execute(input, 'model-uuid-here');
110
179
  ```
111
180
 
112
- or
181
+ > **Tip:** The `version_id` parameter is optional if already in `request_meta`. See [Error Handling](#error-handling) below.
182
+
183
+ ### executeWithCancellationToken(input, version_id?)
184
+
185
+ Execute a model with the ability to cancel the operation mid-execution. Useful for long-running calculations that users may want to cancel.
113
186
 
114
187
  ```js
115
- const response = await spark.execute(input, version_id).catch((err) => {
116
- // Do something here
117
- });
188
+ const execution = spark.executeWithCancellationToken(input);
189
+
190
+ // Cancel anytime before completion
191
+ execution.cancellationToken.cancel();
192
+
193
+ execution.response
194
+ .then((result) => {
195
+ console.log('Completed:', result);
196
+ })
197
+ .catch((err) => {
198
+ if (err instanceof Spark.WasmRunnerErrors.ExecuteCancelled) {
199
+ console.log('User cancelled the execution');
200
+ } else {
201
+ console.error('Execution failed:', err);
202
+ }
203
+ });
118
204
  ```
119
205
 
120
- ### executeWithCancellationToken(input)
206
+ > **Important:** Cancellation is best-effort. The underlying WebAssembly execution may continue running in its worker thread, but the result will be discarded and an `ExecuteCancelled` error will be thrown. The worker is cleaned up after completion.
207
+
208
+ ### destroy()
121
209
 
122
- Perform the calculation with cancellationToken. The sdk will use the request_meta.version_uuid of input argument to locate the model
210
+ Clean up all resources, unload models, and terminate worker threads. Call this when you're done with a Spark instance to prevent memory leaks.
123
211
 
124
212
  ```js
125
- const execute = spark.executeWithCancellationToken(input)
213
+ await spark.destroy();
214
+ ```
126
215
 
127
- // Cancel the execution (best effort)
128
- execute.cancellationToken.cancel()
216
+ > **Critical:** Always call `destroy()` when finished, especially in browsers or long-running Node.js apps. Forgetting this can lead to memory leaks as WebAssembly modules and workers won't be garbage collected.
129
217
 
130
- execute.response.then((result) => {
131
- // Do something here
132
- })
133
- .catch((err) => {
134
- // Do something here
135
- // If error is instance of Spark.WasmRunnerErrors.ExecuteCancelled, meaning the request has been cancelled
136
- });
137
- ```
218
+ ### Error Handling
138
219
 
139
- or
220
+ Specific error types accessible via `Spark.WasmRunnerErrors`:
140
221
 
141
222
  ```js
142
- const execute = spark.executeWithCancellationToken(input)
223
+ try {
224
+ const response = await spark.execute(input);
225
+ } catch (err) {
226
+ if (err instanceof Spark.WasmRunnerErrors.MissingModelError) {
227
+ console.error('Model not found:', err.message);
228
+ } else if (err instanceof Spark.WasmRunnerErrors.ExecuteCancelled) {
229
+ console.error('Execution was cancelled');
230
+ } else if (err instanceof Spark.WasmRunnerErrors.UnauthorizedError) {
231
+ console.error('Authentication failed');
232
+ } else if (err instanceof Spark.WasmRunnerErrors.BadRequestError) {
233
+ console.error('Bad request:', err.message);
234
+ } else {
235
+ console.error('Unknown error:', err);
236
+ }
237
+ }
238
+ ```
143
239
 
144
- // Cancel the execution (best effort)
145
- execute.cancellationToken.cancel()
240
+ **Available Error Types:**
241
+ - `MissingModelError` - Model not found
242
+ - `ExecuteCancelled` - Execution was cancelled via cancellation token
243
+ - `UnauthorizedError` - Authentication failed (invalid token/key)
244
+ - `BadRequestError` - Invalid request or SDK not initialized properly
146
245
 
147
- execute.response.then((result) => {
148
- // Do something here
149
- })
150
- .catch((err) => {
151
- // Do something here
152
- // If error is instance of Spark.WasmRunnerErrors.ExecuteCancelled, meaning the request has been cancelled
153
- });
246
+ ### Error Response Structure
247
+
248
+ When an online API request fails, the Spark API returns structured error responses:
249
+
250
+ ```json
251
+ {
252
+ "error": {
253
+ "status": 401,
254
+ "message": "Unauthorized: Invalid API key",
255
+ "code": "UNAUTHORIZED"
256
+ }
257
+ }
154
258
  ```
155
259
 
156
- Note: The second parameter (version_id) is optional if version_id is already included on request_meta
260
+ Common HTTP status codes:
261
+ - **400 Bad Request** - Invalid input data or malformed request
262
+ - **401 Unauthorized** - Missing or invalid authentication credentials
263
+ - **404 Not Found** - Model or endpoint not found
264
+ - **500 Internal Server Error** - Server-side error during execution
265
+ - **503 Service Unavailable** - Service temporarily unavailable
157
266
 
158
- ### Sample Input
267
+ ### Input Structure
159
268
 
160
269
  ```json
161
270
  {
162
- "request_data": {
163
- "inputs": {
164
- "Input": 1
165
- }
166
- },
271
+ "request_data": { "inputs": { "Input": 1 } },
167
272
  "request_meta": {
168
273
  "version_id": "<model id>",
169
274
  "transaction_date": "2022-09-19T04:17:17.142Z",
170
- "call_purpose": "Spark - API Tester",
171
- "source_system": "",
172
- "correlation_id": "",
173
- "requested_output": ""
275
+ "call_purpose": "Spark - API Tester"
276
+ // correlation_id, source_system, requested_output (optional)
174
277
  }
175
278
  }
176
279
  ```
177
280
 
178
- ### DEMO
281
+ ## Platform-Specific Usage
282
+
283
+ ### Node.js
284
+
285
+ ```js
286
+ // CommonJS
287
+ const Spark = require('@coherentglobal/spark-execute-sdk');
288
+
289
+ // ES Modules / TypeScript
290
+ import Spark from '@coherentglobal/spark-execute-sdk';
291
+
292
+ const fs = require('fs');
293
+ const spark = new Spark({
294
+ nodegenModels: [{
295
+ versionId: "model-uuid",
296
+ binary: fs.readFileSync('./model.zip', 'base64'),
297
+ metaData: { /* ... */ }
298
+ }]
299
+ });
300
+
301
+ const response = await spark.execute(input);
302
+ await spark.destroy();
303
+ ```
304
+
305
+ ### Browser
306
+
307
+ ```html
308
+ <script src="node_modules/@coherentglobal/spark-execute-sdk/dist/browser.js"></script>
309
+ <script>
310
+ const config = {
311
+ nodegenModels: [{
312
+ versionId: "model-uuid",
313
+ binary: base64EncodedModel,
314
+ metaData: { /* ... */ }
315
+ }]
316
+ };
317
+
318
+ const spark = new Spark(config);
319
+
320
+ spark.execute(input)
321
+ .then(response => console.log('Result:', response))
322
+ .catch(err => console.error('Error:', err))
323
+ .finally(() => spark.destroy()); // Always cleanup!
324
+ </script>
325
+ ```
326
+
327
+ ## Troubleshooting
328
+
329
+ ### Common Issues
330
+
331
+ #### "Model not found" Error
332
+
333
+ **Problem:** SDK throws `MissingModelError` when trying to execute.
334
+
335
+ **Solutions:**
336
+ - Verify that `versionId` in your input matches a configured model in `nodegenModels`
337
+ - Check that `request_meta.version_id` is correctly specified in your input
338
+ - Check if the model binary is loaded (check browser console for loading errors)
339
+
340
+ #### "Binary corrupted" or Initialization Errors
341
+
342
+ **Problem:** Model fails to initialize or throws corruption errors.
343
+
344
+ **Solutions:**
345
+ - Verify the model binary is valid and not truncated during download
346
+ - Check that the base64 encoding is correct (no line breaks or extra characters)
347
+ - Try re-downloading the model from Spark
348
+ - Ensure the binary isn't corrupted (use `type: "base64"` for base64-encoded zips)
349
+
350
+ #### "Worker terminated unexpectedly"
351
+
352
+ **Problem:** WebAssembly worker crashes during execution.
353
+
354
+ **Solutions:**
355
+ - Check browser console for memory errors
356
+ - Reduce the number of concurrent replicas
357
+ - Ensure the model isn't too large for the available memory
358
+ - Try increasing timeout value if the model is computationally intensive
359
+
360
+ #### ExecuteCancelled Thrown Instantly
361
+
362
+ **Problem:** Cancellation error occurs immediately even without calling cancel.
363
+
364
+ **Solutions:**
365
+ - Check if cancellation token is being reused from a previous execution
366
+ - Create a new execution instance for each call to `executeWithCancellationToken`
367
+ - Verify that cancel() isn't being called earlier in your code flow
368
+
369
+ #### Online Fallback Not Triggered
370
+
371
+ **Problem:** SDK doesn't fall back to online execution when offline fails.
372
+
373
+ **Solutions:**
374
+ - Ensure both `sparkEndpoint` and `nodegenModels` are configured
375
+ - Check that the offline error is not being caught before fallback occurs
376
+ - Verify network connectivity for online execution
377
+ - Check authentication credentials for the online endpoint
378
+
379
+ #### Memory Leaks in Long-Running Applications
380
+
381
+ **Problem:** Application memory usage keeps increasing over time.
382
+
383
+ **Solutions:**
384
+ - Always call `spark.destroy()` when finished with an instance
385
+ - Don't create multiple Spark instances unnecessarily
386
+ - Monitor the number of loaded models (check against `offlineMaxService` limit)
387
+ - Use `preventCleanup: false` for models that don't need to persist
388
+
389
+ ## Memory Usage & Cleanup
390
+
391
+ ### How Model Cleanup Works
392
+
393
+ The SDK automatically manages memory using LRU (Least Recently Used) eviction:
394
+
395
+ - **offlineMaxService** (default: 20): Maximum models kept in memory
396
+ - **preventCleanup**: Set to `true` to prevent a model from being unloaded
397
+ - When the limit is reached, the least recently used model (without `preventCleanup`) is unloaded
398
+
399
+ ### Worker Thread Management
400
+
401
+ Each model uses dedicated worker threads for parallel execution.
402
+
403
+ #### Concurrency Model
404
+
405
+ - Each model creates a pool of isolated worker threads based on the `replica` count (default: 1)
406
+ - Incoming execution requests are distributed across available workers in round-robin fashion
407
+ - Each worker runs in its own thread with its own WebAssembly instance—no shared state
408
+ - Model are lazy-loaded on first run
409
+ - Higher `replica` counts increase initial execution time (more workers to load) but improve throughput for concurrent requests
410
+
411
+ #### Configuration Guide
412
+
413
+ | Use Case | `replica` | `preventCleanup` |
414
+ |----------|-----------|------------------|
415
+ | High-frequency model** | 4-8 | `true` |
416
+ | Occasional use | 1 | `false` |
417
+ | Critical path | 2-4 | `true` |
418
+ | Background jobs | 1-2 | `false` |
419
+
420
+ #### Memory Considerations
421
+
422
+ Each worker instance loads the full WebAssembly module into memory:
423
+
424
+ - **Memory per worker** = Model required memory (~150-300 MB)
425
+ - **Total memory** = (Number of models) × (Replicas per model) × (Memory per worker)
426
+ - **Example**: 10 models × 3 replicas × 150 MB = ~4500 MB minimum
427
+
428
+ **NOTE**: We recommend keeping `replica` at 2 or fewer, unless you have a very specific performance requirement that justifies higher concurrency.
429
+
430
+ ### Cleanup Best Practices
431
+
432
+ **Always call destroy():**
433
+ ```js
434
+ const spark = new Spark(config);
435
+ try {
436
+ await spark.execute(input);
437
+ } finally {
438
+ await spark.destroy();
439
+ }
440
+ ```
441
+
442
+ **For long-running apps:**
443
+ - Set `offlineMaxService` based on available memory
444
+ - Use `preventCleanup: true` sparingly
445
+ - Call `destroy()` before page unload (browsers)
446
+
447
+ ## Known Limitations
448
+
449
+ ### Browser Memory Constraints
450
+
451
+ WebAssembly in browsers has strict memory limits:
452
+
453
+ | Constraint | Limit | Impact |
454
+ |------------|-------|--------|
455
+ | **WebAssembly memory** | ~1-2 GB (browser dependent, varied between platform) | Large models may fail to load |
456
+ | **Total tab memory** | ~2-4 GB (browser dependent) | Multiple models + high replicas can hit browser limits |
457
+
458
+ **Recommendations for Browser:**
459
+ - Limit `replica` count to 2-4 for large / highly complex models
460
+ - Monitor memory usage with browser DevTools
461
+ - Consider online-only mode for very large models
462
+
463
+ ### Node.js Worker Thread Cost
464
+
465
+ Each worker thread in Node.js has overhead:
466
+
467
+ - **Startup time**: 50-200ms per worker
468
+ - **Memory overhead**: ~150-300 MB per thread (V8 isolate + runtime)
469
+ - **Efficient thread limit**: Typically 50-200 threads (OS dependent)
470
+
471
+ **For Node.js applications:**
472
+ - Don't create hundreds of workers (replica count × model count should be reasonable)
473
+ - Workers are lazy-loaded but all replicas for a model are initialized on first execution
474
+
475
+ ### Cross-Service Call (XCall) Depth
476
+
477
+ **Recursion Limit:**
478
+ - Maximum XCall depth: ~10-20 levels (implementation dependent)
479
+ - Deeply nested XCalls increase memory usage and execution time
480
+ - Stack overflow possible with excessive recursion
481
+
482
+ **Best Practice:**
483
+ - Design models to minimize XCall depth
484
+ - Avoid circular dependencies between models
485
+ - Consider flattening deeply nested call chains
486
+
487
+ ### Model Loading Failures
488
+
489
+ **Common offline mode failures:**
490
+
491
+ | Scenario | Browser | Node.js |
492
+ |----------|---------|---------|
493
+ | 10+ concurrent replicas | May hit memory limits | Works but slow startup |
494
+ | Complex XCall graphs | Stack overflow possible | More headroom |
495
+
496
+ **Mitigation:**
497
+ - Use online mode for exceptionally large models
498
+ - Reduce `replica` count if initialization fails
499
+ - Monitor memory consumption during development
500
+
501
+ ## Live Demo
502
+
503
+ See the SDK in action with this interactive CodeSandbox example:
179
504
 
180
- DEMO LINK- https://codesandbox.io/s/autumn-hill-1342ln?file=/index.html
505
+ **[View Demo →](https://codesandbox.io/s/autumn-hill-1342ln?file=/index.html)**