@ekodb/ekodb-client 0.17.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.
@@ -195,6 +195,90 @@ const functions_1 = require("./functions");
195
195
  });
196
196
  });
197
197
  // ============================================================================
198
+ // JWT primitives: JwtSign, JwtVerify (ekoDB >= 0.42.0)
199
+ // ============================================================================
200
+ (0, vitest_1.describe)("Stage.jwtSign", () => {
201
+ (0, vitest_1.it)("produces a JwtSign stage with claims, expiry, and algorithm", () => {
202
+ const stage = functions_1.Stage.jwtSign({ sub: "{{user_id}}", role: "admin" }, "{{env.JWT_SECRET}}", "token", 3600, "HS256");
203
+ (0, vitest_1.expect)(stage.type).toBe("JwtSign");
204
+ (0, vitest_1.expect)(stage.claims).toEqual({ sub: "{{user_id}}", role: "admin" });
205
+ (0, vitest_1.expect)(stage.secret).toBe("{{env.JWT_SECRET}}");
206
+ (0, vitest_1.expect)(stage.expires_in_secs).toBe(3600);
207
+ (0, vitest_1.expect)(stage.algorithm).toBe("HS256");
208
+ (0, vitest_1.expect)(stage.output_field).toBe("token");
209
+ });
210
+ (0, vitest_1.it)("leaves algorithm and expires_in_secs undefined when omitted", () => {
211
+ const stage = functions_1.Stage.jwtSign({ sub: "u" }, "{{env.JWT_SECRET}}", "t");
212
+ (0, vitest_1.expect)(stage.algorithm).toBeUndefined();
213
+ (0, vitest_1.expect)(stage.expires_in_secs).toBeUndefined();
214
+ });
215
+ });
216
+ (0, vitest_1.describe)("Stage.jwtVerify", () => {
217
+ (0, vitest_1.it)("produces a JwtVerify stage wiring token_field and output_field", () => {
218
+ const stage = functions_1.Stage.jwtVerify("auth_token", "{{env.JWT_SECRET}}", "claims", "HS512");
219
+ (0, vitest_1.expect)(stage.type).toBe("JwtVerify");
220
+ (0, vitest_1.expect)(stage.token_field).toBe("auth_token");
221
+ (0, vitest_1.expect)(stage.secret).toBe("{{env.JWT_SECRET}}");
222
+ (0, vitest_1.expect)(stage.algorithm).toBe("HS512");
223
+ (0, vitest_1.expect)(stage.output_field).toBe("claims");
224
+ });
225
+ });
226
+ (0, vitest_1.describe)("JWT stages JSON wire format", () => {
227
+ (0, vitest_1.it)("JwtSign round-trips through JSON unchanged", () => {
228
+ const stage = functions_1.Stage.jwtSign({ sub: "user-1" }, "{{env.JWT_SECRET}}", "token", 3600, "HS256");
229
+ const wire = JSON.parse(JSON.stringify(stage));
230
+ (0, vitest_1.expect)(wire).toEqual({
231
+ type: "JwtSign",
232
+ claims: { sub: "user-1" },
233
+ secret: "{{env.JWT_SECRET}}",
234
+ algorithm: "HS256",
235
+ expires_in_secs: 3600,
236
+ output_field: "token",
237
+ });
238
+ });
239
+ (0, vitest_1.it)("JwtVerify round-trips through JSON unchanged", () => {
240
+ const stage = functions_1.Stage.jwtVerify("token", "{{env.JWT_SECRET}}", "claims", "HS256");
241
+ const wire = JSON.parse(JSON.stringify(stage));
242
+ (0, vitest_1.expect)(wire).toEqual({
243
+ type: "JwtVerify",
244
+ token_field: "token",
245
+ secret: "{{env.JWT_SECRET}}",
246
+ algorithm: "HS256",
247
+ output_field: "claims",
248
+ });
249
+ });
250
+ });
251
+ // ============================================================================
252
+ // EmailSend (ekoDB >= 0.42.0)
253
+ // ============================================================================
254
+ (0, vitest_1.describe)("Stage.emailSend", () => {
255
+ (0, vitest_1.it)("produces a SendGrid EmailSend stage with full payload", () => {
256
+ const stage = functions_1.Stage.emailSend("alice@example.com", "Welcome", "<p>Hi Alice</p>", "bot@example.com", "{{env.SENDGRID_API_KEY}}", {
257
+ reply_to: "support@example.com",
258
+ provider: "sendgrid",
259
+ html: true,
260
+ output_field: "send_result",
261
+ });
262
+ (0, vitest_1.expect)(stage.type).toBe("EmailSend");
263
+ (0, vitest_1.expect)(stage.to).toBe("alice@example.com");
264
+ (0, vitest_1.expect)(stage.subject).toBe("Welcome");
265
+ (0, vitest_1.expect)(stage.body).toBe("<p>Hi Alice</p>");
266
+ (0, vitest_1.expect)(stage.from).toBe("bot@example.com");
267
+ (0, vitest_1.expect)(stage.reply_to).toBe("support@example.com");
268
+ (0, vitest_1.expect)(stage.api_key).toBe("{{env.SENDGRID_API_KEY}}");
269
+ (0, vitest_1.expect)(stage.provider).toBe("sendgrid");
270
+ (0, vitest_1.expect)(stage.html).toBe(true);
271
+ (0, vitest_1.expect)(stage.output_field).toBe("send_result");
272
+ });
273
+ (0, vitest_1.it)("leaves optional fields undefined when omitted", () => {
274
+ const stage = functions_1.Stage.emailSend("x@example.com", "s", "b", "f@example.com", "k");
275
+ (0, vitest_1.expect)(stage.reply_to).toBeUndefined();
276
+ (0, vitest_1.expect)(stage.provider).toBeUndefined();
277
+ (0, vitest_1.expect)(stage.html).toBeUndefined();
278
+ (0, vitest_1.expect)(stage.output_field).toBeUndefined();
279
+ });
280
+ });
281
+ // ============================================================================
198
282
  // Error Handling & Control Flow: TryCatch, Parallel, Sleep (ekoDB >= 0.42.0)
