@requence/service 1.0.0-alpha.22 → 1.0.0-alpha.24

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @requence/service
2
2
 
3
+ ## 1.0.0-alpha.24
4
+
5
+ ### Patch Changes
6
+
7
+ - f2a63fa: added missing type
8
+
9
+ ## 1.0.0-alpha.23
10
+
11
+ ### Patch Changes
12
+
13
+ - 13c28db: backchannel
14
+
3
15
  ## 1.0.0-alpha.22
4
16
 
5
17
  ### Patch Changes
package/build/index.js CHANGED
@@ -88,9 +88,15 @@ function mapData(data, visitors) {
88
88
  if (typeof data2 === "symbol") {
89
89
  return notUndefined(visitors.Symbol?.(data2, path), data2);
90
90
  }
91
+ if (typeof data2 === "function") {
92
+ return notUndefined(visitors.Function?.(data2, path), data2);
93
+ }
91
94
  if (isRecord(data2)) {
92
95
  return resolve(visitors.Object?.(structuredClone(data2), path), (resolvedData) => {
93
96
  const nextData = resolvedData ?? data2;
97
+ if (!isRecord(nextData)) {
98
+ return nextData;
99
+ }
94
100
  const entries = Object.entries(nextData).map(([key, value]) => [
95
101
  key,
96
102
  traverseData(value, [...path, key])
@@ -119,6 +125,34 @@ mapData.abort = () => {
119
125
  throw new AbortMapData;
120
126
  };
121
127
 
128
+ // ../helpers/src/utils/requenceCallback.ts
129
+ import { z as z2 } from "zod/v4";
130
+ var callbackObjectSchema = z2.object({
131
+ sourceId: z2.string(),
132
+ outputName: z2.string().nullable(),
133
+ propertyPath: z2.array(z2.union([z2.string(), z2.int()])),
134
+ callbackId: z2.uuid(),
135
+ __REQUENCE_type: z2.literal("RequenceCallback")
136
+ });
137
+ function isCallbackObject(data) {
138
+ return callbackObjectSchema.safeParse(data).success;
139
+ }
140
+ function createCallback(callback) {
141
+ let resolvePromise;
142
+ const promise = new Promise((resolve2) => {
143
+ resolvePromise = resolve2;
144
+ });
145
+ const executor = (arg) => {
146
+ resolvePromise(arg);
147
+ callback?.(arg);
148
+ };
149
+ return Object.assign(executor, {
150
+ response() {
151
+ return promise;
152
+ }
153
+ });
154
+ }
155
+
122
156
  // ../helpers/src/files/mapOutput.ts
123
157
  async function upload(uploadUrl, body, signal, contentType) {
124
158
  const uploadResponse = await fetch(uploadUrl, {
@@ -346,11 +380,11 @@ class RequenceStream {
346
380
  }
347
381
 
348
382
  // ../helpers/src/files/streamObject.ts
349
- import { z as z2 } from "zod/v4";
350
- var streamObjectSchema = z2.object({
351
- mime: z2.string(),
352
- url: z2.string().url(),
353
- __REQUENCE_type: z2.literal("RequenceStream")
383
+ import { z as z3 } from "zod/v4";
384
+ var streamObjectSchema = z3.object({
385
+ mime: z3.string(),
386
+ url: z3.string().url(),
387
+ __REQUENCE_type: z3.literal("RequenceStream")
354
388
  });
355
389
  function isStreamObject(data) {
356
390
  return streamObjectSchema.safeParse(data).success;
@@ -363,12 +397,12 @@ function toStreamObject(streamInfo) {
363
397
  }
364
398
 
365
399
  // ../helpers/src/files/fileObject.ts
366
- import { z as z3 } from "zod/v4";
367
- var fileObjectSchema = z3.object({
368
- size: z3.number().int().positive(),
369
- mime: z3.string(),
370
- url: z3.string().url(),
371
- __REQUENCE_type: z3.literal("RequenceFile")
400
+ import { z as z4 } from "zod/v4";
401
+ var fileObjectSchema = z4.object({
402
+ size: z4.int().positive(),
403
+ mime: z4.string(),
404
+ url: z4.url(),
405
+ __REQUENCE_type: z4.literal("RequenceFile")
372
406
  });
373
407
  function toFileObject(fileInfo) {
374
408
  return {
@@ -381,7 +415,7 @@ function isFileObject(data) {
381
415
  }
382
416
 
383
417
  // ../helpers/src/utils/resolveRequenceTypes.ts
384
- function resolveRequenceTypes(data) {
418
+ function resolveRequenceTypes(data, onCallback) {
385
419
  return mapData(data, {
386
420
  Object(obj) {
387
421
  if (isFileObject(obj)) {
@@ -390,18 +424,23 @@ function resolveRequenceTypes(data) {
390
424
  if (isStreamObject(obj)) {
391
425
  return RequenceStream.fromStreamObject(obj);
392
426
  }
427
+ if (isCallbackObject(obj)) {
428
+ return (arg) => {
429
+ onCallback({ ...obj, arg });
430
+ };
431
+ }
393
432
  }
394
433
  });
395
434
  }
396
435
 
397
436
  // src/index.ts
398
- import { z as z5 } from "zod/v4";
437
+ import { z as z6 } from "zod/v4";
399
438
 
400
439
  // src/createAmqpConnection.ts
401
440
  import {
402
441
  connect
403
442
  } from "amqp-connection-manager";
404
- import { z as z4 } from "zod/v4";
443
+ import { z as z5 } from "zod/v4";
405
444
 
406
445
  // src/helpers.ts
407
446
  class RetryError extends Error {
@@ -421,54 +460,131 @@ var OUTPUT_VALUE = Symbol("output_value");
421
460
  function isOutputValue(value) {
422
461
  return value !== null && typeof value === "object" && OUTPUT_VALUE in value;
423
462
  }
424
- async function unwrapResult(result, options, onDone) {
425
- if (isOutputValue(result)) {
463
+ function processData(params) {
464
+ return mapOutput({
465
+ value: mapData(params.data, {
466
+ Function(data, path) {
467
+ return {
468
+ ...params.onCallback(data),
469
+ outputName: params.outputName,
470
+ propertyPath: path,
471
+ __REQUENCE_type: "RequenceCallback"
472
+ };
473
+ }
474
+ }),
475
+ options: params.options,
476
+ onDone: params.onDone
477
+ });
478
+ }
479
+ async function unwrapResult(params) {
480
+ if (isOutputValue(params.result)) {
426
481
  return {
427
- value: await mapOutput({ value: result.value, options, onDone }),
428
- outputName: result[OUTPUT_VALUE]
482
+ value: await processData({
483
+ outputName: params.result[OUTPUT_VALUE],
484
+ data: params.result.value,
485
+ options: params.options,
486
+ onDone: params.onDone,
487
+ onCallback: params.onCallback
488
+ }),
489
+ outputName: params.result[OUTPUT_VALUE]
429
490
  };
430
491
  }
431
492
  return {
432
- value: await mapOutput({ value: result, options, onDone }),
493
+ value: await processData({
494
+ outputName: null,
495
+ data: params.result,
496
+ options: params.options,
497
+ onDone: params.onDone,
498
+ onCallback: params.onCallback
499
+ }),
433
500
  outputName: null
434
501
  };
435
502
  }
503
+ function isAsyncIterable(iterable) {
504
+ return typeof iterable[Symbol.asyncIterator] === "function";
505
+ }
436
506
  function createContextHelper(options) {
437
- return {
438
- taskId: options.taskId,
439
- input: resolveRequenceTypes(options.input),
440
- configuration: resolveRequenceTypes(options.configuration),
441
- toOutput(name, value) {
442
- return {
443
- [OUTPUT_VALUE]: name,
444
- value
445
- };
446
- },
447
- defer: options.onDefer,
448
- debug: {
449
- log(...data) {
450
- options.onLog("log", data);
507
+ const terminatePromise = createRemotePromise();
508
+ return [
509
+ {
510
+ taskId: options.taskId,
511
+ input: resolveRequenceTypes(options.input, options.onCallback),
512
+ configuration: resolveRequenceTypes(options.configuration, () => {
513
+ throw new Error("configuration may not have callbacks");
514
+ }),
515
+ toOutput(name, value) {
516
+ return {
517
+ [OUTPUT_VALUE]: name,
518
+ value
519
+ };
451
520
  },
452
- info(...data) {
453
- options.onLog("info", data);
521
+ defer: options.onDefer,
522
+ debug: {
523
+ log(...data) {
524
+ options.onLog("log", data);
525
+ },
526
+ info(...data) {
527
+ options.onLog("info", data);
528
+ },
529
+ warn(...data) {
530
+ options.onLog("warn", data);
531
+ },
532
+ error(...data) {
533
+ options.onLog("error", data);
534
+ }
454
535
  },
455
- warn(...data) {
456
- options.onLog("warn", data);
536
+ retry(delay) {
537
+ throw new RetryError(delay);
457
538
  },
458
- error(...data) {
459
- options.onLog("error", data);
539
+ abort(reason) {
540
+ throw new AbortError(reason);
541
+ },
542
+ skip() {
543
+ throw new SkipError;
544
+ },
545
+ terminated: terminatePromise.promise,
546
+ wrapIterable: function(iterable) {
547
+ if (isAsyncIterable(iterable)) {
548
+ return {
549
+ [Symbol.asyncIterator]() {
550
+ const iterator = iterable[Symbol.asyncIterator]();
551
+ return {
552
+ async next() {
553
+ const result = await Promise.race([
554
+ terminatePromise.promise,
555
+ iterator.next()
556
+ ]);
557
+ if (typeof result === "string") {
558
+ return { done: true, value: undefined };
559
+ }
560
+ return result;
561
+ }
562
+ };
563
+ }
564
+ };
565
+ }
566
+ return {
567
+ [Symbol.iterator]() {
568
+ const iterator = iterable[Symbol.iterator]();
569
+ let terminated = false;
570
+ terminatePromise.then(() => {
571
+ terminated = true;
572
+ });
573
+ return {
574
+ next() {
575
+ if (terminated) {
576
+ return { done: true, value: undefined };
577
+ }
578
+ const result = iterator.next();
579
+ return result;
580
+ }
581
+ };
582
+ }
583
+ };
460
584
  }
461
585
  },
462
- retry(delay) {
463
- throw new RetryError(delay);
464
- },
465
- abort(reason) {
466
- throw new AbortError(reason);
467
- },
468
- skip() {
469
- throw new SkipError;
470
- }
471
- };
586
+ terminatePromise
587
+ ];
472
588
  }
473
589
 
474
590
  // src/mapInput.ts
@@ -486,33 +602,50 @@ function mapInput(data) {
486
602
  }
487
603
 
488
604
  // src/createAmqpConnection.ts
489
- var messageKeySchema = z4.string().transform((str) => deobfuscate(str)).pipe(z4.tuple([z4.uuid(), z4.uuid()]).rest(z4.string())).transform((arr) => ({
605
+ var messageKeySchema = z5.string().transform((str) => deobfuscate(str)).pipe(z5.tuple([z5.uuid(), z5.uuid()]).rest(z5.string())).transform((arr) => ({
490
606
  taskId: arr[0],
491
607
  messageId: arr[1],
492
608
  uploadUrl: arr[2] || undefined,
493
609
  streamUrl: arr[3] || undefined
494
610
  }));
495
- var headerSchema = z4.object({
496
- properties: z4.object({
497
- correlationId: z4.uuid(),
498
- headers: z4.object({
499
- version: z4.string(),
500
- taskId: z4.uuid()
611
+ var headerSchema = z5.object({
612
+ properties: z5.object({
613
+ correlationId: z5.uuid(),
614
+ headers: z5.object({
615
+ taskId: z5.uuid()
501
616
  })
502
617
  })
503
618
  }).transform((msg) => ({
504
- version: msg.properties.headers.version,
505
619
  messageId: msg.properties.correlationId,
506
620
  taskId: msg.properties.headers.taskId
507
621
  }));
508
- var payloadSchema = z4.object({ content: z4.instanceof(Buffer) }).transform((msg) => JSON.parse(msg.content.toString())).pipe(z4.object({
509
- input: z4.unknown(),
510
- configuration: z4.unknown(),
511
- options: z4.object({
512
- uploadUrl: z4.url().optional(),
513
- streamUrl: z4.url().optional()
622
+ var payloadSchema = z5.object({ content: z5.instanceof(Buffer) }).transform((msg) => JSON.parse(msg.content.toString())).pipe(z5.object({
623
+ input: z5.unknown(),
624
+ configuration: z5.unknown(),
625
+ options: z5.object({
626
+ uploadUrl: z5.url().optional(),
627
+ streamUrl: z5.url().optional()
628
+ })
629
+ }));
630
+ var backchannelMessageSchema = z5.object({
631
+ content: z5.instanceof(Buffer).transform((content) => JSON.parse(content.toString())).pipe(z5.discriminatedUnion("type", [
632
+ z5.object({
633
+ type: z5.literal("stop")
634
+ }),
635
+ z5.object({
636
+ type: z5.literal("callback"),
637
+ callbackId: z5.uuid(),
638
+ arg: z5.any()
639
+ })
640
+ ])),
641
+ properties: z5.object({
642
+ correlationId: z5.string()
514
643
  })
644
+ }).transform((message) => ({
645
+ payload: message.content,
646
+ messageId: message.properties.correlationId
515
647
  }));
648
+ var abortSymbol = Symbol();
516
649
  function isMessageHandlerGenerator(handler) {
517
650
  return handler.constructor.name === "GeneratorFunction" || handler.constructor.name === "AsyncGeneratorFunction";
518
651
  }
@@ -548,11 +681,17 @@ function createConnection({
548
681
  connection.addListener("connect", handleConnect);
549
682
  connection.addListener("connectFailed", handleConnectFailed);
550
683
  });
684
+ const connectionId = crypto.randomUUID();
685
+ const backchannelQueue = `${queue}-backchannel-${connectionId}`;
551
686
  const channel = connection.createChannel({
552
687
  async setup(channel2) {
553
688
  await channel2.checkExchange(exchange);
554
689
  await channel2.checkExchange(`${exchange}-retry`);
555
690
  await channel2.checkQueue(queue);
691
+ await channel2.assertQueue(backchannelQueue, {
692
+ exclusive: true
693
+ });
694
+ await channel2.bindQueue(backchannelQueue, "service", backchannelQueue);
556
695
  await channel2.prefetch(prefetch);
557
696
  }
558
697
  });
@@ -570,13 +709,36 @@ function createConnection({
570
709
  priority,
571
710
  headers: {
572
711
  outputName: outputName ?? null,
712
+ connectionId,
573
713
  ...headers
574
714
  }
575
715
  });
576
716
  };
717
+ const generatorTerminators = new Map;
718
+ const callbacks = new Map;
719
+ channel.consume(backchannelQueue, async (message) => {
720
+ const result = backchannelMessageSchema.safeParse(message);
721
+ if (!result.success) {
722
+ return;
723
+ }
724
+ const backchannelMessage = result.data;
725
+ switch (backchannelMessage.payload.type) {
726
+ case "stop": {
727
+ generatorTerminators.get(backchannelMessage.messageId)?.("task stopped");
728
+ break;
729
+ }
730
+ case "callback": {
731
+ callbacks.get(backchannelMessage.payload.callbackId)?.(backchannelMessage.payload.arg);
732
+ break;
733
+ }
734
+ }
735
+ }, {
736
+ noAck: true
737
+ });
577
738
  channel.consume(queue, async (message) => {
578
739
  let publishIndex = 0;
579
740
  const donePromises = [];
741
+ const registeredCallbackIds = new Set;
580
742
  let removeActiveMessage = () => {};
581
743
  let acked = false;
582
744
  const ack = () => {
@@ -585,14 +747,14 @@ function createConnection({
585
747
  channel.ack(message);
586
748
  }
587
749
  };
750
+ const { messageId, taskId } = headerSchema.parse(message);
588
751
  try {
589
752
  let deferred = false;
590
753
  let deferredReason = null;
591
- const { messageId, taskId } = headerSchema.parse(message);
592
754
  activeMessages.add(messageId);
593
755
  removeActiveMessage = () => activeMessages.delete(messageId);
594
756
  const payload = payloadSchema.parse(message);
595
- const extendedContext = createContextHelper({
757
+ const [extendedContext, terminatePromise] = createContextHelper({
596
758
  taskId,
597
759
  input: mapInput(payload.input),
598
760
  configuration: mapInput(payload.configuration),
@@ -612,12 +774,45 @@ function createConnection({
612
774
  deferredReason = reason || null;
613
775
  ack();
614
776
  return obfuscate(taskId, messageId, payload.options.uploadUrl ?? "", payload.options.streamUrl ?? "");
777
+ },
778
+ onCallback(params) {
779
+ publish({
780
+ index: 0,
781
+ taskId,
782
+ messageId,
783
+ data: params.arg,
784
+ outputName: params.outputName,
785
+ connectionId: params.sourceId,
786
+ callbackId: params.callbackId,
787
+ propertyPath: params.propertyPath
788
+ });
615
789
  }
616
790
  });
791
+ publish({
792
+ index: publishIndex++,
793
+ messageId,
794
+ taskId,
795
+ priority: message.properties.priority,
796
+ begin: true
797
+ });
617
798
  if (isMessageHandlerGenerator(messageHandler)) {
618
799
  const generator = messageHandler(extendedContext);
800
+ const terminationPromise = createRemotePromise();
801
+ generatorTerminators.set(messageId, (reason) => {
802
+ generator.return();
803
+ terminationPromise.resolve(abortSymbol);
804
+ terminatePromise.resolve(reason);
805
+ ack();
806
+ });
619
807
  while (true) {
620
- const { value: result, done } = await generator.next();
808
+ const next = await Promise.race([
809
+ terminationPromise,
810
+ generator.next()
811
+ ]);
812
+ if (next === abortSymbol) {
813
+ break;
814
+ }
815
+ const { value: result, done } = next;
621
816
  ack();
622
817
  if (deferred) {
623
818
  deferred = false;
@@ -625,7 +820,20 @@ function createConnection({
625
820
  }
626
821
  const donePromise = createRemotePromise();
627
822
  donePromises.push(donePromise);
628
- const { value, outputName } = await unwrapResult(result, payload.options, donePromise.resolve);
823
+ const { value, outputName } = await unwrapResult({
824
+ result,
825
+ options: payload.options,
826
+ onDone: donePromise.resolve,
827
+ onCallback: (callback) => {
828
+ const callbackId = crypto.randomUUID();
829
+ registeredCallbackIds.add(callbackId);
830
+ callbacks.set(callbackId, callback);
831
+ return {
832
+ sourceId: connectionId,
833
+ callbackId
834
+ };
835
+ }
836
+ });
629
837
  if (!done || typeof value !== "undefined") {
630
838
  publish({
631
839
  index: publishIndex++,
@@ -645,7 +853,14 @@ function createConnection({
645
853
  ack();
646
854
  const donePromise = createRemotePromise();
647
855
  donePromises.push(donePromise);
648
- const { value, outputName } = await unwrapResult(result, payload.options, donePromise.resolve);
856
+ const { value, outputName } = await unwrapResult({
857
+ result,
858
+ options: payload.options,
859
+ onDone: donePromise.resolve,
860
+ onCallback: () => {
861
+ throw new Error("callbacks can only be used in generator functions");
862
+ }
863
+ });
649
864
  if (deferred && value) {
650
865
  console.warn("returning a value in a deferred message resets the deferred status");
651
866
  deferred = false;
@@ -695,9 +910,10 @@ function createConnection({
695
910
  ...message.properties,
696
911
  expiration: error.delay || 1000
697
912
  };
698
- channel.publish(`${exchange}-retry`, "retry", message.content, properties);
913
+ channel.publish(`${exchange}-retry`, version, message.content, properties);
699
914
  ack();
700
915
  }
916
+ return;
701
917
  }
702
918
  if (error instanceof AbortError && error.message.length > 0) {
703
919
  if (assertNack("data already send, cannot abort")) {
@@ -724,6 +940,7 @@ function createConnection({
724
940
  ...message.properties,
725
941
  headers: {
726
942
  ...message.properties.headers,
943
+ version,
727
944
  exception: true,
728
945
  index: publishIndex++,
729
946
  "error-message": error.message || "error in service handler"
@@ -737,6 +954,8 @@ function createConnection({
737
954
  channel.nack(message, false, false);
738
955
  }
739
956
  } finally {
957
+ generatorTerminators.delete(messageId);
958
+ registeredCallbackIds.forEach((id) => callbacks.delete(id));
740
959
  removeActiveMessage();
741
960
  }
742
961
  });
@@ -754,6 +973,7 @@ function createConnection({
754
973
  throw new Error("Cannot send deferred message while callback handler is still running");
755
974
  }
756
975
  };
976
+ const registeredCallbackIds = new Set;
757
977
  const donePromises = [];
758
978
  await actor({
759
979
  async sendToOutput(outputName, data) {
@@ -764,10 +984,20 @@ function createConnection({
764
984
  index: publishIndex++,
765
985
  messageId,
766
986
  taskId,
767
- data: await mapOutput({
768
- value: data,
987
+ data: await processData({
988
+ outputName,
989
+ data,
769
990
  options,
770
- onDone: donePromise.resolve
991
+ onDone: donePromise.resolve,
992
+ onCallback: (callback) => {
993
+ const callbackId = crypto.randomUUID();
994
+ registeredCallbackIds.add(callbackId);
995
+ callbacks.set(callbackId, callback);
996
+ return {
997
+ sourceId: connectionId,
998
+ callbackId
999
+ };
1000
+ }
771
1001
  }),
772
1002
  outputName
773
1003
  });
@@ -780,10 +1010,20 @@ function createConnection({
780
1010
  index: publishIndex++,
781
1011
  messageId,
782
1012
  taskId,
783
- data: await mapOutput({
784
- value: data,
1013
+ data: await processData({
1014
+ outputName: null,
1015
+ data,
785
1016
  options,
786
- onDone: donePromise.resolve
1017
+ onDone: donePromise.resolve,
1018
+ onCallback: (callback) => {
1019
+ const callbackId = crypto.randomUUID();
1020
+ registeredCallbackIds.add(callbackId);
1021
+ callbacks.set(callbackId, callback);
1022
+ return {
1023
+ sourceId: connectionId,
1024
+ callbackId
1025
+ };
1026
+ }
787
1027
  })
788
1028
  });
789
1029
  },
@@ -805,6 +1045,7 @@ function createConnection({
805
1045
  });
806
1046
  setImmediate(async () => {
807
1047
  await Promise.allSettled(donePromises);
1048
+ registeredCallbackIds.forEach((id) => callbacks.delete(id));
808
1049
  publish({
809
1050
  index: publishIndex++,
810
1051
  messageId,
@@ -818,16 +1059,16 @@ function createConnection({
818
1059
  }
819
1060
 
820
1061
  // src/index.ts
821
- var connectionOptionsSchema = z5.object({
822
- url: z5.union([
823
- z5.string().regex(/^amqps?:\/\/(.+):(.+)@(.+)$/).transform((str) => new URL(str)),
824
- z5.instanceof(URL).refine((url) => url.protocol.match(/^amqps?:/), "Wrong protocol")
1062
+ var connectionOptionsSchema = z6.object({
1063
+ url: z6.union([
1064
+ z6.string().regex(/^amqps?:\/\/(.+):(.+)@(.+)$/).transform((str) => new URL(str)),
1065
+ z6.instanceof(URL).refine((url) => url.protocol.match(/^amqps?:/), "Wrong protocol")
825
1066
  ]),
826
- version: z5.string(),
827
- silent: z5.boolean().optional(),
828
- prefetch: z5.number().int().optional(),
829
- connectionTimeout: z5.number().positive().default(3000),
830
- connectionOptions: z5.any()
1067
+ version: z6.string(),
1068
+ silent: z6.boolean().optional(),
1069
+ prefetch: z6.number().int().optional(),
1070
+ connectionTimeout: z6.number().positive().default(3000),
1071
+ connectionOptions: z6.any()
831
1072
  });
832
1073
  function createService(optionsOrVersion, optionalMessageHandler) {
833
1074
  let options;
@@ -861,9 +1102,10 @@ function createService(optionsOrVersion, optionalMessageHandler) {
861
1102
  }
862
1103
  export {
863
1104
  createService,
1105
+ createCallback,
864
1106
  RequenceStream,
865
1107
  RequenceFile
866
1108
  };
867
1109
 
868
- //# debugId=08A19A699A5C700C64756E2164756E21
1110
+ //# debugId=DB60EAB4653D0F1964756E2164756E21
869
1111
  //# sourceMappingURL=index.js.map