@dudousxd/nestjs-durable 0.42.1 → 0.43.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.cjs +80 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +80 -6
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
|
|
1
|
+
import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
|
|
2
2
|
export { AttributeFilter, EngineEvent, InferSearchAttributes, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
|
|
@@ -900,6 +900,9 @@ declare class ProxyRunGateway implements RunGateway {
|
|
|
900
900
|
listRuns(query: RunQuery): Promise<RunListItem[]>;
|
|
901
901
|
/** Counted on the operator, over the same tenant-scoped predicates its `listRuns` reply obeys. */
|
|
902
902
|
runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
903
|
+
/** Enumerated on the operator, over the same tenant-scoped predicates its `listRuns` reply obeys —
|
|
904
|
+
* so a tenant's pickers offer its own tenants/tags/attributes and never another's. */
|
|
905
|
+
runValueFacets(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
|
|
903
906
|
/** Round-trips to the operator, which scopes the result to this tenant's own `@<tenant>` groups. */
|
|
904
907
|
workerHealth(): Promise<GroupHealth[]>;
|
|
905
908
|
/** Bulk, one request for the whole id list (like `listRuns`, not one request per id). The operator
|
|
@@ -1000,6 +1003,14 @@ declare class StoreRunGateway implements RunGateway {
|
|
|
1000
1003
|
* listing — correct, but the unbounded read this exists to avoid, so it is the last resort.
|
|
1001
1004
|
*/
|
|
1002
1005
|
runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
1006
|
+
/**
|
|
1007
|
+
* The distinct values behind a console's pickers, straight off the store's enumeration. A store
|
|
1008
|
+
* without one answers `[]` rather than falling back to a listing: unlike the facet counts above —
|
|
1009
|
+
* where a full listing is at least the RIGHT answer expensively — a picker built from an unbounded
|
|
1010
|
+
* scan would be the same unbounded read on every keystroke, for a control whose free-text entry
|
|
1011
|
+
* still works without it.
|
|
1012
|
+
*/
|
|
1013
|
+
runValueFacets(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
|
|
1003
1014
|
/**
|
|
1004
1015
|
* Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/
|
|
1005
1016
|
* paginated run listing (e.g. "which of MY suspended runs are stuck at a breakpoint") without
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
|
|
1
|
+
import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
|
|
2
2
|
export { AttributeFilter, EngineEvent, InferSearchAttributes, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
|
|
@@ -900,6 +900,9 @@ declare class ProxyRunGateway implements RunGateway {
|
|
|
900
900
|
listRuns(query: RunQuery): Promise<RunListItem[]>;
|
|
901
901
|
/** Counted on the operator, over the same tenant-scoped predicates its `listRuns` reply obeys. */
|
|
902
902
|
runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
903
|
+
/** Enumerated on the operator, over the same tenant-scoped predicates its `listRuns` reply obeys —
|
|
904
|
+
* so a tenant's pickers offer its own tenants/tags/attributes and never another's. */
|
|
905
|
+
runValueFacets(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
|
|
903
906
|
/** Round-trips to the operator, which scopes the result to this tenant's own `@<tenant>` groups. */
|
|
904
907
|
workerHealth(): Promise<GroupHealth[]>;
|
|
905
908
|
/** Bulk, one request for the whole id list (like `listRuns`, not one request per id). The operator
|
|
@@ -1000,6 +1003,14 @@ declare class StoreRunGateway implements RunGateway {
|
|
|
1000
1003
|
* listing — correct, but the unbounded read this exists to avoid, so it is the last resort.
|
|
1001
1004
|
*/
|
|
1002
1005
|
runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
1006
|
+
/**
|
|
1007
|
+
* The distinct values behind a console's pickers, straight off the store's enumeration. A store
|
|
1008
|
+
* without one answers `[]` rather than falling back to a listing: unlike the facet counts above —
|
|
1009
|
+
* where a full listing is at least the RIGHT answer expensively — a picker built from an unbounded
|
|
1010
|
+
* scan would be the same unbounded read on every keystroke, for a control whose free-text entry
|
|
1011
|
+
* still works without it.
|
|
1012
|
+
*/
|
|
1013
|
+
runValueFacets(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
|
|
1003
1014
|
/**
|
|
1004
1015
|
* Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/
|
|
1005
1016
|
* paginated run listing (e.g. "which of MY suspended runs are stuck at a breakpoint") without
|
package/dist/index.js
CHANGED
|
@@ -577,6 +577,9 @@ function unavailableRunGateway() {
|
|
|
577
577
|
runFacets(_query) {
|
|
578
578
|
return tenantGatewayUnavailable("runFacets");
|
|
579
579
|
},
|
|
580
|
+
runValueFacets(_axis, _query, _opts) {
|
|
581
|
+
return tenantGatewayUnavailable("runValueFacets");
|
|
582
|
+
},
|
|
580
583
|
waitingFor(_runIds) {
|
|
581
584
|
return tenantGatewayUnavailable("waitingFor");
|
|
582
585
|
},
|
|
@@ -1006,6 +1009,16 @@ var ProxyRunGateway = class {
|
|
|
1006
1009
|
query
|
|
1007
1010
|
});
|
|
1008
1011
|
}
|
|
1012
|
+
/** Enumerated on the operator, over the same tenant-scoped predicates its `listRuns` reply obeys —
|
|
1013
|
+
* so a tenant's pickers offer its own tenants/tags/attributes and never another's. */
|
|
1014
|
+
runValueFacets(axis, query, opts) {
|
|
1015
|
+
return this.request({
|
|
1016
|
+
kind: "runValueFacets",
|
|
1017
|
+
axis,
|
|
1018
|
+
query,
|
|
1019
|
+
opts
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1009
1022
|
/** Round-trips to the operator, which scopes the result to this tenant's own `@<tenant>` groups. */
|
|
1010
1023
|
workerHealth() {
|
|
1011
1024
|
return this.request({
|
|
@@ -1156,6 +1169,15 @@ var RetentionPoller = class {
|
|
|
1156
1169
|
onModuleDestroy() {
|
|
1157
1170
|
if (this.timer) clearInterval(this.timer);
|
|
1158
1171
|
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Prune one round.
|
|
1174
|
+
*
|
|
1175
|
+
* Never rejects, for the same reason {@link TimerPoller.poll} does not: it is awaited inside
|
|
1176
|
+
* `onApplicationBootstrap` (so a rejection propagates into the host's `bootstrap()` and kills the
|
|
1177
|
+
* process) and fired as `void` on the interval (so a rejection is an unhandled one, also fatal).
|
|
1178
|
+
* Pruning history is the least urgent thing this package does — a `pruneTerminalRuns` that fails
|
|
1179
|
+
* on a locked table must cost this sweep and nothing more.
|
|
1180
|
+
*/
|
|
1159
1181
|
async sweep() {
|
|
1160
1182
|
if (this.sweeping) return;
|
|
1161
1183
|
const retention = this.options.retention;
|
|
@@ -1172,6 +1194,8 @@ var RetentionPoller = class {
|
|
|
1172
1194
|
if (deleted < batchSize) break;
|
|
1173
1195
|
}
|
|
1174
1196
|
}
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
console.error("[nestjs-durable] retention sweep failed; retrying next interval:", error);
|
|
1175
1199
|
} finally {
|
|
1176
1200
|
this.sweeping = false;
|
|
1177
1201
|
}
|
|
@@ -1235,6 +1259,19 @@ var RunRequestResponder = class {
|
|
|
1235
1259
|
}
|
|
1236
1260
|
};
|
|
1237
1261
|
}
|
|
1262
|
+
if (body.kind === "runValueFacets") {
|
|
1263
|
+
const data = await this.gateway.runValueFacets(body.axis, {
|
|
1264
|
+
...body.query,
|
|
1265
|
+
namespace: msg.tenant
|
|
1266
|
+
}, body.opts);
|
|
1267
|
+
return {
|
|
1268
|
+
requestId: msg.requestId,
|
|
1269
|
+
result: {
|
|
1270
|
+
ok: true,
|
|
1271
|
+
data
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1238
1275
|
if (body.kind === "workerHealth") {
|
|
1239
1276
|
const all = await this.gateway.workerHealth();
|
|
1240
1277
|
const data = all.filter((h) => h.group.endsWith(`@${msg.tenant}`));
|
|
@@ -1401,6 +1438,16 @@ var StoreRunGateway = class {
|
|
|
1401
1438
|
})));
|
|
1402
1439
|
}
|
|
1403
1440
|
/**
|
|
1441
|
+
* The distinct values behind a console's pickers, straight off the store's enumeration. A store
|
|
1442
|
+
* without one answers `[]` rather than falling back to a listing: unlike the facet counts above —
|
|
1443
|
+
* where a full listing is at least the RIGHT answer expensively — a picker built from an unbounded
|
|
1444
|
+
* scan would be the same unbounded read on every keystroke, for a control whose free-text entry
|
|
1445
|
+
* still works without it.
|
|
1446
|
+
*/
|
|
1447
|
+
async runValueFacets(axis, query, opts) {
|
|
1448
|
+
return this.store.runValueFacets?.(axis, query, opts) ?? [];
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1404
1451
|
* Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/
|
|
1405
1452
|
* paginated run listing (e.g. "which of MY suspended runs are stuck at a breakpoint") without
|
|
1406
1453
|
* re-deriving `listRuns`' waiter scan or querying `durable_step_checkpoints` directly. Mirrors
|
|
@@ -1543,7 +1590,11 @@ var TimerPoller = class {
|
|
|
1543
1590
|
}
|
|
1544
1591
|
async onApplicationBootstrap() {
|
|
1545
1592
|
if (!isDrivingOperator(this.options)) return;
|
|
1546
|
-
this.unsubscribeEnqueued = this.engine.onEnqueued((runId) =>
|
|
1593
|
+
this.unsubscribeEnqueued = this.engine.onEnqueued((runId) => {
|
|
1594
|
+
void this.engine.runOne(runId).catch((error) => {
|
|
1595
|
+
console.error(`[nestjs-durable] could not run enqueued run ${runId}:`, error);
|
|
1596
|
+
});
|
|
1597
|
+
});
|
|
1547
1598
|
await this.poll();
|
|
1548
1599
|
const intervalMs = this.options.timerPollMs ?? 1e3;
|
|
1549
1600
|
if (intervalMs > 0) {
|
|
@@ -1555,22 +1606,45 @@ var TimerPoller = class {
|
|
|
1555
1606
|
if (this.timer) clearInterval(this.timer);
|
|
1556
1607
|
this.unsubscribeEnqueued?.();
|
|
1557
1608
|
}
|
|
1609
|
+
/**
|
|
1610
|
+
* One sweep of the engine's recovery work.
|
|
1611
|
+
*
|
|
1612
|
+
* **This method never rejects, and that is a hard requirement rather than defensiveness.** It has
|
|
1613
|
+
* exactly two callers and a rejection out of either one kills the process: on boot it is awaited
|
|
1614
|
+
* inside `onApplicationBootstrap`, so the rejection propagates through `NestApplication.init()`
|
|
1615
|
+
* into the host's `bootstrap()`; on the interval it is a fire-and-forget `void`, so it surfaces as
|
|
1616
|
+
* an unhandled rejection. Both are fatal, and both re-occur on the next boot with the same state,
|
|
1617
|
+
* so a single unrecoverable run crash-loops every worker in the deployment — taking down every
|
|
1618
|
+
* other workflow with it. A poll that goes wrong must cost this tick, not the pod.
|
|
1619
|
+
*
|
|
1620
|
+
* Each sweep is isolated separately because they are independent: a store that fails
|
|
1621
|
+
* `recoverIncomplete` says nothing about whether durable timers should still fire, and skipping
|
|
1622
|
+
* the rest of the tick would silently widen one subsystem's outage into all of them.
|
|
1623
|
+
*/
|
|
1558
1624
|
async poll() {
|
|
1559
1625
|
if (this.polling) return;
|
|
1560
1626
|
this.polling = true;
|
|
1561
1627
|
try {
|
|
1562
|
-
await this.engine.runPending();
|
|
1563
|
-
await this.engine.recoverIncomplete();
|
|
1564
|
-
await this.engine.resumeDueTimers();
|
|
1565
|
-
await this.engine.sweepTimeouts();
|
|
1628
|
+
await this.sweep("runPending", () => this.engine.runPending());
|
|
1629
|
+
await this.sweep("recoverIncomplete", () => this.engine.recoverIncomplete());
|
|
1630
|
+
await this.sweep("resumeDueTimers", () => this.engine.resumeDueTimers());
|
|
1631
|
+
await this.sweep("sweepTimeouts", () => this.engine.sweepTimeouts());
|
|
1566
1632
|
const schedules = this.options.schedules;
|
|
1567
1633
|
if (schedules && schedules.length > 0) {
|
|
1568
|
-
await runSchedules(this.engine, schedules, Date.now());
|
|
1634
|
+
await this.sweep("schedules", () => runSchedules(this.engine, schedules, Date.now()));
|
|
1569
1635
|
}
|
|
1570
1636
|
} finally {
|
|
1571
1637
|
this.polling = false;
|
|
1572
1638
|
}
|
|
1573
1639
|
}
|
|
1640
|
+
/** Run one sweep, absorbing and reporting its failure — see {@link poll}. */
|
|
1641
|
+
async sweep(name, run) {
|
|
1642
|
+
try {
|
|
1643
|
+
await run();
|
|
1644
|
+
} catch (error) {
|
|
1645
|
+
console.error(`[nestjs-durable] timer poll: ${name} failed; continuing:`, error);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1574
1648
|
};
|
|
1575
1649
|
TimerPoller = _ts_decorate9([
|
|
1576
1650
|
Injectable9(),
|