199
283
  // ============================================================================
200
284
  (0, vitest_1.describe)("Stage.tryCatch", () => {
@@ -325,3 +409,134 @@ const functions_1 = require("./functions");
325
409
  });
326
410
  });
327
411
  });
412
+ (0, vitest_1.describe)("Crypto and concurrency stages", () => {
413
+ (0, vitest_1.it)("hmacSign builds a stage with explicit algorithm and encoding", () => {
414
+ const s = functions_1.Stage.hmacSign("{{payload}}", "{{env.KEY}}", "mac", {
415
+ algorithm: "sha256",
416
+ encoding: "hex",
417
+ });
418
+ (0, vitest_1.expect)(s).toEqual({
419
+ type: "HmacSign",
420
+ input: "{{payload}}",
421
+ secret: "{{env.KEY}}",
422
+ algorithm: "sha256",
423
+ output_field: "mac",
424
+ encoding: "hex",
425
+ });
426
+ });
427
+ (0, vitest_1.it)("hmacVerify wires all fields", () => {
428
+ const s = functions_1.Stage.hmacVerify("{{p}}", "{{m}}", "{{env.K}}", "ok");
429
+ (0, vitest_1.expect)(s).toMatchObject({
430
+ type: "HmacVerify",
431
+ input: "{{p}}",
432
+ provided_mac: "{{m}}",
433
+ secret: "{{env.K}}",
434
+ output_field: "ok",
435
+ });
436
+ });
437
+ (0, vitest_1.it)("aesEncrypt and aesDecrypt build matching envelope contracts", () => {
438
+ const enc = functions_1.Stage.aesEncrypt("{{plain}}", "{{env.DATA_KEY}}", "envelope", "hex");
439
+ (0, vitest_1.expect)(enc).toMatchObject({
440
+ type: "AesEncrypt",
441
+ plaintext: "{{plain}}",
442
+ key: "{{env.DATA_KEY}}",
443
+ key_encoding: "hex",
444
+ output_field: "envelope",
445
+ });
446
+ const dec = functions_1.Stage.aesDecrypt("envelope", "{{env.DATA_KEY}}", "plain", "hex");
447
+ (0, vitest_1.expect)(dec).toMatchObject({
448
+ type: "AesDecrypt",
449
+ ciphertext_field: "envelope",
450
+ output_field: "plain",
451
+ });
452
+ });
453
+ (0, vitest_1.it)("uuidGenerate is a single-field stage", () => {
454
+ (0, vitest_1.expect)(functions_1.Stage.uuidGenerate("id")).toEqual({
455
+ type: "UuidGenerate",
456
+ output_field: "id",
457
+ });
458
+ });
459
+ (0, vitest_1.it)("totpGenerate and totpVerify build with all options", () => {
460
+ const gen = functions_1.Stage.totpGenerate("{{env.TOTP}}", "code", {
461
+ digits: 6,
462
+ period: 30,
463
+ algorithm: "sha1",
464
+ });
465
+ (0, vitest_1.expect)(gen.type).toBe("TotpGenerate");
466
+ const ver = functions_1.Stage.totpVerify("{{user_code}}", "{{env.TOTP}}", "ok", {
467
+ skew: 1,
468
+ });
469
+ (0, vitest_1.expect)(ver.type).toBe("TotpVerify");
470
+ });
471
+ (0, vitest_1.it)("base64Encode/Decode and hexEncode/Decode build correctly", () => {
472
+ (0, vitest_1.expect)(functions_1.Stage.base64Encode("{{x}}", "b", true)).toMatchObject({
473
+ type: "Base64Encode",
474
+ url_safe: true,
475
+ });
476
+ (0, vitest_1.expect)(functions_1.Stage.base64Decode("{{b}}", "x")).toMatchObject({
477
+ type: "Base64Decode",
478
+ });
479
+ (0, vitest_1.expect)(functions_1.Stage.hexEncode("{{x}}", "h")).toEqual({
480
+ type: "HexEncode",
481
+ input: "{{x}}",
482
+ output_field: "h",
483
+ });
484
+ (0, vitest_1.expect)(functions_1.Stage.hexDecode("{{h}}", "x")).toEqual({
485
+ type: "HexDecode",
486
+ input: "{{h}}",
487
+ output_field: "x",
488
+ });
489
+ });
490
+ (0, vitest_1.it)("slugify builds a stage", () => {
491
+ (0, vitest_1.expect)(functions_1.Stage.slugify("{{title}}", "slug")).toEqual({
492
+ type: "Slugify",
493
+ input: "{{title}}",
494
+ output_field: "slug",
495
+ });
496
+ });
497
+ (0, vitest_1.it)("idempotencyClaim, rateLimit, lockAcquire, lockRelease build correctly", () => {
498
+ (0, vitest_1.expect)(functions_1.Stage.idempotencyClaim("{{ikey}}", 3600, "claim")).toEqual({
499
+ type: "IdempotencyClaim",
500
+ key: "{{ikey}}",
501
+ ttl_secs: 3600,
502
+ output_field: "claim",
503
+ });
504
+ (0, vitest_1.expect)(functions_1.Stage.rateLimit("{{user}}", 100, 60, "rl", "skip")).toMatchObject({
505
+ type: "RateLimit",
506
+ limit: 100,
507
+ window_secs: 60,
508
+ on_exceed: "skip",
509
+ });
510
+ (0, vitest_1.expect)(functions_1.Stage.lockAcquire("{{r}}", 30, "lock")).toEqual({
511
+ type: "LockAcquire",
512
+ key: "{{r}}",
513
+ ttl_secs: 30,
514
+ output_field: "lock",
515
+ });
516
+ (0, vitest_1.expect)(functions_1.Stage.lockRelease("{{r}}", "{{lock.token}}", "rel")).toEqual({
517
+ type: "LockRelease",
518
+ key: "{{r}}",
519
+ token: "{{lock.token}}",
520
+ output_field: "rel",
521
+ });
522
+ });
523
+ (0, vitest_1.it)("crypto + concurrency stages round-trip through JSON unchanged", () => {
524
+ const stages = [
525
+ functions_1.Stage.hmacSign("a", "k", "m", { algorithm: "sha256", encoding: "hex" }),
526
+ functions_1.Stage.aesEncrypt("p", "k", "e", "hex"),
527
+ functions_1.Stage.uuidGenerate("id"),
528
+ functions_1.Stage.totpGenerate("s", "c", {
529
+ digits: 6,
530
+ period: 30,
531
+ algorithm: "sha1",
532
+ }),
533
+ functions_1.Stage.idempotencyClaim("k", 60, "f"),
534
+ functions_1.Stage.rateLimit("k", 5, 60, "rl"),
535
+ functions_1.Stage.lockAcquire("r", 60, "l"),
536
+ ];
537
+ for (const s of stages) {
538
+ const wire = JSON.parse(JSON.stringify(s));
539
+ (0, vitest_1.expect)(wire.type).toBe(s.type);
540
+ }
541
+ });
542
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekodb/ekodb-client",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Official TypeScript/JavaScript client for ekoDB",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -254,6 +254,138 @@ describe("Crypto stages JSON wire format", () => {
254
254
  });
255
255
  });
