@ekodb/ekodb-client 0.16.0 → 0.18.0
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/dist/functions.d.ts +457 -4
- package/dist/functions.js +312 -0
- package/dist/functions.test.d.ts +9 -0
- package/dist/functions.test.js +542 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +1 -1
- package/src/functions.test.ts +726 -0
- package/src/functions.ts +766 -4
- package/src/index.ts +2 -1
package/src/functions.ts
CHANGED
|
@@ -14,6 +14,19 @@ export interface UserFunction {
|
|
|
14
14
|
tags?: string[];
|
|
15
15
|
created_at?: string;
|
|
16
16
|
updated_at?: string;
|
|
17
|
+
/**
|
|
18
|
+
* REST method this function answers — `"GET"`, `"POST"`, etc.
|
|
19
|
+
* Pair with `http_path` to expose the function under the
|
|
20
|
+
* path-routed dispatcher at `/api/route/{path}`.
|
|
21
|
+
* Requires ekoDB >= 0.42.0.
|
|
22
|
+
*/
|
|
23
|
+
http_method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
24
|
+
/**
|
|
25
|
+
* REST path pattern (e.g. `"/users/:id"`). Path segments
|
|
26
|
+
* starting with `:` are extracted into the function's params
|
|
27
|
+
* map at call time. Requires ekoDB >= 0.42.0.
|
|
28
|
+
*/
|
|
29
|
+
http_path?: string;
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
export interface ParameterDefinition {
|
|
@@ -226,6 +239,260 @@ export type FunctionStageConfig =
|
|
|
226
239
|
timeout_seconds?: number;
|
|
227
240
|
output_field?: string;
|
|
228
241
|
collection?: string;
|
|
242
|
+
}
|
|
243
|
+
| {
|
|
244
|
+
/**
|
|
245
|
+
* Bcrypt-hash a plaintext value and add the result to every record in
|
|
246
|
+
* the working data as `output_field`. Requires ekoDB >= 0.41.0.
|
|
247
|
+
*/
|
|
248
|
+
type: "BcryptHash";
|
|
249
|
+
plain: string;
|
|
250
|
+
cost?: number;
|
|
251
|
+
output_field: string;
|
|
252
|
+
}
|
|
253
|
+
| {
|
|
254
|
+
/**
|
|
255
|
+
* Verify a plaintext against a bcrypt hash stored on the first record
|
|
256
|
+
* in the working data and write a boolean result into `output_field`.
|
|
257
|
+
* Pair with an `If` stage for login flows. Requires ekoDB >= 0.41.0.
|
|
258
|
+
*/
|
|
259
|
+
type: "BcryptVerify";
|
|
260
|
+
plain: string;
|
|
261
|
+
hash_field: string;
|
|
262
|
+
output_field: string;
|
|
263
|
+
}
|
|
264
|
+
| {
|
|
265
|
+
/**
|
|
266
|
+
* Generate a cryptographically-random token and add it to every
|
|
267
|
+
* record in the working data. Requires ekoDB >= 0.41.0.
|
|
268
|
+
*/
|
|
269
|
+
type: "RandomToken";
|
|
270
|
+
bytes: number;
|
|
271
|
+
encoding?: "hex" | "base64" | "base64url";
|
|
272
|
+
output_field: string;
|
|
273
|
+
}
|
|
274
|
+
| {
|
|
275
|
+
/**
|
|
276
|
+
* Sign a JWT and write the resulting token to every working
|
|
277
|
+
* record. Pair with `BcryptVerify` to issue a session token
|
|
278
|
+
* after login. Use `"{{env.JWT_SECRET}}"` for `secret` so the
|
|
279
|
+
* LLM never sees the operator-owned signing key. `iat` and
|
|
280
|
+
* `exp` are auto-stamped when `expires_in_secs` is set.
|
|
281
|
+
* Requires ekoDB >= 0.42.0.
|
|
282
|
+
*/
|
|
283
|
+
type: "JwtSign";
|
|
284
|
+
claims: Record<string, unknown>;
|
|
285
|
+
secret: string;
|
|
286
|
+
algorithm?: "HS256" | "HS384" | "HS512";
|
|
287
|
+
expires_in_secs?: number;
|
|
288
|
+
output_field: string;
|
|
289
|
+
}
|
|
290
|
+
| {
|
|
291
|
+
/**
|
|
292
|
+
* Verify a JWT held in `token_field` on the first working
|
|
293
|
+
* record. On success, writes the decoded claims object into
|
|
294
|
+
* `output_field`. On failure, writes `null` so callers can
|
|
295
|
+
* branch with `If { FieldEquals { value: null } }` to reject.
|
|
296
|
+
* Requires ekoDB >= 0.42.0.
|
|
297
|
+
*/
|
|
298
|
+
type: "JwtVerify";
|
|
299
|
+
token_field: string;
|
|
300
|
+
secret: string;
|
|
301
|
+
algorithm?: "HS256" | "HS384" | "HS512";
|
|
302
|
+
output_field: string;
|
|
303
|
+
}
|
|
304
|
+
| {
|
|
305
|
+
/**
|
|
306
|
+
* Send a transactional email through a provider's REST API.
|
|
307
|
+
* Today only `provider = "sendgrid"` is supported. Pull the
|
|
308
|
+
* API key from `"{{env.SENDGRID_API_KEY}}"` so the LLM never
|
|
309
|
+
* sees the operator-owned secret. Result envelope
|
|
310
|
+
* `{provider_status, provider_message, provider}` is written
|
|
311
|
+
* to `output_field` (defaults to `"email_send"`).
|
|
312
|
+
* Requires ekoDB >= 0.42.0.
|
|
313
|
+
*/
|
|
314
|
+
type: "EmailSend";
|
|
315
|
+
to: string;
|
|
316
|
+
subject: string;
|
|
317
|
+
body: string;
|
|
318
|
+
from: string;
|
|
319
|
+
reply_to?: string;
|
|
320
|
+
api_key: string;
|
|
321
|
+
provider?: "sendgrid";
|
|
322
|
+
html?: boolean;
|
|
323
|
+
output_field?: string;
|
|
324
|
+
}
|
|
325
|
+
| {
|
|
326
|
+
/** HMAC-SHA256/384/512 sign. Requires ekoDB >= 0.42.0. */
|
|
327
|
+
type: "HmacSign";
|
|
328
|
+
input: string;
|
|
329
|
+
secret: string;
|
|
330
|
+
algorithm?: "sha256" | "sha384" | "sha512";
|
|
331
|
+
output_field: string;
|
|
332
|
+
encoding?: "hex" | "base64";
|
|
333
|
+
}
|
|
334
|
+
| {
|
|
335
|
+
/** HMAC verify (constant-time). Writes a boolean. */
|
|
336
|
+
type: "HmacVerify";
|
|
337
|
+
input: string;
|
|
338
|
+
provided_mac: string;
|
|
339
|
+
secret: string;
|
|
340
|
+
algorithm?: "sha256" | "sha384" | "sha512";
|
|
341
|
+
encoding?: "hex" | "base64";
|
|
342
|
+
output_field: string;
|
|
343
|
+
}
|
|
344
|
+
| {
|
|
345
|
+
/** AES-256-GCM authenticated encryption. */
|
|
346
|
+
type: "AesEncrypt";
|
|
347
|
+
plaintext: string;
|
|
348
|
+
key: string;
|
|
349
|
+
key_encoding?: "hex" | "base64" | "base64url";
|
|
350
|
+
output_field: string;
|
|
351
|
+
}
|
|
352
|
+
| {
|
|
353
|
+
/** AES-256-GCM decrypt. Reads `{ciphertext, nonce}` envelope from `ciphertext_field`. */
|
|
354
|
+
type: "AesDecrypt";
|
|
355
|
+
ciphertext_field: string;
|
|
356
|
+
key: string;
|
|
357
|
+
key_encoding?: "hex" | "base64" | "base64url";
|
|
358
|
+
output_field: string;
|
|
359
|
+
}
|
|
360
|
+
| {
|
|
361
|
+
/** Generate a v4 UUID into `output_field`. */
|
|
362
|
+
type: "UuidGenerate";
|
|
363
|
+
output_field: string;
|
|
364
|
+
}
|
|
365
|
+
| {
|
|
366
|
+
/** TOTP code generation (RFC 6238). */
|
|
367
|
+
type: "TotpGenerate";
|
|
368
|
+
secret: string;
|
|
369
|
+
digits?: 6 | 8;
|
|
370
|
+
period?: number;
|
|
371
|
+
algorithm?: "sha1" | "sha256" | "sha512";
|
|
372
|
+
output_field: string;
|
|
373
|
+
}
|
|
374
|
+
| {
|
|
375
|
+
/** TOTP verify; tolerates `skew` time-steps either side. */
|
|
376
|
+
type: "TotpVerify";
|
|
377
|
+
code: string;
|
|
378
|
+
secret: string;
|
|
379
|
+
digits?: 6 | 8;
|
|
380
|
+
period?: number;
|
|
381
|
+
algorithm?: "sha1" | "sha256" | "sha512";
|
|
382
|
+
skew?: number;
|
|
383
|
+
output_field: string;
|
|
384
|
+
}
|
|
385
|
+
| {
|
|
386
|
+
/** Base64 encode (`url_safe = true` for URL-safe / no-pad). */
|
|
387
|
+
type: "Base64Encode";
|
|
388
|
+
input: string;
|
|
389
|
+
url_safe?: boolean;
|
|
390
|
+
output_field: string;
|
|
391
|
+
}
|
|
392
|
+
| {
|
|
393
|
+
/** Base64 decode → UTF-8 string. Fail-closed. */
|
|
394
|
+
type: "Base64Decode";
|
|
395
|
+
input: string;
|
|
396
|
+
url_safe?: boolean;
|
|
397
|
+
output_field: string;
|
|
398
|
+
}
|
|
399
|
+
| {
|
|
400
|
+
/** Hex encode (lowercase). */
|
|
401
|
+
type: "HexEncode";
|
|
402
|
+
input: string;
|
|
403
|
+
output_field: string;
|
|
404
|
+
}
|
|
405
|
+
| {
|
|
406
|
+
/** Hex decode → UTF-8 string. Fail-closed. */
|
|
407
|
+
type: "HexDecode";
|
|
408
|
+
input: string;
|
|
409
|
+
output_field: string;
|
|
410
|
+
}
|
|
411
|
+
| {
|
|
412
|
+
/** URL-friendly slug. */
|
|
413
|
+
type: "Slugify";
|
|
414
|
+
input: string;
|
|
415
|
+
output_field: string;
|
|
416
|
+
}
|
|
417
|
+
| {
|
|
418
|
+
/**
|
|
419
|
+
* Idempotency-key claim (KV SETNX with TTL). Writes
|
|
420
|
+
* `{claimed: true, key}` on first call, `{claimed: false, key,
|
|
421
|
+
* response}` on replay. Requires ekoDB >= 0.42.0.
|
|
422
|
+
*/
|
|
423
|
+
type: "IdempotencyClaim";
|
|
424
|
+
key: string;
|
|
425
|
+
ttl_secs: number;
|
|
426
|
+
output_field: string;
|
|
427
|
+
}
|
|
428
|
+
| {
|
|
429
|
+
/**
|
|
430
|
+
* Fixed-window rate-limit gate. `on_exceed` either errors
|
|
431
|
+
* (`"fail"`, default) or writes `allowed: false` (`"skip"`).
|
|
432
|
+
*/
|
|
433
|
+
type: "RateLimit";
|
|
434
|
+
key: string;
|
|
435
|
+
limit: number;
|
|
436
|
+
window_secs: number;
|
|
437
|
+
on_exceed?: "fail" | "skip";
|
|
438
|
+
output_field: string;
|
|
439
|
+
}
|
|
440
|
+
| {
|
|
441
|
+
/** Distributed-lock acquire (token-fenced). */
|
|
442
|
+
type: "LockAcquire";
|
|
443
|
+
key: string;
|
|
444
|
+
ttl_secs: number;
|
|
445
|
+
output_field: string;
|
|
446
|
+
}
|
|
447
|
+
| {
|
|
448
|
+
/** Distributed-lock release; token-fenced (no foreign release). */
|
|
449
|
+
type: "LockRelease";
|
|
450
|
+
key: string;
|
|
451
|
+
token: string;
|
|
452
|
+
output_field: string;
|
|
453
|
+
}
|
|
454
|
+
| {
|
|
455
|
+
/**
|
|
456
|
+
* Try/Catch error handling for graceful failure recovery.
|
|
457
|
+
* Executes try_functions, and if any fail, executes catch_functions.
|
|
458
|
+
*/
|
|
459
|
+
type: "TryCatch";
|
|
460
|
+
try_functions: FunctionStageConfig[];
|
|
461
|
+
catch_functions: FunctionStageConfig[];
|
|
462
|
+
output_error_field?: string;
|
|
463
|
+
}
|
|
464
|
+
| {
|
|
465
|
+
/**
|
|
466
|
+
* Execute multiple functions in parallel (concurrently).
|
|
467
|
+
* All functions run simultaneously, results are merged.
|
|
468
|
+
*/
|
|
469
|
+
type: "Parallel";
|
|
470
|
+
functions: FunctionStageConfig[];
|
|
471
|
+
wait_for_all: boolean;
|
|
472
|
+
}
|
|
473
|
+
| {
|
|
474
|
+
/** Sleep/delay execution for rate limiting or timing control. */
|
|
475
|
+
type: "Sleep";
|
|
476
|
+
duration_ms: string | number;
|
|
477
|
+
}
|
|
478
|
+
| {
|
|
479
|
+
/**
|
|
480
|
+
* Return a shaped response (final output formatting).
|
|
481
|
+
* Constructs the final response object from current execution context.
|
|
482
|
+
*/
|
|
483
|
+
type: "Return";
|
|
484
|
+
fields: Record<string, any>;
|
|
485
|
+
status_code?: number;
|
|
486
|
+
}
|
|
487
|
+
| {
|
|
488
|
+
/**
|
|
489
|
+
* Validate data against a JSON schema before processing.
|
|
490
|
+
* Prevents invalid data from corrupting database or causing errors downstream.
|
|
491
|
+
*/
|
|
492
|
+
type: "Validate";
|
|
493
|
+
schema: Record<string, any>;
|
|
494
|
+
data_field: string;
|
|
495
|
+
on_error?: FunctionStageConfig[];
|
|
229
496
|
};
|
|
230
497
|
|
|
231
498
|
export interface ChatMessage {
|
|
@@ -300,8 +567,54 @@ export interface StageStats {
|
|
|
300
567
|
execution_time_ms: number;
|
|
301
568
|
}
|
|
302
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Reference a call-time function parameter inside a stored-function stage
|
|
572
|
+
* body (Insert.record, Update.updates, UpdateById.updates,
|
|
573
|
+
* FindOneAndUpdate.updates, BatchInsert.records, or any nested JSON value).
|
|
574
|
+
*
|
|
575
|
+
* Returns the structural placeholder `{"type": "Parameter", "name": "<name>"}`.
|
|
576
|
+
* ekoDB's `resolve_json_parameters` recognizes this shape and substitutes the
|
|
577
|
+
* actual parameter value at execution time, preserving the original FieldType
|
|
578
|
+
* (Binary, DateTime, UUID, Decimal, Duration, Number, Set, Vector) via the
|
|
579
|
+
* `{type,value}` wrapped form. Safe to use for any type.
|
|
580
|
+
*
|
|
581
|
+
* This is the structural alternative to the text-level `"{{name}}"` form;
|
|
582
|
+
* both are accepted but structural placeholders are preferred when the
|
|
583
|
+
* parameter is a whole-object Record or a value whose type would be lost in
|
|
584
|
+
* raw JSON.
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```ts
|
|
588
|
+
* const createUser: UserFunction = {
|
|
589
|
+
* label: "users_create",
|
|
590
|
+
* name: "Create user",
|
|
591
|
+
* parameters: {
|
|
592
|
+
* record: { required: true },
|
|
593
|
+
* },
|
|
594
|
+
* functions: [
|
|
595
|
+
* Stage.insert("users", Stage.param("record")),
|
|
596
|
+
* ],
|
|
597
|
+
* };
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
export interface ParameterRef {
|
|
601
|
+
type: "Parameter";
|
|
602
|
+
name: string;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function parameterRef(name: string): ParameterRef {
|
|
606
|
+
return { type: "Parameter", name };
|
|
607
|
+
}
|
|
608
|
+
|
|
303
609
|
// Stage builder functions
|
|
304
610
|
export const Stage = {
|
|
611
|
+
/**
|
|
612
|
+
* Shorthand for `parameterRef(name)` — builds the structural placeholder
|
|
613
|
+
* `{"type": "Parameter", "name": name}`. See `parameterRef` for the full
|
|
614
|
+
* explanation and example.
|
|
615
|
+
*/
|
|
616
|
+
param: (name: string): ParameterRef => parameterRef(name),
|
|
617
|
+
|
|
305
618
|
findAll: (collection: string): FunctionStageConfig => ({
|
|
306
619
|
type: "FindAll",
|
|
307
620
|
collection,
|
|
@@ -344,7 +657,7 @@ export const Stage = {
|
|
|
344
657
|
|
|
345
658
|
insert: (
|
|
346
659
|
collection: string,
|
|
347
|
-
record: Record<string, any
|
|
660
|
+
record: Record<string, any> | ParameterRef,
|
|
348
661
|
bypassRipple = false,
|
|
349
662
|
ttl?: number,
|
|
350
663
|
): FunctionStageConfig => ({
|
|
@@ -358,7 +671,7 @@ export const Stage = {
|
|
|
358
671
|
update: (
|
|
359
672
|
collection: string,
|
|
360
673
|
filter: Record<string, any>,
|
|
361
|
-
updates: Record<string, any
|
|
674
|
+
updates: Record<string, any> | ParameterRef,
|
|
362
675
|
bypassRipple = false,
|
|
363
676
|
ttl?: number,
|
|
364
677
|
): FunctionStageConfig => ({
|
|
@@ -373,7 +686,7 @@ export const Stage = {
|
|
|
373
686
|
updateById: (
|
|
374
687
|
collection: string,
|
|
375
688
|
record_id: string,
|
|
376
|
-
updates: Record<string, any
|
|
689
|
+
updates: Record<string, any> | ParameterRef,
|
|
377
690
|
bypassRipple = false,
|
|
378
691
|
ttl?: number,
|
|
379
692
|
): FunctionStageConfig => ({
|
|
@@ -570,7 +883,7 @@ export const Stage = {
|
|
|
570
883
|
findOneAndUpdate: (
|
|
571
884
|
collection: string,
|
|
572
885
|
record_id: string,
|
|
573
|
-
updates: Record<string, any
|
|
886
|
+
updates: Record<string, any> | ParameterRef,
|
|
574
887
|
bypassRipple = false,
|
|
575
888
|
ttl?: number,
|
|
576
889
|
): FunctionStageConfig => ({
|
|
@@ -684,4 +997,453 @@ export const Stage = {
|
|
|
684
997
|
output_field,
|
|
685
998
|
collection,
|
|
686
999
|
}),
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Bcrypt-hash a plaintext value and write the result into every record
|
|
1003
|
+
* in the working data as `output_field`. Requires ekoDB >= 0.41.0.
|
|
1004
|
+
*
|
|
1005
|
+
* @param plain - Plaintext to hash. Typically a `"{{password}}"`
|
|
1006
|
+
* placeholder that the substituter replaces with the call-time param
|
|
1007
|
+
* before this stage runs.
|
|
1008
|
+
* @param output_field - Field name to write the bcrypt hash into.
|
|
1009
|
+
* @param cost - bcrypt cost factor (4..=31). Defaults to 12 when undefined.
|
|
1010
|
+
*/
|
|
1011
|
+
bcryptHash: (
|
|
1012
|
+
plain: string,
|
|
1013
|
+
output_field: string,
|
|
1014
|
+
cost?: number,
|
|
1015
|
+
): FunctionStageConfig => ({
|
|
1016
|
+
type: "BcryptHash",
|
|
1017
|
+
plain,
|
|
1018
|
+
cost,
|
|
1019
|
+
output_field,
|
|
1020
|
+
}),
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Verify a plaintext against a bcrypt hash stored on the first record in
|
|
1024
|
+
* the working data. Writes a boolean into `output_field` on every
|
|
1025
|
+
* working record. Pair with `Stage.if` to branch on success / failure.
|
|
1026
|
+
* Requires ekoDB >= 0.41.0.
|
|
1027
|
+
*
|
|
1028
|
+
* @param plain - Plaintext to verify (typically `"{{password}}"`).
|
|
1029
|
+
* @param hash_field - Name of the field on the current record that
|
|
1030
|
+
* holds the stored bcrypt hash (e.g. `"password_hash"`).
|
|
1031
|
+
* @param output_field - Field name to write the boolean result into.
|
|
1032
|
+
*/
|
|
1033
|
+
bcryptVerify: (
|
|
1034
|
+
plain: string,
|
|
1035
|
+
hash_field: string,
|
|
1036
|
+
output_field: string,
|
|
1037
|
+
): FunctionStageConfig => ({
|
|
1038
|
+
type: "BcryptVerify",
|
|
1039
|
+
plain,
|
|
1040
|
+
hash_field,
|
|
1041
|
+
output_field,
|
|
1042
|
+
}),
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Generate a cryptographically-random token and add it to every record
|
|
1046
|
+
* in the working data. Requires ekoDB >= 0.41.0.
|
|
1047
|
+
*
|
|
1048
|
+
* @param bytes - Number of random bytes to draw (1..=1024).
|
|
1049
|
+
* @param output_field - Field name to write the encoded token into.
|
|
1050
|
+
* @param encoding - `"hex"` (default) | `"base64"` | `"base64url"`.
|
|
1051
|
+
*/
|
|
1052
|
+
randomToken: (
|
|
1053
|
+
bytes: number,
|
|
1054
|
+
output_field: string,
|
|
1055
|
+
encoding?: "hex" | "base64" | "base64url",
|
|
1056
|
+
): FunctionStageConfig => ({
|
|
1057
|
+
type: "RandomToken",
|
|
1058
|
+
bytes,
|
|
1059
|
+
encoding,
|
|
1060
|
+
output_field,
|
|
1061
|
+
}),
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Sign a JWT and write the resulting token to every working
|
|
1065
|
+
* record. Pair with `Stage.bcryptVerify` to issue a session
|
|
1066
|
+
* token after login. Use `"{{env.JWT_SECRET}}"` for `secret` so
|
|
1067
|
+
* the LLM never sees the operator-owned signing key. `iat` and
|
|
1068
|
+
* `exp` are auto-stamped when `expires_in_secs` is set.
|
|
1069
|
+
* Requires ekoDB >= 0.42.0.
|
|
1070
|
+
*
|
|
1071
|
+
* @param claims - JWT payload claims.
|
|
1072
|
+
* @param secret - Signing secret (typically `"{{env.JWT_SECRET}}"`).
|
|
1073
|
+
* @param output_field - Field name to write the signed JWT into.
|
|
1074
|
+
* @param expires_in_secs - Lifetime in seconds (auto-stamps `iat` + `exp`).
|
|
1075
|
+
* @param algorithm - `"HS256"` (default) | `"HS384"` | `"HS512"`.
|
|
1076
|
+
*/
|
|
1077
|
+
jwtSign: (
|
|
1078
|
+
claims: Record<string, unknown>,
|
|
1079
|
+
secret: string,
|
|
1080
|
+
output_field: string,
|
|
1081
|
+
expires_in_secs?: number,
|
|
1082
|
+
algorithm?: "HS256" | "HS384" | "HS512",
|
|
1083
|
+
): FunctionStageConfig => ({
|
|
1084
|
+
type: "JwtSign",
|
|
1085
|
+
claims,
|
|
1086
|
+
secret,
|
|
1087
|
+
algorithm,
|
|
1088
|
+
expires_in_secs,
|
|
1089
|
+
output_field,
|
|
1090
|
+
}),
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Verify a JWT held in `token_field` on the first working record.
|
|
1094
|
+
* On success writes the decoded claims object into `output_field`;
|
|
1095
|
+
* on failure writes `null`. Branch with `Stage.if` matching
|
|
1096
|
+
* `output_field == null` to reject. Requires ekoDB >= 0.42.0.
|
|
1097
|
+
*
|
|
1098
|
+
* @param token_field - Field on the working record holding the JWT.
|
|
1099
|
+
* @param secret - Verification secret (must match the signing secret).
|
|
1100
|
+
* @param output_field - Field name to write decoded claims into.
|
|
1101
|
+
* @param algorithm - Expected algorithm (default `"HS256"`).
|
|
1102
|
+
*/
|
|
1103
|
+
jwtVerify: (
|
|
1104
|
+
token_field: string,
|
|
1105
|
+
secret: string,
|
|
1106
|
+
output_field: string,
|
|
1107
|
+
algorithm?: "HS256" | "HS384" | "HS512",
|
|
1108
|
+
): FunctionStageConfig => ({
|
|
1109
|
+
type: "JwtVerify",
|
|
1110
|
+
token_field,
|
|
1111
|
+
secret,
|
|
1112
|
+
algorithm,
|
|
1113
|
+
output_field,
|
|
1114
|
+
}),
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Send a transactional email. Today only the `"sendgrid"`
|
|
1118
|
+
* provider is supported. Use `"{{env.SENDGRID_API_KEY}}"` for
|
|
1119
|
+
* `api_key` so the LLM never sees the operator-owned secret.
|
|
1120
|
+
* Set `html: true` to send `text/html`. The result envelope
|
|
1121
|
+
* (`{provider_status, provider_message, provider}`) is written
|
|
1122
|
+
* to `output_field` (default `"email_send"`).
|
|
1123
|
+
* Requires ekoDB >= 0.42.0.
|
|
1124
|
+
*/
|
|
1125
|
+
emailSend: (
|
|
1126
|
+
to: string,
|
|
1127
|
+
subject: string,
|
|
1128
|
+
body: string,
|
|
1129
|
+
from: string,
|
|
1130
|
+
api_key: string,
|
|
1131
|
+
options?: {
|
|
1132
|
+
reply_to?: string;
|
|
1133
|
+
provider?: "sendgrid";
|
|
1134
|
+
html?: boolean;
|
|
1135
|
+
output_field?: string;
|
|
1136
|
+
},
|
|
1137
|
+
): FunctionStageConfig => ({
|
|
1138
|
+
type: "EmailSend",
|
|
1139
|
+
to,
|
|
1140
|
+
subject,
|
|
1141
|
+
body,
|
|
1142
|
+
from,
|
|
1143
|
+
reply_to: options?.reply_to,
|
|
1144
|
+
api_key,
|
|
1145
|
+
provider: options?.provider,
|
|
1146
|
+
html: options?.html,
|
|
1147
|
+
output_field: options?.output_field,
|
|
1148
|
+
}),
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Try/Catch error handling for graceful failure recovery.
|
|
1152
|
+
* Executes tryFunctions, and if any fail, executes catchFunctions.
|
|
1153
|
+
*
|
|
1154
|
+
* @param tryFunctions - Functions to attempt.
|
|
1155
|
+
* @param catchFunctions - Functions to execute on failure.
|
|
1156
|
+
* @param outputErrorField - Field name to store error details (default: "error").
|
|
1157
|
+
*/
|
|
1158
|
+
tryCatch: (
|
|
1159
|
+
tryFunctions: FunctionStageConfig[],
|
|
1160
|
+
catchFunctions: FunctionStageConfig[],
|
|
1161
|
+
outputErrorField?: string,
|
|
1162
|
+
): FunctionStageConfig => ({
|
|
1163
|
+
type: "TryCatch",
|
|
1164
|
+
try_functions: tryFunctions,
|
|
1165
|
+
catch_functions: catchFunctions,
|
|
1166
|
+
output_error_field: outputErrorField,
|
|
1167
|
+
}),
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Execute multiple functions in parallel (concurrently).
|
|
1171
|
+
* All functions run simultaneously, results are merged.
|
|
1172
|
+
*
|
|
1173
|
+
* @param functions - Functions to execute concurrently.
|
|
1174
|
+
* @param waitForAll - true = wait for all to complete, false = return on first completion.
|
|
1175
|
+
*/
|
|
1176
|
+
parallel: (
|
|
1177
|
+
functions: FunctionStageConfig[],
|
|
1178
|
+
waitForAll = true,
|
|
1179
|
+
): FunctionStageConfig => ({
|
|
1180
|
+
type: "Parallel",
|
|
1181
|
+
functions,
|
|
1182
|
+
wait_for_all: waitForAll,
|
|
1183
|
+
}),
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Sleep/delay execution for rate limiting or timing control.
|
|
1187
|
+
*
|
|
1188
|
+
* @param durationMs - Duration in milliseconds: `1000` or `"{{delay_param}}"`.
|
|
1189
|
+
*/
|
|
1190
|
+
sleep: (durationMs: string | number): FunctionStageConfig => ({
|
|
1191
|
+
type: "Sleep",
|
|
1192
|
+
duration_ms: durationMs,
|
|
1193
|
+
}),
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Return a shaped response (final output formatting).
|
|
1197
|
+
* Constructs the final response object from current execution context.
|
|
1198
|
+
*
|
|
1199
|
+
* @param fields - Fields to include in response with `{{param}}` substitution.
|
|
1200
|
+
* @param statusCode - HTTP status code (default: 200).
|
|
1201
|
+
*/
|
|
1202
|
+
returnResponse: (
|
|
1203
|
+
fields: Record<string, any>,
|
|
1204
|
+
statusCode?: number,
|
|
1205
|
+
): FunctionStageConfig => ({
|
|
1206
|
+
type: "Return",
|
|
1207
|
+
fields,
|
|
1208
|
+
status_code: statusCode,
|
|
1209
|
+
}),
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Validate data against a JSON schema before processing.
|
|
1213
|
+
*
|
|
1214
|
+
* @param schema - JSON Schema to validate against.
|
|
1215
|
+
* @param dataField - Field containing data to validate.
|
|
1216
|
+
* @param onError - Functions to execute on validation failure.
|
|
1217
|
+
*/
|
|
1218
|
+
validate: (
|
|
1219
|
+
schema: Record<string, any>,
|
|
1220
|
+
dataField: string,
|
|
1221
|
+
onError?: FunctionStageConfig[],
|
|
1222
|
+
): FunctionStageConfig => ({
|
|
1223
|
+
type: "Validate",
|
|
1224
|
+
schema,
|
|
1225
|
+
data_field: dataField,
|
|
1226
|
+
on_error: onError,
|
|
1227
|
+
}),
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* HMAC-SHA256/384/512 sign. Use for outbound webhook signing or
|
|
1231
|
+
* pre-signed URL generation. Requires ekoDB >= 0.42.0.
|
|
1232
|
+
*/
|
|
1233
|
+
hmacSign: (
|
|
1234
|
+
input: string,
|
|
1235
|
+
secret: string,
|
|
1236
|
+
output_field: string,
|
|
1237
|
+
options?: {
|
|
1238
|
+
algorithm?: "sha256" | "sha384" | "sha512";
|
|
1239
|
+
encoding?: "hex" | "base64";
|
|
1240
|
+
},
|
|
1241
|
+
): FunctionStageConfig => ({
|
|
1242
|
+
type: "HmacSign",
|
|
1243
|
+
input,
|
|
1244
|
+
secret,
|
|
1245
|
+
algorithm: options?.algorithm,
|
|
1246
|
+
output_field,
|
|
1247
|
+
encoding: options?.encoding,
|
|
1248
|
+
}),
|
|
1249
|
+
|
|
1250
|
+
/** HMAC verify (constant-time). Writes a boolean. */
|
|
1251
|
+
hmacVerify: (
|
|
1252
|
+
input: string,
|
|
1253
|
+
provided_mac: string,
|
|
1254
|
+
secret: string,
|
|
1255
|
+
output_field: string,
|
|
1256
|
+
options?: {
|
|
1257
|
+
algorithm?: "sha256" | "sha384" | "sha512";
|
|
1258
|
+
encoding?: "hex" | "base64";
|
|
1259
|
+
},
|
|
1260
|
+
): FunctionStageConfig => ({
|
|
1261
|
+
type: "HmacVerify",
|
|
1262
|
+
input,
|
|
1263
|
+
provided_mac,
|
|
1264
|
+
secret,
|
|
1265
|
+
algorithm: options?.algorithm,
|
|
1266
|
+
encoding: options?.encoding,
|
|
1267
|
+
output_field,
|
|
1268
|
+
}),
|
|
1269
|
+
|
|
1270
|
+
/** AES-256-GCM encrypt; writes `{ciphertext, nonce}` envelope. */
|
|
1271
|
+
aesEncrypt: (
|
|
1272
|
+
plaintext: string,
|
|
1273
|
+
key: string,
|
|
1274
|
+
output_field: string,
|
|
1275
|
+
key_encoding?: "hex" | "base64" | "base64url",
|
|
1276
|
+
): FunctionStageConfig => ({
|
|
1277
|
+
type: "AesEncrypt",
|
|
1278
|
+
plaintext,
|
|
1279
|
+
key,
|
|
1280
|
+
key_encoding,
|
|
1281
|
+
output_field,
|
|
1282
|
+
}),
|
|
1283
|
+
|
|
1284
|
+
/** AES-256-GCM decrypt; reads envelope from `ciphertext_field`. */
|
|
1285
|
+
aesDecrypt: (
|
|
1286
|
+
ciphertext_field: string,
|
|
1287
|
+
key: string,
|
|
1288
|
+
output_field: string,
|
|
1289
|
+
key_encoding?: "hex" | "base64" | "base64url",
|
|
1290
|
+
): FunctionStageConfig => ({
|
|
1291
|
+
type: "AesDecrypt",
|
|
1292
|
+
ciphertext_field,
|
|
1293
|
+
key,
|
|
1294
|
+
key_encoding,
|
|
1295
|
+
output_field,
|
|
1296
|
+
}),
|
|
1297
|
+
|
|
1298
|
+
/** Generate a v4 UUID into `output_field`. */
|
|
1299
|
+
uuidGenerate: (output_field: string): FunctionStageConfig => ({
|
|
1300
|
+
type: "UuidGenerate",
|
|
1301
|
+
output_field,
|
|
1302
|
+
}),
|
|
1303
|
+
|
|
1304
|
+
/** TOTP code generation (RFC 6238). */
|
|
1305
|
+
totpGenerate: (
|
|
1306
|
+
secret: string,
|
|
1307
|
+
output_field: string,
|
|
1308
|
+
options?: {
|
|
1309
|
+
digits?: 6 | 8;
|
|
1310
|
+
period?: number;
|
|
1311
|
+
algorithm?: "sha1" | "sha256" | "sha512";
|
|
1312
|
+
},
|
|
1313
|
+
): FunctionStageConfig => ({
|
|
1314
|
+
type: "TotpGenerate",
|
|
1315
|
+
secret,
|
|
1316
|
+
digits: options?.digits,
|
|
1317
|
+
period: options?.period,
|
|
1318
|
+
algorithm: options?.algorithm,
|
|
1319
|
+
output_field,
|
|
1320
|
+
}),
|
|
1321
|
+
|
|
1322
|
+
/** TOTP verify; tolerates `skew` time-steps either side (default 1). */
|
|
1323
|
+
totpVerify: (
|
|
1324
|
+
code: string,
|
|
1325
|
+
secret: string,
|
|
1326
|
+
output_field: string,
|
|
1327
|
+
options?: {
|
|
1328
|
+
digits?: 6 | 8;
|
|
1329
|
+
period?: number;
|
|
1330
|
+
algorithm?: "sha1" | "sha256" | "sha512";
|
|
1331
|
+
skew?: number;
|
|
1332
|
+
},
|
|
1333
|
+
): FunctionStageConfig => ({
|
|
1334
|
+
type: "TotpVerify",
|
|
1335
|
+
code,
|
|
1336
|
+
secret,
|
|
1337
|
+
digits: options?.digits,
|
|
1338
|
+
period: options?.period,
|
|
1339
|
+
algorithm: options?.algorithm,
|
|
1340
|
+
skew: options?.skew,
|
|
1341
|
+
output_field,
|
|
1342
|
+
}),
|
|
1343
|
+
|
|
1344
|
+
/** Base64 encode (`url_safe = true` for URL-safe / no-pad). */
|
|
1345
|
+
base64Encode: (
|
|
1346
|
+
input: string,
|
|
1347
|
+
output_field: string,
|
|
1348
|
+
url_safe?: boolean,
|
|
1349
|
+
): FunctionStageConfig => ({
|
|
1350
|
+
type: "Base64Encode",
|
|
1351
|
+
input,
|
|
1352
|
+
url_safe,
|
|
1353
|
+
output_field,
|
|
1354
|
+
}),
|
|
1355
|
+
|
|
1356
|
+
/** Base64 decode → UTF-8 string. Fail-closed. */
|
|
1357
|
+
base64Decode: (
|
|
1358
|
+
input: string,
|
|
1359
|
+
output_field: string,
|
|
1360
|
+
url_safe?: boolean,
|
|
1361
|
+
): FunctionStageConfig => ({
|
|
1362
|
+
type: "Base64Decode",
|
|
1363
|
+
input,
|
|
1364
|
+
url_safe,
|
|
1365
|
+
output_field,
|
|
1366
|
+
}),
|
|
1367
|
+
|
|
1368
|
+
/** Hex encode (lowercase). */
|
|
1369
|
+
hexEncode: (input: string, output_field: string): FunctionStageConfig => ({
|
|
1370
|
+
type: "HexEncode",
|
|
1371
|
+
input,
|
|
1372
|
+
output_field,
|
|
1373
|
+
}),
|
|
1374
|
+
|
|
1375
|
+
/** Hex decode → UTF-8 string. Fail-closed. */
|
|
1376
|
+
hexDecode: (input: string, output_field: string): FunctionStageConfig => ({
|
|
1377
|
+
type: "HexDecode",
|
|
1378
|
+
input,
|
|
1379
|
+
output_field,
|
|
1380
|
+
}),
|
|
1381
|
+
|
|
1382
|
+
/** URL-friendly slug. */
|
|
1383
|
+
slugify: (input: string, output_field: string): FunctionStageConfig => ({
|
|
1384
|
+
type: "Slugify",
|
|
1385
|
+
input,
|
|
1386
|
+
output_field,
|
|
1387
|
+
}),
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Idempotency-key claim (KV SETNX with TTL). Pass an idempotency
|
|
1391
|
+
* key (typically `"{{idempotency_key}}"`) and a TTL; first call
|
|
1392
|
+
* writes `{claimed: true, key}`, subsequent calls within the TTL
|
|
1393
|
+
* write `{claimed: false, key, response}` so the caller can
|
|
1394
|
+
* short-circuit. Requires ekoDB >= 0.42.0.
|
|
1395
|
+
*/
|
|
1396
|
+
idempotencyClaim: (
|
|
1397
|
+
key: string,
|
|
1398
|
+
ttl_secs: number,
|
|
1399
|
+
output_field: string,
|
|
1400
|
+
): FunctionStageConfig => ({
|
|
1401
|
+
type: "IdempotencyClaim",
|
|
1402
|
+
key,
|
|
1403
|
+
ttl_secs,
|
|
1404
|
+
output_field,
|
|
1405
|
+
}),
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* Fixed-window rate-limit gate. `on_exceed` either errors
|
|
1409
|
+
* (`"fail"`, default) or writes `allowed: false` (`"skip"`).
|
|
1410
|
+
*/
|
|
1411
|
+
rateLimit: (
|
|
1412
|
+
key: string,
|
|
1413
|
+
limit: number,
|
|
1414
|
+
window_secs: number,
|
|
1415
|
+
output_field: string,
|
|
1416
|
+
on_exceed?: "fail" | "skip",
|
|
1417
|
+
): FunctionStageConfig => ({
|
|
1418
|
+
type: "RateLimit",
|
|
1419
|
+
key,
|
|
1420
|
+
limit,
|
|
1421
|
+
window_secs,
|
|
1422
|
+
on_exceed,
|
|
1423
|
+
output_field,
|
|
1424
|
+
}),
|
|
1425
|
+
|
|
1426
|
+
/** Distributed-lock acquire (token-fenced). */
|
|
1427
|
+
lockAcquire: (
|
|
1428
|
+
key: string,
|
|
1429
|
+
ttl_secs: number,
|
|
1430
|
+
output_field: string,
|
|
1431
|
+
): FunctionStageConfig => ({
|
|
1432
|
+
type: "LockAcquire",
|
|
1433
|
+
key,
|
|
1434
|
+
ttl_secs,
|
|
1435
|
+
output_field,
|
|
1436
|
+
}),
|
|
1437
|
+
|
|
1438
|
+
/** Distributed-lock release; only releases on token match. */
|
|
1439
|
+
lockRelease: (
|
|
1440
|
+
key: string,
|
|
1441
|
+
token: string,
|
|
1442
|
+
output_field: string,
|
|
1443
|
+
): FunctionStageConfig => ({
|
|
1444
|
+
type: "LockRelease",
|
|
1445
|
+
key,
|
|
1446
|
+
token,
|
|
1447
|
+
output_field,
|
|
1448
|
+
}),
|
|
687
1449
|
};
|