@cadenza.io/core 3.26.0 → 3.27.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/index.d.mts CHANGED
@@ -1993,6 +1993,7 @@ interface ActorSpec<D extends Record<string, any>, R = AnyObject> {
1993
1993
  interface ActorFactoryOptions<D extends Record<string, any> = Record<string, any>, R = AnyObject> {
1994
1994
  isMeta?: boolean;
1995
1995
  definitionSource?: ActorDefinition<D, R>;
1996
+ hydrateDurableState?: (actorKey: string) => Promise<ActorDurableStateHydration<D> | null>;
1996
1997
  }
1997
1998
  /**
1998
1999
  * Optional per-binding behavior when wrapping actor handlers.
@@ -2075,6 +2076,10 @@ interface ActorTaskContext<D extends Record<string, any>, R = AnyObject> {
2075
2076
  * Handler signature used by `actor.task(...)`.
2076
2077
  */
2077
2078
  type ActorTaskHandler<D extends Record<string, any>, R = AnyObject> = (context: ActorTaskContext<D, R>) => TaskResult | ActorStateReducer<D> | Promise<TaskResult | ActorStateReducer<D>>;
2079
+ interface ActorDurableStateHydration<D extends Record<string, any>> {
2080
+ durableState: D;
2081
+ durableVersion: number;
2082
+ }
2078
2083
  /**
2079
2084
  * Metadata attached to wrapped actor task functions.
2080
2085
  */
@@ -2101,9 +2106,11 @@ declare class Actor<D extends Record<string, any> = AnyObject, R = AnyObject> {
2101
2106
  readonly spec: ActorSpec<D, R>;
2102
2107
  readonly kind: ActorKind;
2103
2108
  private readonly sourceDefinition?;
2109
+ private readonly hydrateDurableState?;
2104
2110
  private readonly stateByKey;
2105
2111
  private readonly sessionByKey;
2106
2112
  private readonly idempotencyByKey;
2113
+ private readonly pendingHydrationByKey;
2107
2114
  private readonly writeQueueByKey;
2108
2115
  private nextTaskBindingIndex;
2109
2116
  /**
@@ -2154,7 +2161,10 @@ declare class Actor<D extends Record<string, any> = AnyObject, R = AnyObject> {
2154
2161
  private resolveInitialDurableState;
2155
2162
  private resolveInitialRuntimeState;
2156
2163
  private ensureStateRecord;
2164
+ private maybeHydrateStateRecord;
2157
2165
  private touchSession;
2166
+ private pruneExpiredActorKeys;
2167
+ private isSessionExpired;
2158
2168
  private runWithOptionalIdempotency;
2159
2169
  private runWithPerKeyWriteSerialization;
2160
2170
  private getActiveIdempotencyRecord;
package/dist/index.d.ts CHANGED
@@ -1993,6 +1993,7 @@ interface ActorSpec<D extends Record<string, any>, R = AnyObject> {
1993
1993
  interface ActorFactoryOptions<D extends Record<string, any> = Record<string, any>, R = AnyObject> {
1994
1994
  isMeta?: boolean;
1995
1995
  definitionSource?: ActorDefinition<D, R>;
1996
+ hydrateDurableState?: (actorKey: string) => Promise<ActorDurableStateHydration<D> | null>;
1996
1997
  }
1997
1998
  /**
1998
1999
  * Optional per-binding behavior when wrapping actor handlers.
@@ -2075,6 +2076,10 @@ interface ActorTaskContext<D extends Record<string, any>, R = AnyObject> {
2075
2076
  * Handler signature used by `actor.task(...)`.
2076
2077
  */
2077
2078
  type ActorTaskHandler<D extends Record<string, any>, R = AnyObject> = (context: ActorTaskContext<D, R>) => TaskResult | ActorStateReducer<D> | Promise<TaskResult | ActorStateReducer<D>>;
2079
+ interface ActorDurableStateHydration<D extends Record<string, any>> {
2080
+ durableState: D;
2081
+ durableVersion: number;
2082
+ }
2078
2083
  /**
2079
2084
  * Metadata attached to wrapped actor task functions.
2080
2085
  */
@@ -2101,9 +2106,11 @@ declare class Actor<D extends Record<string, any> = AnyObject, R = AnyObject> {
2101
2106
  readonly spec: ActorSpec<D, R>;
2102
2107
  readonly kind: ActorKind;
2103
2108
  private readonly sourceDefinition?;
2109
+ private readonly hydrateDurableState?;
2104
2110
  private readonly stateByKey;
2105
2111
  private readonly sessionByKey;
2106
2112
  private readonly idempotencyByKey;
2113
+ private readonly pendingHydrationByKey;
2107
2114
  private readonly writeQueueByKey;
2108
2115
  private nextTaskBindingIndex;
2109
2116
  /**
@@ -2154,7 +2161,10 @@ declare class Actor<D extends Record<string, any> = AnyObject, R = AnyObject> {
2154
2161
  private resolveInitialDurableState;
2155
2162
  private resolveInitialRuntimeState;
2156
2163
  private ensureStateRecord;
2164
+ private maybeHydrateStateRecord;
2157
2165
  private touchSession;
2166
+ private pruneExpiredActorKeys;
2167
+ private isSessionExpired;
2158
2168
  private runWithOptionalIdempotency;
2159
2169
  private runWithPerKeyWriteSerialization;
2160
2170
  private getActiveIdempotencyRecord;
package/dist/index.js CHANGED
@@ -3893,6 +3893,7 @@ var Actor = class {
3893
3893
  this.stateByKey = /* @__PURE__ */ new Map();
3894
3894
  this.sessionByKey = /* @__PURE__ */ new Map();
3895
3895
  this.idempotencyByKey = /* @__PURE__ */ new Map();
3896
+ this.pendingHydrationByKey = /* @__PURE__ */ new Map();
3896
3897
  this.writeQueueByKey = /* @__PURE__ */ new Map();
3897
3898
  this.nextTaskBindingIndex = 0;
3898
3899
  if (!spec.name || typeof spec.name !== "string") {
@@ -3904,6 +3905,7 @@ var Actor = class {
3904
3905
  }
3905
3906
  this.kind = options.isMeta || spec.kind === "meta" ? "meta" : "standard";
3906
3907
  this.sourceDefinition = options.definitionSource;
3908
+ this.hydrateDurableState = options.hydrateDurableState;
3907
3909
  this.spec = {
3908
3910
  ...spec,
3909
3911
  defaultKey: normalizedDefaultKey,
@@ -3927,9 +3929,11 @@ var Actor = class {
3927
3929
  bindingOptions.touchSession
3928
3930
  );
3929
3931
  const actorKey = this.resolveActorKey(normalizedInput, invocationOptions);
3930
- this.touchSession(actorKey, invocationOptions.touchSession, Date.now());
3932
+ const now2 = Date.now();
3933
+ this.pruneExpiredActorKeys(now2);
3934
+ this.touchSession(actorKey, invocationOptions.touchSession, now2);
3931
3935
  const runTask = async () => {
3932
- const stateRecord = this.ensureStateRecord(actorKey);
3936
+ const stateRecord = await this.maybeHydrateStateRecord(actorKey);
3933
3937
  stateRecord.lastReadAt = Date.now();
3934
3938
  let durableStateChanged = false;
3935
3939
  let runtimeStateChanged = false;
@@ -4098,6 +4102,7 @@ var Actor = class {
4098
4102
  */
4099
4103
  getDurableState(actorKey) {
4100
4104
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4105
+ this.pruneExpiredActorKeys(Date.now());
4101
4106
  return cloneForDurableState(this.ensureStateRecord(key).durableState);
4102
4107
  }
4103
4108
  /**
@@ -4105,6 +4110,7 @@ var Actor = class {
4105
4110
  */
4106
4111
  getRuntimeState(actorKey) {
4107
4112
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4113
+ this.pruneExpiredActorKeys(Date.now());
4108
4114
  return this.ensureStateRecord(key).runtimeState;
4109
4115
  }
4110
4116
  /**
@@ -4118,6 +4124,7 @@ var Actor = class {
4118
4124
  */
4119
4125
  getDurableVersion(actorKey) {
4120
4126
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4127
+ this.pruneExpiredActorKeys(Date.now());
4121
4128
  return this.ensureStateRecord(key).version;
4122
4129
  }
4123
4130
  /**
@@ -4125,6 +4132,7 @@ var Actor = class {
4125
4132
  */
4126
4133
  getRuntimeVersion(actorKey) {
4127
4134
  const key = normalizeActorKey(actorKey) ?? this.spec.defaultKey;
4135
+ this.pruneExpiredActorKeys(Date.now());
4128
4136
  return this.ensureStateRecord(key).runtimeVersion;
4129
4137
  }
4130
4138
  /**
@@ -4167,6 +4175,7 @@ var Actor = class {
4167
4175
  this.stateByKey.clear();
4168
4176
  this.sessionByKey.clear();
4169
4177
  this.idempotencyByKey.clear();
4178
+ this.pendingHydrationByKey.clear();
4170
4179
  if ((this.spec.loadPolicy ?? "eager") === "eager") {
4171
4180
  this.ensureStateRecord(this.spec.defaultKey);
4172
4181
  }
@@ -4178,6 +4187,7 @@ var Actor = class {
4178
4187
  }
4179
4188
  this.stateByKey.delete(normalizedKey);
4180
4189
  this.sessionByKey.delete(normalizedKey);
4190
+ this.pendingHydrationByKey.delete(normalizedKey);
4181
4191
  for (const key of this.idempotencyByKey.keys()) {
4182
4192
  if (key.startsWith(`${normalizedKey}:`)) {
4183
4193
  this.idempotencyByKey.delete(key);
@@ -4263,6 +4273,7 @@ var Actor = class {
4263
4273
  runtimeState: this.resolveInitialRuntimeState(),
4264
4274
  version: 0,
4265
4275
  runtimeVersion: 0,
4276
+ hydrationResolved: this.hydrateDurableState === void 0,
4266
4277
  createdAt: now2,
4267
4278
  lastReadAt: now2,
4268
4279
  lastDurableWriteAt: now2,
@@ -4272,6 +4283,54 @@ var Actor = class {
4272
4283
  this.touchSession(actorKey, true, now2);
4273
4284
  return record;
4274
4285
  }
4286
+ async maybeHydrateStateRecord(actorKey) {
4287
+ const record = this.ensureStateRecord(actorKey);
4288
+ if (record.hydrationResolved || !this.hydrateDurableState) {
4289
+ return record;
4290
+ }
4291
+ const pending = this.pendingHydrationByKey.get(actorKey);
4292
+ if (pending) {
4293
+ return pending;
4294
+ }
4295
+ let hydrationPromise;
4296
+ hydrationPromise = (async () => {
4297
+ const current = this.ensureStateRecord(actorKey);
4298
+ if (current.hydrationResolved || !this.hydrateDurableState) {
4299
+ return current;
4300
+ }
4301
+ const snapshot = await this.hydrateDurableState(actorKey);
4302
+ const latest = this.ensureStateRecord(actorKey);
4303
+ if (latest.hydrationResolved) {
4304
+ return latest;
4305
+ }
4306
+ if (snapshot) {
4307
+ const durableVersion = Number(snapshot.durableVersion);
4308
+ if (!Number.isInteger(durableVersion) || durableVersion < 0) {
4309
+ throw new Error(
4310
+ `Actor "${this.spec.name}" received invalid hydrated durable version for key "${actorKey}"`
4311
+ );
4312
+ }
4313
+ if (!isObject2(snapshot.durableState) || Array.isArray(snapshot.durableState)) {
4314
+ throw new Error(
4315
+ `Actor "${this.spec.name}" received invalid hydrated durable state for key "${actorKey}"`
4316
+ );
4317
+ }
4318
+ const hydratedAt = Date.now();
4319
+ latest.durableState = cloneForDurableState(snapshot.durableState);
4320
+ latest.version = durableVersion;
4321
+ latest.lastReadAt = hydratedAt;
4322
+ latest.lastDurableWriteAt = hydratedAt;
4323
+ }
4324
+ latest.hydrationResolved = true;
4325
+ return latest;
4326
+ })().finally(() => {
4327
+ if (this.pendingHydrationByKey.get(actorKey) === hydrationPromise) {
4328
+ this.pendingHydrationByKey.delete(actorKey);
4329
+ }
4330
+ });
4331
+ this.pendingHydrationByKey.set(actorKey, hydrationPromise);
4332
+ return hydrationPromise;
4333
+ }
4275
4334
  touchSession(actorKey, shouldTouch, touchedAt) {
4276
4335
  if (!this.spec.session?.enabled || !shouldTouch) {
4277
4336
  return;
@@ -4295,6 +4354,30 @@ var Actor = class {
4295
4354
  existing.absoluteExpiresAt = touchedAt + absoluteTtlMs;
4296
4355
  }
4297
4356
  }
4357
+ pruneExpiredActorKeys(now2) {
4358
+ if (!this.spec.session?.enabled || this.sessionByKey.size === 0) {
4359
+ return;
4360
+ }
4361
+ for (const [actorKey, session] of this.sessionByKey.entries()) {
4362
+ if (!this.isSessionExpired(session, now2)) {
4363
+ continue;
4364
+ }
4365
+ if (this.writeQueueByKey.has(actorKey)) {
4366
+ continue;
4367
+ }
4368
+ this.stateByKey.delete(actorKey);
4369
+ this.sessionByKey.delete(actorKey);
4370
+ }
4371
+ }
4372
+ isSessionExpired(session, now2) {
4373
+ if (session.absoluteExpiresAt !== null && now2 >= session.absoluteExpiresAt) {
4374
+ return true;
4375
+ }
4376
+ if (session.idleExpiresAt !== null && now2 >= session.idleExpiresAt) {
4377
+ return true;
4378
+ }
4379
+ return false;
4380
+ }
4298
4381
  runWithOptionalIdempotency(taskBindingId, actorKey, options, runTask) {
4299
4382
  const idempotencyPolicy = this.spec.idempotency;
4300
4383
  const enabled = idempotencyPolicy?.enabled ?? false;
@@ -7345,7 +7428,8 @@ var Cadenza = class {
7345
7428
  };
7346
7429
  const actorOptions = {
7347
7430
  isMeta: options.isMeta,
7348
- definitionSource: definition
7431
+ definitionSource: definition,
7432
+ hydrateDurableState: options.hydrateDurableState
7349
7433
  };
7350
7434
  return this.createActor(spec, actorOptions);
7351
7435
  }