256
256
 
257
+ // ============================================================================
258
+ // JWT primitives: JwtSign, JwtVerify (ekoDB >= 0.42.0)
259
+ // ============================================================================
260
+
261
+ describe("Stage.jwtSign", () => {
262
+ it("produces a JwtSign stage with claims, expiry, and algorithm", () => {
263
+ const stage = Stage.jwtSign(
264
+ { sub: "{{user_id}}", role: "admin" },
265
+ "{{env.JWT_SECRET}}",
266
+ "token",
267
+ 3600,
268
+ "HS256",
269
+ ) as Extract<FunctionStageConfig, { type: "JwtSign" }>;
270
+ expect(stage.type).toBe("JwtSign");
271
+ expect(stage.claims).toEqual({ sub: "{{user_id}}", role: "admin" });
272
+ expect(stage.secret).toBe("{{env.JWT_SECRET}}");
273
+ expect(stage.expires_in_secs).toBe(3600);
274
+ expect(stage.algorithm).toBe("HS256");
275
+ expect(stage.output_field).toBe("token");
276
+ });
277
+
278
+ it("leaves algorithm and expires_in_secs undefined when omitted", () => {
279
+ const stage = Stage.jwtSign(
280
+ { sub: "u" },
281
+ "{{env.JWT_SECRET}}",
282
+ "t",
283
+ ) as Extract<FunctionStageConfig, { type: "JwtSign" }>;
284
+ expect(stage.algorithm).toBeUndefined();
285
+ expect(stage.expires_in_secs).toBeUndefined();
286
+ });
287
+ });
288
+
289
+ describe("Stage.jwtVerify", () => {
290
+ it("produces a JwtVerify stage wiring token_field and output_field", () => {
291
+ const stage = Stage.jwtVerify(
292
+ "auth_token",
293
+ "{{env.JWT_SECRET}}",
294
+ "claims",
295
+ "HS512",
296
+ ) as Extract<FunctionStageConfig, { type: "JwtVerify" }>;
297
+ expect(stage.type).toBe("JwtVerify");
298
+ expect(stage.token_field).toBe("auth_token");
299
+ expect(stage.secret).toBe("{{env.JWT_SECRET}}");
300
+ expect(stage.algorithm).toBe("HS512");
301
+ expect(stage.output_field).toBe("claims");
302
+ });
303
+ });
304
+
305
+ describe("JWT stages JSON wire format", () => {
306
+ it("JwtSign round-trips through JSON unchanged", () => {
307
+ const stage = Stage.jwtSign(
308
+ { sub: "user-1" },
309
+ "{{env.JWT_SECRET}}",
310
+ "token",
311
+ 3600,
312
+ "HS256",
313
+ );
314
+ const wire = JSON.parse(JSON.stringify(stage));
315
+ expect(wire).toEqual({
316
+ type: "JwtSign",
317
+ claims: { sub: "user-1" },
318
+ secret: "{{env.JWT_SECRET}}",
319
+ algorithm: "HS256",
320
+ expires_in_secs: 3600,
321
+ output_field: "token",
322
+ });
323
+ });
324
+
325
+ it("JwtVerify round-trips through JSON unchanged", () => {
326
+ const stage = Stage.jwtVerify(
327
+ "token",
328
+ "{{env.JWT_SECRET}}",
329
+ "claims",
330
+ "HS256",
331
+ );
332
+ const wire = JSON.parse(JSON.stringify(stage));
333
+ expect(wire).toEqual({
334
+ type: "JwtVerify",
335
+ token_field: "token",
336
+ secret: "{{env.JWT_SECRET}}",
337
+ algorithm: "HS256",
338
+ output_field: "claims",
339
+ });
340
+ });
341
+ });
342
+
343
+ // ============================================================================
344
+ // EmailSend (ekoDB >= 0.42.0)
345
+ // ============================================================================
346
+
347
+ describe("Stage.emailSend", () => {
348
+ it("produces a SendGrid EmailSend stage with full payload", () => {
349
+ const stage = Stage.emailSend(
350
+ "alice@example.com",
351
+ "Welcome",
352
+ "<p>Hi Alice</p>",
353
+ "bot@example.com",
354
+ "{{env.SENDGRID_API_KEY}}",
355
+ {
356
+ reply_to: "support@example.com",
357
+ provider: "sendgrid",
358
+ html: true,
359
+ output_field: "send_result",
360
+ },
361
+ ) as Extract<FunctionStageConfig, { type: "EmailSend" }>;
362
+ expect(stage.type).toBe("EmailSend");
363
+ expect(stage.to).toBe("alice@example.com");
364
+ expect(stage.subject).toBe("Welcome");
365
+ expect(stage.body).toBe("<p>Hi Alice</p>");
366
+ expect(stage.from).toBe("bot@example.com");
367
+ expect(stage.reply_to).toBe("support@example.com");
368
+ expect(stage.api_key).toBe("{{env.SENDGRID_API_KEY}}");
369
+ expect(stage.provider).toBe("sendgrid");
370
+ expect(stage.html).toBe(true);
371
+ expect(stage.output_field).toBe("send_result");
372
+ });
373
+
374
+ it("leaves optional fields undefined when omitted", () => {
375
+ const stage = Stage.emailSend(
376
+ "x@example.com",
377
+ "s",
378
+ "b",
379
+ "f@example.com",
380
+ "k",
381
+ ) as Extract<FunctionStageConfig, { type: "EmailSend" }>;
382
+ expect(stage.reply_to).toBeUndefined();
383
+ expect(stage.provider).toBeUndefined();
384
+ expect(stage.html).toBeUndefined();
385
+ expect(stage.output_field).toBeUndefined();
386
+ });
387
+ });
388
+
257
389
  // ============================================================================
