@dudousxd/nestjs-durable-core 0.28.0 → 0.28.2
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.cjs +67 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +67 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -277,6 +277,28 @@ interface StateStore {
|
|
|
277
277
|
transaction?<T>(work: (tx: StoreTransaction) => Promise<T>): Promise<T>;
|
|
278
278
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
279
279
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
280
|
+
/**
|
|
281
|
+
* The LATEST checkpoint for `runId` whose `name` equals `name` exactly (highest `seq` wins), or
|
|
282
|
+
* `undefined` if none. A targeted read that avoids fetching + deserializing every checkpoint just to
|
|
283
|
+
* keep one match — the store does the filter (`WHERE name = … ORDER BY seq DESC LIMIT 1`). Preserves
|
|
284
|
+
* the "last in seq order wins" semantics the engine relies on for `getEvent` (a re-published key
|
|
285
|
+
* overwrites the prior value at a higher seq, so the highest-seq match is the current value).
|
|
286
|
+
*
|
|
287
|
+
* Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
|
|
288
|
+
* plus an in-JS filter that produces the identical result.
|
|
289
|
+
*/
|
|
290
|
+
getLatestCheckpointByName?(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
291
|
+
/**
|
|
292
|
+
* All checkpoints for `runId` whose `name` starts with ANY of `prefixes`, ordered by `seq` ascending
|
|
293
|
+
* (same order as {@link listCheckpoints}). A targeted read that avoids scanning every checkpoint just
|
|
294
|
+
* to keep the prefix matches — the store does the filter (`WHERE name LIKE 'prefix%' …`). Used by the
|
|
295
|
+
* run-tree to find a parent's child edges (`signal:child:` / `spawn:` checkpoints) without loading the
|
|
296
|
+
* whole history. An empty `prefixes` array matches nothing.
|
|
297
|
+
*
|
|
298
|
+
* Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
|
|
299
|
+
* plus an in-JS prefix scan that produces the identical result.
|
|
300
|
+
*/
|
|
301
|
+
listCheckpointsByNamePrefix?(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
280
302
|
}
|
|
281
303
|
/** Typed, queryable per-run data — exact values for `eq`/`ne`, numbers/strings for range ops. */
|
|
282
304
|
type SearchAttributes = Record<string, string | number | boolean>;
|
|
@@ -1792,6 +1814,8 @@ declare class CodecStateStore implements StateStore {
|
|
|
1792
1814
|
} | null>;
|
|
1793
1815
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
1794
1816
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
1817
|
+
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
1818
|
+
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
1795
1819
|
}
|
|
1796
1820
|
|
|
1797
1821
|
/** Token prefix for every waiter on `name` — used by `listSignalWaiters` to fan an event out. */
|
|
@@ -1960,6 +1984,16 @@ declare function attributeOperand(filter: AttributeFilter): string | number;
|
|
|
1960
1984
|
/**
|
|
1961
1985
|
* Dependency-injection tokens. Adapters bind their implementations to these so `core` and
|
|
1962
1986
|
* `@dudousxd/nestjs-durable` depend only on the interfaces in `./interfaces`.
|
|
1987
|
+
*
|
|
1988
|
+
* These use `Symbol.for(key)` (the global symbol registry), NOT `Symbol(key)`, so the token is the
|
|
1989
|
+
* SAME instance no matter how many times `core` is evaluated in a process. A consumer can hold more
|
|
1990
|
+
* than one physical copy of `core` at runtime — pnpm peer-dependency multiplexing installs a
|
|
1991
|
+
* separate virtual copy per distinct peer set, and the dual ESM/CJS build can be loaded once as
|
|
1992
|
+
* `import` (`index.js`) and once as `require` (`index.cjs`). A plain `Symbol()` would mint a
|
|
1993
|
+
* DISTINCT token per copy, so `DurableModule` (provider) and `DashboardService` / a store adapter
|
|
1994
|
+
* (injector) could resolve different symbols → Nest can't satisfy the dependency
|
|
1995
|
+
* (`STATE_STORE is not available in the DurableApiModule context`). A registered symbol collapses
|
|
1996
|
+
* them to one. Mirrors the `CONTEXT_ACCESSOR` token in `@dudousxd/nestjs-durable`'s `tokens.ts`.
|
|
1963
1997
|
*/
|
|
1964
1998
|
declare const STATE_STORE: unique symbol;
|
|
1965
1999
|
declare const TRANSPORT: unique symbol;
|
|
@@ -1982,6 +2016,13 @@ declare class InMemoryStateStore implements StateStore {
|
|
|
1982
2016
|
private readonly attributeIndex;
|
|
1983
2017
|
/** Test/observability hook: how many candidate runs the last attribute query's index produced. */
|
|
1984
2018
|
lastAttributeCandidates: number;
|
|
2019
|
+
/**
|
|
2020
|
+
* Test/observability hook: how many checkpoint rows the LAST checkpoint read materialized (the rows
|
|
2021
|
+
* actually returned to the caller). `listCheckpoints` returns all rows for the run; the targeted
|
|
2022
|
+
* {@link getLatestCheckpointByName} / {@link listCheckpointsByNamePrefix} return only the matches —
|
|
2023
|
+
* so this lets the benchmark contrast "fetch N, scan in JS" against "fetch the k that match".
|
|
2024
|
+
*/
|
|
2025
|
+
lastCheckpointRowsRead: number;
|
|
1985
2026
|
private key;
|
|
1986
2027
|
/** Drop then re-add a run's normalized attribute rows so the index tracks its current attributes. */
|
|
1987
2028
|
private reindexAttributes;
|
|
@@ -2016,6 +2057,8 @@ declare class InMemoryStateStore implements StateStore {
|
|
|
2016
2057
|
private candidatesForFilter;
|
|
2017
2058
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
2018
2059
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
2060
|
+
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
2061
|
+
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
2019
2062
|
}
|
|
2020
2063
|
|
|
2021
2064
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -277,6 +277,28 @@ interface StateStore {
|
|
|
277
277
|
transaction?<T>(work: (tx: StoreTransaction) => Promise<T>): Promise<T>;
|
|
278
278
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
279
279
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
280
|
+
/**
|
|
281
|
+
* The LATEST checkpoint for `runId` whose `name` equals `name` exactly (highest `seq` wins), or
|
|
282
|
+
* `undefined` if none. A targeted read that avoids fetching + deserializing every checkpoint just to
|
|
283
|
+
* keep one match — the store does the filter (`WHERE name = … ORDER BY seq DESC LIMIT 1`). Preserves
|
|
284
|
+
* the "last in seq order wins" semantics the engine relies on for `getEvent` (a re-published key
|
|
285
|
+
* overwrites the prior value at a higher seq, so the highest-seq match is the current value).
|
|
286
|
+
*
|
|
287
|
+
* Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
|
|
288
|
+
* plus an in-JS filter that produces the identical result.
|
|
289
|
+
*/
|
|
290
|
+
getLatestCheckpointByName?(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
291
|
+
/**
|
|
292
|
+
* All checkpoints for `runId` whose `name` starts with ANY of `prefixes`, ordered by `seq` ascending
|
|
293
|
+
* (same order as {@link listCheckpoints}). A targeted read that avoids scanning every checkpoint just
|
|
294
|
+
* to keep the prefix matches — the store does the filter (`WHERE name LIKE 'prefix%' …`). Used by the
|
|
295
|
+
* run-tree to find a parent's child edges (`signal:child:` / `spawn:` checkpoints) without loading the
|
|
296
|
+
* whole history. An empty `prefixes` array matches nothing.
|
|
297
|
+
*
|
|
298
|
+
* Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
|
|
299
|
+
* plus an in-JS prefix scan that produces the identical result.
|
|
300
|
+
*/
|
|
301
|
+
listCheckpointsByNamePrefix?(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
280
302
|
}
|
|
281
303
|
/** Typed, queryable per-run data — exact values for `eq`/`ne`, numbers/strings for range ops. */
|
|
282
304
|
type SearchAttributes = Record<string, string | number | boolean>;
|
|
@@ -1792,6 +1814,8 @@ declare class CodecStateStore implements StateStore {
|
|
|
1792
1814
|
} | null>;
|
|
1793
1815
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
1794
1816
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
1817
|
+
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
1818
|
+
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
1795
1819
|
}
|
|
1796
1820
|
|
|
1797
1821
|
/** Token prefix for every waiter on `name` — used by `listSignalWaiters` to fan an event out. */
|
|
@@ -1960,6 +1984,16 @@ declare function attributeOperand(filter: AttributeFilter): string | number;
|
|
|
1960
1984
|
/**
|
|
1961
1985
|
* Dependency-injection tokens. Adapters bind their implementations to these so `core` and
|
|
1962
1986
|
* `@dudousxd/nestjs-durable` depend only on the interfaces in `./interfaces`.
|
|
1987
|
+
*
|
|
1988
|
+
* These use `Symbol.for(key)` (the global symbol registry), NOT `Symbol(key)`, so the token is the
|
|
1989
|
+
* SAME instance no matter how many times `core` is evaluated in a process. A consumer can hold more
|
|
1990
|
+
* than one physical copy of `core` at runtime — pnpm peer-dependency multiplexing installs a
|
|
1991
|
+
* separate virtual copy per distinct peer set, and the dual ESM/CJS build can be loaded once as
|
|
1992
|
+
* `import` (`index.js`) and once as `require` (`index.cjs`). A plain `Symbol()` would mint a
|
|
1993
|
+
* DISTINCT token per copy, so `DurableModule` (provider) and `DashboardService` / a store adapter
|
|
1994
|
+
* (injector) could resolve different symbols → Nest can't satisfy the dependency
|
|
1995
|
+
* (`STATE_STORE is not available in the DurableApiModule context`). A registered symbol collapses
|
|
1996
|
+
* them to one. Mirrors the `CONTEXT_ACCESSOR` token in `@dudousxd/nestjs-durable`'s `tokens.ts`.
|
|
1963
1997
|
*/
|
|
1964
1998
|
declare const STATE_STORE: unique symbol;
|
|
1965
1999
|
declare const TRANSPORT: unique symbol;
|
|
@@ -1982,6 +2016,13 @@ declare class InMemoryStateStore implements StateStore {
|
|
|
1982
2016
|
private readonly attributeIndex;
|
|
1983
2017
|
/** Test/observability hook: how many candidate runs the last attribute query's index produced. */
|
|
1984
2018
|
lastAttributeCandidates: number;
|
|
2019
|
+
/**
|
|
2020
|
+
* Test/observability hook: how many checkpoint rows the LAST checkpoint read materialized (the rows
|
|
2021
|
+
* actually returned to the caller). `listCheckpoints` returns all rows for the run; the targeted
|
|
2022
|
+
* {@link getLatestCheckpointByName} / {@link listCheckpointsByNamePrefix} return only the matches —
|
|
2023
|
+
* so this lets the benchmark contrast "fetch N, scan in JS" against "fetch the k that match".
|
|
2024
|
+
*/
|
|
2025
|
+
lastCheckpointRowsRead: number;
|
|
1985
2026
|
private key;
|
|
1986
2027
|
/** Drop then re-add a run's normalized attribute rows so the index tracks its current attributes. */
|
|
1987
2028
|
private reindexAttributes;
|
|
@@ -2016,6 +2057,8 @@ declare class InMemoryStateStore implements StateStore {
|
|
|
2016
2057
|
private candidatesForFilter;
|
|
2017
2058
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
2018
2059
|
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
2060
|
+
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
2061
|
+
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
2019
2062
|
}
|
|
2020
2063
|
|
|
2021
2064
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2039,7 +2039,12 @@ var WorkflowEngine = class {
|
|
|
2039
2039
|
for (const w of await this.store.listSignalWaiters("child:")) {
|
|
2040
2040
|
if (w.runId === parentRunId) childIds.add(w.token.slice("child:".length));
|
|
2041
2041
|
}
|
|
2042
|
-
|
|
2042
|
+
const prefixes = [
|
|
2043
|
+
"signal:child:",
|
|
2044
|
+
"spawn:"
|
|
2045
|
+
];
|
|
2046
|
+
const childCheckpoints = this.store.listCheckpointsByNamePrefix ? await this.store.listCheckpointsByNamePrefix(parentRunId, prefixes) : (await this.store.listCheckpoints(parentRunId)).filter((cp) => prefixes.some((p) => cp.name.startsWith(p)));
|
|
2047
|
+
for (const cp of childCheckpoints) {
|
|
2043
2048
|
if (cp.name.startsWith("signal:child:")) childIds.add(cp.name.slice("signal:child:".length));
|
|
2044
2049
|
if (cp.name.startsWith("spawn:") && typeof cp.output === "string") childIds.add(cp.output);
|
|
2045
2050
|
}
|
|
@@ -2830,9 +2835,12 @@ var WorkflowEngine = class {
|
|
|
2830
2835
|
*/
|
|
2831
2836
|
async getEvent(runId, key) {
|
|
2832
2837
|
const name = `event:${key}`;
|
|
2833
|
-
|
|
2838
|
+
if (this.store.getLatestCheckpointByName) {
|
|
2839
|
+
const latest2 = await this.store.getLatestCheckpointByName(runId, name);
|
|
2840
|
+
return latest2?.output;
|
|
2841
|
+
}
|
|
2834
2842
|
let latest;
|
|
2835
|
-
for (const cp of
|
|
2843
|
+
for (const cp of await this.store.listCheckpoints(runId)) if (cp.name === name) latest = cp.output;
|
|
2836
2844
|
return latest;
|
|
2837
2845
|
}
|
|
2838
2846
|
/**
|
|
@@ -3285,6 +3293,19 @@ var CodecStateStore = class {
|
|
|
3285
3293
|
async listCheckpoints(runId) {
|
|
3286
3294
|
return (await this.inner.listCheckpoints(runId)).map((c) => this.decCp(c));
|
|
3287
3295
|
}
|
|
3296
|
+
async getLatestCheckpointByName(runId, name) {
|
|
3297
|
+
if (this.inner.getLatestCheckpointByName) {
|
|
3298
|
+
const cp = await this.inner.getLatestCheckpointByName(runId, name);
|
|
3299
|
+
return cp ? this.decCp(cp) : void 0;
|
|
3300
|
+
}
|
|
3301
|
+
let latest;
|
|
3302
|
+
for (const cp of await this.inner.listCheckpoints(runId)) if (cp.name === name) latest = cp;
|
|
3303
|
+
return latest ? this.decCp(latest) : void 0;
|
|
3304
|
+
}
|
|
3305
|
+
async listCheckpointsByNamePrefix(runId, prefixes) {
|
|
3306
|
+
const matches = this.inner.listCheckpointsByNamePrefix ? await this.inner.listCheckpointsByNamePrefix(runId, prefixes) : (await this.inner.listCheckpoints(runId)).filter((cp) => prefixes.some((p) => cp.name.startsWith(p)));
|
|
3307
|
+
return matches.map((c) => this.decCp(c));
|
|
3308
|
+
}
|
|
3288
3309
|
};
|
|
3289
3310
|
|
|
3290
3311
|
// src/metrics.ts
|
|
@@ -3309,11 +3330,16 @@ function collectMetrics(engine) {
|
|
|
3309
3330
|
};
|
|
3310
3331
|
const wf = /* @__PURE__ */ __name((name) => {
|
|
3311
3332
|
const k = name ?? "unknown";
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3333
|
+
let entry = m.byWorkflow[k];
|
|
3334
|
+
if (!entry) {
|
|
3335
|
+
entry = {
|
|
3336
|
+
started: 0,
|
|
3337
|
+
completed: 0,
|
|
3338
|
+
failed: 0
|
|
3339
|
+
};
|
|
3340
|
+
m.byWorkflow[k] = entry;
|
|
3341
|
+
}
|
|
3342
|
+
return entry;
|
|
3317
3343
|
}, "wf");
|
|
3318
3344
|
const stop = engine.subscribe((e) => {
|
|
3319
3345
|
switch (e.type) {
|
|
@@ -3539,9 +3565,9 @@ function attributeOperand(filter) {
|
|
|
3539
3565
|
__name(attributeOperand, "attributeOperand");
|
|
3540
3566
|
|
|
3541
3567
|
// src/tokens.ts
|
|
3542
|
-
var STATE_STORE = Symbol("nestjs-durable:STATE_STORE");
|
|
3543
|
-
var TRANSPORT = Symbol("nestjs-durable:TRANSPORT");
|
|
3544
|
-
var DURABLE_OPTIONS = Symbol("nestjs-durable:DURABLE_OPTIONS");
|
|
3568
|
+
var STATE_STORE = Symbol.for("nestjs-durable:STATE_STORE");
|
|
3569
|
+
var TRANSPORT = Symbol.for("nestjs-durable:TRANSPORT");
|
|
3570
|
+
var DURABLE_OPTIONS = Symbol.for("nestjs-durable:DURABLE_OPTIONS");
|
|
3545
3571
|
|
|
3546
3572
|
// src/testing/in-memory-state-store.ts
|
|
3547
3573
|
var InMemoryStateStore = class {
|
|
@@ -3560,6 +3586,13 @@ var InMemoryStateStore = class {
|
|
|
3560
3586
|
attributeIndex = /* @__PURE__ */ new Map();
|
|
3561
3587
|
/** Test/observability hook: how many candidate runs the last attribute query's index produced. */
|
|
3562
3588
|
lastAttributeCandidates = 0;
|
|
3589
|
+
/**
|
|
3590
|
+
* Test/observability hook: how many checkpoint rows the LAST checkpoint read materialized (the rows
|
|
3591
|
+
* actually returned to the caller). `listCheckpoints` returns all rows for the run; the targeted
|
|
3592
|
+
* {@link getLatestCheckpointByName} / {@link listCheckpointsByNamePrefix} return only the matches —
|
|
3593
|
+
* so this lets the benchmark contrast "fetch N, scan in JS" against "fetch the k that match".
|
|
3594
|
+
*/
|
|
3595
|
+
lastCheckpointRowsRead = 0;
|
|
3563
3596
|
key(runId, seq) {
|
|
3564
3597
|
return `${runId}:${seq}`;
|
|
3565
3598
|
}
|
|
@@ -3766,11 +3799,33 @@ var InMemoryStateStore = class {
|
|
|
3766
3799
|
}));
|
|
3767
3800
|
}
|
|
3768
3801
|
async listCheckpoints(runId) {
|
|
3769
|
-
|
|
3802
|
+
const rows = [
|
|
3770
3803
|
...this.checkpoints.values()
|
|
3771
3804
|
].filter((cp) => cp.runId === runId).sort((a, b) => a.seq - b.seq).map((cp) => ({
|
|
3772
3805
|
...cp
|
|
3773
3806
|
}));
|
|
3807
|
+
this.lastCheckpointRowsRead = rows.length;
|
|
3808
|
+
return rows;
|
|
3809
|
+
}
|
|
3810
|
+
async getLatestCheckpointByName(runId, name) {
|
|
3811
|
+
let latest;
|
|
3812
|
+
for (const cp of this.checkpoints.values()) {
|
|
3813
|
+
if (cp.runId !== runId || cp.name !== name) continue;
|
|
3814
|
+
if (!latest || cp.seq > latest.seq) latest = cp;
|
|
3815
|
+
}
|
|
3816
|
+
this.lastCheckpointRowsRead = latest ? 1 : 0;
|
|
3817
|
+
return latest ? {
|
|
3818
|
+
...latest
|
|
3819
|
+
} : void 0;
|
|
3820
|
+
}
|
|
3821
|
+
async listCheckpointsByNamePrefix(runId, prefixes) {
|
|
3822
|
+
const rows = [
|
|
3823
|
+
...this.checkpoints.values()
|
|
3824
|
+
].filter((cp) => cp.runId === runId && prefixes.some((p) => cp.name.startsWith(p))).sort((a, b) => a.seq - b.seq).map((cp) => ({
|
|
3825
|
+
...cp
|
|
3826
|
+
}));
|
|
3827
|
+
this.lastCheckpointRowsRead = rows.length;
|
|
3828
|
+
return rows;
|
|
3774
3829
|
}
|
|
3775
3830
|
};
|
|
3776
3831
|
|