@cadenza.io/core 3.25.1 → 3.26.1

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/index.js CHANGED
@@ -1280,6 +1280,7 @@ var SignalBroker = class _SignalBroker {
1280
1280
  // TODO: Signals should be a class with a the observers, registered flag and other data.
1281
1281
  this.signalObservers = /* @__PURE__ */ new Map();
1282
1282
  this.emittedSignalsRegistry = /* @__PURE__ */ new Set();
1283
+ this.signalMetadataRegistry = /* @__PURE__ */ new Map();
1283
1284
  // ── Flush Strategy Management ───────────────────────────────────────
1284
1285
  this.flushStrategies = /* @__PURE__ */ new Map();
1285
1286
  this.strategyData = /* @__PURE__ */ new Map();
@@ -1312,6 +1313,47 @@ var SignalBroker = class _SignalBroker {
1312
1313
  setVerbose(value) {
1313
1314
  this.verbose = value;
1314
1315
  }
1316
+ resolveSignalMetadataKey(signal) {
1317
+ const normalizedSignal = typeof signal === "string" ? signal.trim() : "";
1318
+ if (!normalizedSignal) {
1319
+ return "";
1320
+ }
1321
+ return normalizedSignal.split(":")[0] ?? normalizedSignal;
1322
+ }
1323
+ normalizeSignalMetadata(metadata) {
1324
+ if (!metadata) {
1325
+ return void 0;
1326
+ }
1327
+ const deliveryMode = metadata.deliveryMode === "broadcast" ? "broadcast" : "single";
1328
+ const broadcastFilter = metadata.broadcastFilter && typeof metadata.broadcastFilter === "object" && !Array.isArray(metadata.broadcastFilter) ? merge_default({}, metadata.broadcastFilter) : null;
1329
+ return {
1330
+ deliveryMode,
1331
+ broadcastFilter
1332
+ };
1333
+ }
1334
+ setSignalMetadata(signal, metadata) {
1335
+ const metadataKey = this.resolveSignalMetadataKey(signal);
1336
+ if (!metadataKey) {
1337
+ return;
1338
+ }
1339
+ const normalized = this.normalizeSignalMetadata(metadata);
1340
+ if (!normalized) {
1341
+ this.signalMetadataRegistry.delete(metadataKey);
1342
+ return;
1343
+ }
1344
+ this.signalMetadataRegistry.set(metadataKey, normalized);
1345
+ }
1346
+ getSignalMetadata(signal) {
1347
+ const metadataKey = this.resolveSignalMetadataKey(signal);
1348
+ if (!metadataKey) {
1349
+ return void 0;
1350
+ }
1351
+ const metadata = this.signalMetadataRegistry.get(metadataKey);
1352
+ if (!metadata) {
1353
+ return void 0;
1354
+ }
1355
+ return merge_default({}, metadata);
1356
+ }
1315
1357
  // Dor debugging
1316
1358
  logMemoryFootprint(label = "current") {
1317
1359
  console.log(`[${label}] SignalBroker state sizes:`);
@@ -1396,7 +1438,8 @@ var SignalBroker = class _SignalBroker {
1396
1438
  const processedSignals = uniqueSignals.map((signal) => ({
1397
1439
  signal,
1398
1440
  data: {
1399
- registered: this.signalObservers.get(signal)?.registered ?? false
1441
+ registered: this.signalObservers.get(signal)?.registered ?? false,
1442
+ metadata: this.getSignalMetadata(signal) ?? null
1400
1443
  }
1401
1444
  }));
1402
1445
  return {
@@ -1746,10 +1789,11 @@ var SignalBroker = class _SignalBroker {
1746
1789
  execute(signal, context) {
1747
1790
  const isMeta = signal.includes("meta.");
1748
1791
  const isSubMeta = signal.includes("sub_meta.") || context.__isSubMeta;
1792
+ const isReceivedSignalTransmission = context.__receivedSignalTransmission === true;
1749
1793
  const isMetric = context.__signalEmission?.isMetric;
1794
+ const isNewTrace = !context.__signalEmission?.executionTraceId && !context.__metadata?.__executionTraceId && !context.__executionTraceId;
1750
1795
  const executionTraceId = context.__signalEmission?.executionTraceId ?? context.__metadata?.__executionTraceId ?? context.__executionTraceId ?? (0, import_uuid.v4)();
1751
1796
  if (!isSubMeta && (!isMeta || this.debug)) {
1752
- const isNewTrace = !context.__signalEmission?.executionTraceId && !context.__metadata?.__executionTraceId && !context.__executionTraceId;
1753
1797
  if (isNewTrace) {
1754
1798
  this.emit("sub_meta.signal_broker.new_trace", {
1755
1799
  data: {
@@ -1778,21 +1822,33 @@ var SignalBroker = class _SignalBroker {
1778
1822
  const signalParts = signal.split(":");
1779
1823
  const signalName = signalParts[0];
1780
1824
  const signalTag = signalParts.length > 1 ? signalParts[1] : null;
1825
+ const existingSignalEmission = context.__signalEmission && typeof context.__signalEmission === "object" ? context.__signalEmission : {};
1781
1826
  context.__signalEmission = {
1782
- ...context.__signalEmission ?? {},
1783
- uuid: (0, import_uuid.v4)(),
1784
- executionTraceId,
1785
- signalName,
1786
- signalTag,
1787
- emittedAt: formatTimestamp(emittedAt),
1788
- consumed: false,
1789
- consumedBy: null,
1790
- isMeta
1827
+ ...existingSignalEmission,
1828
+ fullSignalName: existingSignalEmission.fullSignalName ?? signal,
1829
+ uuid: existingSignalEmission.uuid ?? (0, import_uuid.v4)(),
1830
+ executionTraceId: existingSignalEmission.executionTraceId ?? executionTraceId,
1831
+ signalName: existingSignalEmission.signalName ?? signalName,
1832
+ signalTag: existingSignalEmission.signalTag ?? signalTag,
1833
+ emittedAt: existingSignalEmission.emittedAt ?? formatTimestamp(emittedAt),
1834
+ consumed: existingSignalEmission.consumed ?? false,
1835
+ consumedBy: existingSignalEmission.consumedBy ?? null,
1836
+ isMeta: existingSignalEmission.isMeta ?? isMeta,
1837
+ __traceCreatedBySignalBroker: existingSignalEmission.__traceCreatedBySignalBroker ?? isNewTrace
1791
1838
  };
1792
- this.emit("sub_meta.signal_broker.emitting_signal", { ...context });
1839
+ if (!isReceivedSignalTransmission) {
1840
+ if (isNewTrace) {
1841
+ context.__traceCreatedBySignalBroker = true;
1842
+ }
1843
+ this.emit("sub_meta.signal_broker.emitting_signal", { ...context });
1844
+ if (isNewTrace) {
1845
+ delete context.__traceCreatedBySignalBroker;
1846
+ }
1847
+ }
1793
1848
  } else if (isSubMeta) {
1794
1849
  context.__isSubMeta = true;
1795
1850
  }
1851
+ delete context.__receivedSignalTransmission;
1796
1852
  context.__metadata = {
1797
1853
  ...context.__metadata,
1798
1854
  __executionTraceId: executionTraceId
@@ -1856,8 +1912,11 @@ var SignalBroker = class _SignalBroker {
1856
1912
  * @param {string} signal - The name of the signal to be added.
1857
1913
  * @return {void} This method does not return any value.
1858
1914
  */
1859
- addSignal(signal) {
1915
+ addSignal(signal, metadata) {
1860
1916
  let _signal = signal;
1917
+ if (metadata) {
1918
+ this.setSignalMetadata(signal, metadata);
1919
+ }
1861
1920
  if (!this.signalObservers.has(_signal)) {
1862
1921
  this.validateSignalName(_signal);
1863
1922
  this.signalObservers.set(_signal, {
@@ -1887,11 +1946,14 @@ var SignalBroker = class _SignalBroker {
1887
1946
  * @param routineOrTask The observer.
1888
1947
  * @edge Duplicates ignored; supports wildcards for broad listening.
1889
1948
  */
1890
- observe(signal, routineOrTask) {
1891
- this.addSignal(signal);
1949
+ observe(signal, routineOrTask, metadata) {
1950
+ this.addSignal(signal, metadata);
1892
1951
  this.signalObservers.get(signal).tasks.add(routineOrTask);
1893
1952
  }
1894
- registerEmittedSignal(signal) {
1953
+ registerEmittedSignal(signal, metadata) {
1954
+ if (metadata) {
1955
+ this.setSignalMetadata(signal, metadata);
1956
+ }
1895
1957
  this.emittedSignalsRegistry.add(signal);
1896
1958
  }
1897
1959
  /**
@@ -1926,6 +1988,7 @@ var SignalBroker = class _SignalBroker {
1926
1988
  this.clearThrottleState();
1927
1989
  this.signalObservers.clear();
1928
1990
  this.emittedSignalsRegistry.clear();
1991
+ this.signalMetadataRegistry.clear();
1929
1992
  }
1930
1993
  shutdown() {
1931
1994
  this.reset();
@@ -2334,6 +2397,27 @@ var SignalEmitter = class {
2334
2397
  };
2335
2398
 
2336
2399
  // src/graph/execution/GraphNode.ts
2400
+ function normalizeGraphErrorMessage(error) {
2401
+ if (typeof error === "string") {
2402
+ return error;
2403
+ }
2404
+ if (error instanceof Error) {
2405
+ return error.message;
2406
+ }
2407
+ if (error && typeof error === "object") {
2408
+ const record = error;
2409
+ if (typeof record.__error === "string") {
2410
+ return record.__error;
2411
+ }
2412
+ if (typeof record.error === "string") {
2413
+ return record.error;
2414
+ }
2415
+ if (typeof record.message === "string") {
2416
+ return record.message;
2417
+ }
2418
+ }
2419
+ return String(error);
2420
+ }
2337
2421
  var GraphNode = class _GraphNode extends SignalEmitter {
2338
2422
  constructor(task, context, routineExecId, prevNodes = [], debug = false, verbose = false) {
2339
2423
  super(
@@ -2518,7 +2602,8 @@ var GraphNode = class _GraphNode extends SignalEmitter {
2518
2602
  {
2519
2603
  data: {
2520
2604
  taskExecutionId: this.id,
2521
- previousTaskExecutionId: node.id
2605
+ previousTaskExecutionId: node.id,
2606
+ executionTraceId: context.__metadata?.__executionTraceId ?? context.__executionTraceId ?? null
2522
2607
  },
2523
2608
  filter: {
2524
2609
  taskName: this.task.name,
@@ -2536,7 +2621,8 @@ var GraphNode = class _GraphNode extends SignalEmitter {
2536
2621
  {
2537
2622
  data: {
2538
2623
  taskExecutionId: this.id,
2539
- previousTaskExecutionId: context.__previousTaskExecutionId
2624
+ previousTaskExecutionId: context.__previousTaskExecutionId,
2625
+ executionTraceId: context.__metadata?.__executionTraceId ?? context.__executionTraceId ?? null
2540
2626
  },
2541
2627
  filter: {
2542
2628
  taskName: this.task.name,
@@ -2950,11 +3036,12 @@ var GraphNode = class _GraphNode extends SignalEmitter {
2950
3036
  * @return {void} This method does not return any value.
2951
3037
  */
2952
3038
  onError(error, errorData = {}) {
3039
+ const normalizedError = normalizeGraphErrorMessage(error);
2953
3040
  this.result = {
2954
3041
  ...this.context.getFullContext(),
2955
- __error: `Node error: ${error}`,
3042
+ __error: `Node error: ${normalizedError}`,
2956
3043
  __retries: this.retries,
2957
- error: `Node error: ${error}`,
3044
+ error: `Node error: ${normalizedError}`,
2958
3045
  errored: true,
2959
3046
  returnedValue: this.result,
2960
3047
  ...errorData
@@ -3529,8 +3616,16 @@ var GraphRunner = class extends SignalEmitter {
3529
3616
  const isNewTrace = !context.__routineExecId && !context.__metadata?.__executionTraceId && !context.__executionTraceId;
3530
3617
  const executionTraceId = context.__metadata?.__executionTraceId ?? context.__executionTraceId ?? (0, import_uuid5.v4)();
3531
3618
  context.__executionTraceId = executionTraceId;
3619
+ context.__traceCreatedByRunner = isNewTrace;
3620
+ const isNewRoutine = !context.__routineExecId;
3532
3621
  const routineExecId = context.__routineExecId ?? (0, import_uuid5.v4)();
3533
3622
  context.__routineExecId = routineExecId;
3623
+ const routineCreatedAt = formatTimestamp(Date.now());
3624
+ context.__routineCreatedByRunner = isNewRoutine;
3625
+ context.__routineName = routineName;
3626
+ context.__routineVersion = routineVersion;
3627
+ context.__routineCreatedAt = routineCreatedAt;
3628
+ context.__routineIsMeta = isMeta;
3534
3629
  Cadenza.applyRuntimeValidationScopesToContext(
3535
3630
  context,
3536
3631
  routineName,
@@ -3565,8 +3660,7 @@ var GraphRunner = class extends SignalEmitter {
3565
3660
  executionTraceId,
3566
3661
  context: ctx.getContext(),
3567
3662
  metaContext: ctx.getMetadata(),
3568
- previousRoutineExecution: context.__localRoutineExecId ?? context.__metadata?.__routineExecId ?? null,
3569
- created: formatTimestamp(Date.now())
3663
+ created: routineCreatedAt
3570
3664
  },
3571
3665
  __metadata: {
3572
3666
  __executionTraceId: executionTraceId
@@ -3799,6 +3893,7 @@ var Actor = class {
3799
3893
  this.stateByKey = /* @__PURE__ */ new Map();
3800
3894
  this.sessionByKey = /* @__PURE__ */ new Map();
3801
3895
  this.idempotencyByKey = /* @__PURE__ */ new Map();
3896
+ this.writeQueueByKey = /* @__PURE__ */ new Map();
3802
3897
  this.nextTaskBindingIndex = 0;
3803
3898
  if (!spec.name || typeof spec.name !== "string") {
3804
3899
  throw new Error("Actor name must be a non-empty string");
@@ -3832,7 +3927,9 @@ var Actor = class {
3832
3927
  bindingOptions.touchSession
3833
3928
  );
3834
3929
  const actorKey = this.resolveActorKey(normalizedInput, invocationOptions);
3835
- this.touchSession(actorKey, invocationOptions.touchSession, Date.now());
3930
+ const now2 = Date.now();
3931
+ this.pruneExpiredActorKeys(now2);
3932
+ this.touchSession(actorKey, invocationOptions.touchSession, now2);
3836
3933
  const runTask = async () => {
3837
3934
  const stateRecord = this.ensureStateRecord(actorKey);
3838
3935
  stateRecord.lastReadAt = Date.now();
@@ -3980,7 +4077,7 @@ var Actor = class {
3980
4077
  taskBindingId,
3981
4078
  actorKey,
3982
4079
  invocationOptions,
3983
- runTask
4080
+ () => this.runWithPerKeyWriteSerialization(actorKey, mode, runTask)
3984
4081
  );
3985
4082
  };
3986
4083
  wrapped[ACTOR_TASK_METADATA] = {
@@ -4003,6 +4100,7 @@ var Actor = class {
4003
4100
  */
4004
4101
  getDurableState(actorKey) {
4005
4102
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4103
+ this.pruneExpiredActorKeys(Date.now());
4006
4104
  return cloneForDurableState(this.ensureStateRecord(key).durableState);
4007
4105
  }
4008
4106
  /**
@@ -4010,6 +4108,7 @@ var Actor = class {
4010
4108
  */
4011
4109
  getRuntimeState(actorKey) {
4012
4110
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4111
+ this.pruneExpiredActorKeys(Date.now());
4013
4112
  return this.ensureStateRecord(key).runtimeState;
4014
4113
  }
4015
4114
  /**
@@ -4023,6 +4122,7 @@ var Actor = class {
4023
4122
  */
4024
4123
  getDurableVersion(actorKey) {
4025
4124
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4125
+ this.pruneExpiredActorKeys(Date.now());
4026
4126
  return this.ensureStateRecord(key).version;
4027
4127
  }
4028
4128
  /**
@@ -4030,6 +4130,7 @@ var Actor = class {
4030
4130
  */
4031
4131
  getRuntimeVersion(actorKey) {
4032
4132
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4133
+ this.pruneExpiredActorKeys(Date.now());
4033
4134
  return this.ensureStateRecord(key).runtimeVersion;
4034
4135
  }
4035
4136
  /**
@@ -4200,6 +4301,30 @@ var Actor = class {
4200
4301
  existing.absoluteExpiresAt = touchedAt + absoluteTtlMs;
4201
4302
  }
4202
4303
  }
4304
+ pruneExpiredActorKeys(now2) {
4305
+ if (!this.spec.session?.enabled || this.sessionByKey.size === 0) {
4306
+ return;
4307
+ }
4308
+ for (const [actorKey, session] of this.sessionByKey.entries()) {
4309
+ if (!this.isSessionExpired(session, now2)) {
4310
+ continue;
4311
+ }
4312
+ if (this.writeQueueByKey.has(actorKey)) {
4313
+ continue;
4314
+ }
4315
+ this.stateByKey.delete(actorKey);
4316
+ this.sessionByKey.delete(actorKey);
4317
+ }
4318
+ }
4319
+ isSessionExpired(session, now2) {
4320
+ if (session.absoluteExpiresAt !== null && now2 >= session.absoluteExpiresAt) {
4321
+ return true;
4322
+ }
4323
+ if (session.idleExpiresAt !== null && now2 >= session.idleExpiresAt) {
4324
+ return true;
4325
+ }
4326
+ return false;
4327
+ }
4203
4328
  runWithOptionalIdempotency(taskBindingId, actorKey, options, runTask) {
4204
4329
  const idempotencyPolicy = this.spec.idempotency;
4205
4330
  const enabled = idempotencyPolicy?.enabled ?? false;
@@ -4258,6 +4383,24 @@ var Actor = class {
4258
4383
  });
4259
4384
  return promise;
4260
4385
  }
4386
+ runWithPerKeyWriteSerialization(actorKey, mode, runTask) {
4387
+ if (mode === "read") {
4388
+ return runTask();
4389
+ }
4390
+ const previous = this.writeQueueByKey.get(actorKey) ?? Promise.resolve();
4391
+ let releaseCurrent;
4392
+ const currentGate = new Promise((resolve) => {
4393
+ releaseCurrent = resolve;
4394
+ });
4395
+ const currentTail = previous.catch(() => void 0).then(() => currentGate);
4396
+ this.writeQueueByKey.set(actorKey, currentTail);
4397
+ return previous.catch(() => void 0).then(runTask).finally(() => {
4398
+ releaseCurrent();
4399
+ if (this.writeQueueByKey.get(actorKey) === currentTail) {
4400
+ this.writeQueueByKey.delete(actorKey);
4401
+ }
4402
+ });
4403
+ }
4261
4404
  getActiveIdempotencyRecord(compositeKey) {
4262
4405
  const record = this.idempotencyByKey.get(compositeKey);
4263
4406
  if (!record) {
@@ -4275,23 +4418,25 @@ var Actor = class {
4275
4418
  return;
4276
4419
  }
4277
4420
  const timeoutMs = normalizePositiveInteger(this.spec.session?.persistenceTimeoutMs) ?? 5e3;
4421
+ const persistenceContext = {
4422
+ actor_name: this.spec.name,
4423
+ actor_version: 1,
4424
+ actor_key: actorKey,
4425
+ durable_state: cloneForDurableState(durableState),
4426
+ durable_version: durableVersion,
4427
+ expires_at: null
4428
+ };
4278
4429
  const response = await inquire(
4279
4430
  META_ACTOR_SESSION_STATE_PERSIST_INTENT,
4280
- {
4281
- actor_name: this.spec.name,
4282
- actor_version: 1,
4283
- actor_key: actorKey,
4284
- durable_state: cloneForDurableState(durableState),
4285
- durable_version: durableVersion,
4286
- expires_at: null
4287
- },
4431
+ persistenceContext,
4288
4432
  {
4289
4433
  timeout: timeoutMs,
4434
+ requireComplete: true,
4290
4435
  rejectOnTimeout: true
4291
4436
  }
4292
4437
  );
4293
4438
  if (!isObject2(response) || response.__success !== true || response.persisted !== true) {
4294
- const reason = isObject2(response) ? response.__error ?? response.error : void 0;
4439
+ const reason = isObject2(response) ? response.__error ?? response.error ?? response.internalError ?? (response.errored === true ? `errored response keys: ${Object.keys(response).join(",")}` : response.failed === true ? `failed response keys: ${Object.keys(response).join(",")}` : void 0) : void 0;
4295
4440
  throw new Error(
4296
4441
  `Actor "${this.spec.name}" durable state persistence failed for key "${actorKey}"${reason ? `: ${String(reason)}` : ""}`
4297
4442
  );
@@ -4325,6 +4470,20 @@ var Actor = class {
4325
4470
  };
4326
4471
 
4327
4472
  // src/graph/definition/Task.ts
4473
+ function normalizeSignalDefinition(input) {
4474
+ if (typeof input === "string") {
4475
+ return {
4476
+ name: input
4477
+ };
4478
+ }
4479
+ return {
4480
+ name: input.name,
4481
+ metadata: {
4482
+ deliveryMode: input.deliveryMode,
4483
+ broadcastFilter: input.broadcastFilter ?? null
4484
+ }
4485
+ };
4486
+ }
4328
4487
  var Task = class _Task extends SignalEmitter {
4329
4488
  /**
4330
4489
  * Constructs an instance of the task with the specified properties and configuration options.
@@ -5145,9 +5304,10 @@ var Task = class _Task extends SignalEmitter {
5145
5304
  * @return {this} The current instance after adding the specified signals.
5146
5305
  */
5147
5306
  doOn(...signals) {
5148
- signals.forEach((signal) => {
5307
+ signals.forEach((input) => {
5308
+ const { name: signal, metadata } = normalizeSignalDefinition(input);
5149
5309
  if (this.observedSignals.has(signal)) return;
5150
- Cadenza.signalBroker.observe(signal, this);
5310
+ Cadenza.signalBroker.observe(signal, this, metadata);
5151
5311
  this.observedSignals.add(signal);
5152
5312
  if (this.register) {
5153
5313
  this.emitWithMetadata("meta.task.observed_signal", {
@@ -5168,13 +5328,14 @@ var Task = class _Task extends SignalEmitter {
5168
5328
  * @return {this} The current instance for method chaining.
5169
5329
  */
5170
5330
  emits(...signals) {
5171
- signals.forEach((signal) => {
5331
+ signals.forEach((input) => {
5332
+ const { name: signal } = normalizeSignalDefinition(input);
5172
5333
  if (this.observedSignals.has(signal))
5173
5334
  throw new Error(
5174
5335
  `Detected signal loop for task ${this.name}. Signal name: ${signal}`
5175
5336
  );
5176
5337
  this.signalsToEmitAfter.add(signal);
5177
- this.attachSignal(signal);
5338
+ this.attachSignal(input);
5178
5339
  });
5179
5340
  return this;
5180
5341
  }
@@ -5186,9 +5347,10 @@ var Task = class _Task extends SignalEmitter {
5186
5347
  * @return {this} Returns the current instance for chaining.
5187
5348
  */
5188
5349
  emitsOnFail(...signals) {
5189
- signals.forEach((signal) => {
5350
+ signals.forEach((input) => {
5351
+ const { name: signal } = normalizeSignalDefinition(input);
5190
5352
  this.signalsToEmitOnFail.add(signal);
5191
- this.attachSignal(signal);
5353
+ this.attachSignal(input);
5192
5354
  });
5193
5355
  return this;
5194
5356
  }
@@ -5199,9 +5361,10 @@ var Task = class _Task extends SignalEmitter {
5199
5361
  * @return {void} This method does not return a value.
5200
5362
  */
5201
5363
  attachSignal(...signals) {
5202
- signals.forEach((signal) => {
5364
+ signals.forEach((input) => {
5365
+ const { name: signal, metadata } = normalizeSignalDefinition(input);
5203
5366
  this.emitsSignals.add(signal);
5204
- Cadenza.signalBroker.registerEmittedSignal(signal);
5367
+ Cadenza.signalBroker.registerEmittedSignal(signal, metadata);
5205
5368
  if (this.register) {
5206
5369
  const data = {
5207
5370
  signals: {