258
390
  // Error Handling & Control Flow: TryCatch, Parallel, Sleep (ekoDB >= 0.42.0)
259
391
  // ============================================================================
@@ -442,3 +574,153 @@ describe("New stages JSON wire format", () => {
442
574
  });
443
575
  });
444
576
  });
577
+
578
+ describe("Crypto and concurrency stages", () => {
579
+ it("hmacSign builds a stage with explicit algorithm and encoding", () => {
580
+ const s = Stage.hmacSign("{{payload}}", "{{env.KEY}}", "mac", {
581
+ algorithm: "sha256",
582
+ encoding: "hex",
583
+ });
584
+ expect(s).toEqual({
585
+ type: "HmacSign",
586
+ input: "{{payload}}",
587
+ secret: "{{env.KEY}}",
588
+ algorithm: "sha256",
589
+ output_field: "mac",
590
+ encoding: "hex",
591
+ });
592
+ });
593
+
594
+ it("hmacVerify wires all fields", () => {
595
+ const s = Stage.hmacVerify("{{p}}", "{{m}}", "{{env.K}}", "ok");
596
+ expect(s).toMatchObject({
597
+ type: "HmacVerify",
598
+ input: "{{p}}",
599
+ provided_mac: "{{m}}",
600
+ secret: "{{env.K}}",
601
+ output_field: "ok",
602
+ });
603
+ });
604
+
605
+ it("aesEncrypt and aesDecrypt build matching envelope contracts", () => {
606
+ const enc = Stage.aesEncrypt(
607
+ "{{plain}}",
608
+ "{{env.DATA_KEY}}",
609
+ "envelope",
610
+ "hex",
611
+ );
612
+ expect(enc).toMatchObject({
613
+ type: "AesEncrypt",
614
+ plaintext: "{{plain}}",
615
+ key: "{{env.DATA_KEY}}",
616
+ key_encoding: "hex",
617
+ output_field: "envelope",
618
+ });
619
+ const dec = Stage.aesDecrypt(
620
+ "envelope",
621
+ "{{env.DATA_KEY}}",
622
+ "plain",
623
+ "hex",
624
+ );
625
+ expect(dec).toMatchObject({
626
+ type: "AesDecrypt",
627
+ ciphertext_field: "envelope",
628
+ output_field: "plain",
629
+ });
630
+ });
631
+
632
+ it("uuidGenerate is a single-field stage", () => {
633
+ expect(Stage.uuidGenerate("id")).toEqual({
634
+ type: "UuidGenerate",
635
+ output_field: "id",
636
+ });
637
+ });
638
+
639
+ it("totpGenerate and totpVerify build with all options", () => {
640
+ const gen = Stage.totpGenerate("{{env.TOTP}}", "code", {
641
+ digits: 6,
642
+ period: 30,
643
+ algorithm: "sha1",
644
+ });
645
+ expect(gen.type).toBe("TotpGenerate");
646
+ const ver = Stage.totpVerify("{{user_code}}", "{{env.TOTP}}", "ok", {
647
+ skew: 1,
648
+ });
649
+ expect(ver.type).toBe("TotpVerify");
650
+ });
651
+
652
+ it("base64Encode/Decode and hexEncode/Decode build correctly", () => {
653
+ expect(Stage.base64Encode("{{x}}", "b", true)).toMatchObject({
654
+ type: "Base64Encode",
655
+ url_safe: true,
656
+ });
657
+ expect(Stage.base64Decode("{{b}}", "x")).toMatchObject({
658
+ type: "Base64Decode",
659
+ });
660
+ expect(Stage.hexEncode("{{x}}", "h")).toEqual({
661
+ type: "HexEncode",
662
+ input: "{{x}}",
663
+ output_field: "h",
664
+ });
665
+ expect(Stage.hexDecode("{{h}}", "x")).toEqual({
666
+ type: "HexDecode",
667
+ input: "{{h}}",
668
+ output_field: "x",
669
+ });
670
+ });
671
+
672
+ it("slugify builds a stage", () => {
673
+ expect(Stage.slugify("{{title}}", "slug")).toEqual({
674
+ type: "Slugify",
675
+ input: "{{title}}",
676
+ output_field: "slug",
677
+ });
678
+ });
679
+
680
+ it("idempotencyClaim, rateLimit, lockAcquire, lockRelease build correctly", () => {
681
+ expect(Stage.idempotencyClaim("{{ikey}}", 3600, "claim")).toEqual({
682
+ type: "IdempotencyClaim",
683
+ key: "{{ikey}}",
684
+ ttl_secs: 3600,
685
+ output_field: "claim",
686
+ });
687
+ expect(Stage.rateLimit("{{user}}", 100, 60, "rl", "skip")).toMatchObject({
688
+ type: "RateLimit",
689
+ limit: 100,
690
+ window_secs: 60,
691
+ on_exceed: "skip",
692
+ });
693
+ expect(Stage.lockAcquire("{{r}}", 30, "lock")).toEqual({
694
+ type: "LockAcquire",
695
+ key: "{{r}}",
696
+ ttl_secs: 30,
697
+ output_field: "lock",
698
+ });
699
+ expect(Stage.lockRelease("{{r}}", "{{lock.token}}", "rel")).toEqual({
700
+ type: "LockRelease",
701
+ key: "{{r}}",
702
+ token: "{{lock.token}}",
703
+ output_field: "rel",
704
+ });
705
+ });
706
+
707
+ it("crypto + concurrency stages round-trip through JSON unchanged", () => {
708
+ const stages = [
709
+ Stage.hmacSign("a", "k", "m", { algorithm: "sha256", encoding: "hex" }),
710
+ Stage.aesEncrypt("p", "k", "e", "hex"),
711
+ Stage.uuidGenerate("id"),
712
+ Stage.totpGenerate("s", "c", {
713
+ digits: 6,
714
+ period: 30,
715
+ algorithm: "sha1",
716
+ }),
717
+ Stage.idempotencyClaim("k", 60, "f"),
718
+ Stage.rateLimit("k", 5, 60, "rl"),
719
+ Stage.lockAcquire("r", 60, "l"),
720
+ ];
721
+ for (const s of stages) {
722
+ const wire = JSON.parse(JSON.stringify(s));
723
+ expect(wire.type).toBe(s.type);
724
+ }
725
+ });
726
+ });