@dudousxd/nestjs-durable 0.30.2 → 0.32.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.cjs +136 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -1
- package/dist/index.d.ts +106 -1
- package/dist/index.js +135 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -56,6 +56,7 @@ __export(index_exports, {
|
|
|
56
56
|
ThinWorkflowRegistrar: () => ThinWorkflowRegistrar,
|
|
57
57
|
WORKFLOW_METADATA: () => WORKFLOW_METADATA,
|
|
58
58
|
Workflow: () => Workflow,
|
|
59
|
+
WorkflowEngine: () => import_nestjs_durable_core13.WorkflowEngine,
|
|
59
60
|
WorkflowService: () => WorkflowService,
|
|
60
61
|
entityConfigFor: () => entityConfigFor,
|
|
61
62
|
getDurableStepMeta: () => getDurableStepMeta,
|
|
@@ -87,6 +88,7 @@ function Workflow(options) {
|
|
|
87
88
|
executionTimeout: options.executionTimeout,
|
|
88
89
|
inputSchema: options.inputSchema,
|
|
89
90
|
validateInput: options.validateInput,
|
|
91
|
+
searchAttributes: options.searchAttributes,
|
|
90
92
|
onEvent: options.onEvent,
|
|
91
93
|
debounce: options.debounce,
|
|
92
94
|
batch: options.batch
|
|
@@ -501,6 +503,9 @@ function unavailableRunGateway() {
|
|
|
501
503
|
listRuns(_query) {
|
|
502
504
|
return tenantGatewayUnavailable("listRuns");
|
|
503
505
|
},
|
|
506
|
+
waitingFor(_runIds) {
|
|
507
|
+
return tenantGatewayUnavailable("waitingFor");
|
|
508
|
+
},
|
|
504
509
|
workerHealth() {
|
|
505
510
|
return tenantGatewayUnavailable("workerHealth");
|
|
506
511
|
},
|
|
@@ -918,6 +923,14 @@ var ProxyRunGateway = class {
|
|
|
918
923
|
kind: "workerHealth"
|
|
919
924
|
});
|
|
920
925
|
}
|
|
926
|
+
/** Bulk, one request for the whole id list (like `listRuns`, not one request per id). The operator
|
|
927
|
+
* filters the reply to runs this tenant actually owns (see `RunRequestResponder`). */
|
|
928
|
+
waitingFor(runIds) {
|
|
929
|
+
return this.request({
|
|
930
|
+
kind: "waitingFor",
|
|
931
|
+
runIds
|
|
932
|
+
});
|
|
933
|
+
}
|
|
921
934
|
cancel(runId, opts) {
|
|
922
935
|
return this.request(opts === void 0 ? {
|
|
923
936
|
kind: "cancel",
|
|
@@ -1131,6 +1144,27 @@ var RunRequestResponder = class {
|
|
|
1131
1144
|
}
|
|
1132
1145
|
};
|
|
1133
1146
|
}
|
|
1147
|
+
if (body.kind === "waitingFor") {
|
|
1148
|
+
const all = await this.gateway.waitingFor(body.runIds);
|
|
1149
|
+
const owned = await Promise.all(Object.entries(all).map(async ([runId, waiting]) => {
|
|
1150
|
+
const runDetail = await this.gateway.getRunDetail(runId);
|
|
1151
|
+
return runDetail && runDetail.run.namespace === msg.tenant ? [
|
|
1152
|
+
runId,
|
|
1153
|
+
waiting
|
|
1154
|
+
] : void 0;
|
|
1155
|
+
}));
|
|
1156
|
+
const data = {};
|
|
1157
|
+
for (const entry of owned) {
|
|
1158
|
+
if (entry) data[entry[0]] = entry[1];
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
requestId: msg.requestId,
|
|
1162
|
+
result: {
|
|
1163
|
+
ok: true,
|
|
1164
|
+
data
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1134
1168
|
const detail = await this.gateway.getRunDetail(body.runId);
|
|
1135
1169
|
if (detail && detail.run.namespace !== msg.tenant) {
|
|
1136
1170
|
return {
|
|
@@ -1249,6 +1283,36 @@ var StoreRunGateway = class {
|
|
|
1249
1283
|
} : run;
|
|
1250
1284
|
});
|
|
1251
1285
|
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/
|
|
1288
|
+
* paginated run listing (e.g. "which of MY suspended runs are stuck at a breakpoint") without
|
|
1289
|
+
* re-deriving `listRuns`' waiter scan or querying `durable_step_checkpoints` directly. Mirrors
|
|
1290
|
+
* `listRuns`' waiting computation (the SAME bulk signal-waiter scan + `resolveRunWaiting`), but ALSO
|
|
1291
|
+
* bulk-fetches the currently-suspended runs to check real status: `engine.cancel` (the non-compensate
|
|
1292
|
+
* path) doesn't clear a run's signal waiter row, so a cancelled run can leave an ORPHANED waiter
|
|
1293
|
+
* behind — trusting waiter presence alone would wrongly report a terminal run as still waiting.
|
|
1294
|
+
* Two bulk scans total (never one query per requested id), same as `listRuns`.
|
|
1295
|
+
*/
|
|
1296
|
+
async waitingFor(runIds) {
|
|
1297
|
+
if (runIds.length === 0) return {};
|
|
1298
|
+
const idSet = new Set(runIds);
|
|
1299
|
+
const [suspended, waiters] = await Promise.all([
|
|
1300
|
+
this.store.listRuns({
|
|
1301
|
+
statuses: [
|
|
1302
|
+
"suspended"
|
|
1303
|
+
]
|
|
1304
|
+
}),
|
|
1305
|
+
this.store.listSignalWaiters("")
|
|
1306
|
+
]);
|
|
1307
|
+
const waiterByRun = (0, import_nestjs_durable_core8.indexWaitersByRun)(waiters);
|
|
1308
|
+
const result = {};
|
|
1309
|
+
for (const run of suspended) {
|
|
1310
|
+
if (!idSet.has(run.id)) continue;
|
|
1311
|
+
const waiting = (0, import_nestjs_durable_core8.resolveRunWaiting)(run, waiterByRun);
|
|
1312
|
+
if (waiting) result[run.id] = waiting;
|
|
1313
|
+
}
|
|
1314
|
+
return result;
|
|
1315
|
+
}
|
|
1252
1316
|
/** Every group the engine knows about — unscoped. A tenant proxy's request is scoped by the
|
|
1253
1317
|
* `RunRequestResponder` (to the requester's `@<tenant>` groups); the operator's own UI sees all. */
|
|
1254
1318
|
workerHealth() {
|
|
@@ -1540,6 +1604,7 @@ var WorkflowRegistrar = class {
|
|
|
1540
1604
|
singleton: meta.singleton,
|
|
1541
1605
|
executionTimeout: meta.executionTimeout,
|
|
1542
1606
|
validateInput,
|
|
1607
|
+
searchAttributesSchema: meta.searchAttributes,
|
|
1543
1608
|
onEvent: getOnEvents(meta, workflowCtor),
|
|
1544
1609
|
eventBatch,
|
|
1545
1610
|
// Uniform dispatch (opt-in): when an in-app worker is configured, register the body GROUP-SERVED
|
|
@@ -1772,6 +1837,55 @@ function assertValidRole(options) {
|
|
|
1772
1837
|
}
|
|
1773
1838
|
}
|
|
1774
1839
|
__name(assertValidRole, "assertValidRole");
|
|
1840
|
+
function assertValidTopology(options) {
|
|
1841
|
+
const topology = options.topology;
|
|
1842
|
+
if (topology === void 0) return;
|
|
1843
|
+
if (topology.role === "control-plane") {
|
|
1844
|
+
if (options.store === void 0 || options.transport === void 0 && options.transports === void 0) {
|
|
1845
|
+
throw new Error("topology: { role: 'control-plane' } needs `store` AND (`transport` or `transports`).\nA control-plane node is the durable operator: it owns the state store and dispatches runs to workers over a transport \u2014 without both, there is nothing for it to operate.");
|
|
1846
|
+
}
|
|
1847
|
+
if (options.partition !== void 0) {
|
|
1848
|
+
throw new Error("topology: { role: 'control-plane' } forbids `partition`.\npartition is the WORKER queue-routing suffix \u2014 on a control-plane node, runs are dispatched to tenant partitions via each run's namespace, not via this option. Set `partition` (or `topology: { role: 'tenant', tenant }`) on the WORKER node instead.");
|
|
1849
|
+
}
|
|
1850
|
+
if (topology.tenant !== void 0 && options.namespace !== void 0 && options.namespace !== topology.tenant) {
|
|
1851
|
+
throw new Error(`topology: { role: 'control-plane', tenant: '${topology.tenant}' } conflicts with \`namespace: '${options.namespace}'\`.
|
|
1852
|
+
tenant maps 1:1 onto namespace (the operator's poll-scoping axis) \u2014 set only \`tenant\`, or set \`namespace\` to the same value.`);
|
|
1853
|
+
}
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
if (options.connection === void 0) {
|
|
1857
|
+
throw new Error(`topology: { role: 'tenant', tenant: '${topology.tenant}' } needs \`connection\`.
|
|
1858
|
+
A tenant is a store-less worker: it connects to the broker directly to pull its own partition of tasks instead of polling a store it does not have.`);
|
|
1859
|
+
}
|
|
1860
|
+
if (options.store !== void 0) {
|
|
1861
|
+
throw new Error("topology: { role: 'tenant' } forbids `store`.\nA tenant is store-less BY DEFINITION \u2014 a `store` present means you actually wanted `topology: { role: 'control-plane' }` (the store-owning role).");
|
|
1862
|
+
}
|
|
1863
|
+
if (options.namespace !== void 0) {
|
|
1864
|
+
throw new Error("topology: { role: 'tenant' } forbids `namespace`.\nnamespace is the OPERATOR's poll-scoping axis (which runs a control-plane instance drives/recovers/resumes) \u2014 a tenant worker has no poll loop for it to scope. Use this preset's `tenant` field for the worker's own routing axis instead.");
|
|
1865
|
+
}
|
|
1866
|
+
if (options.partition !== void 0 && options.partition !== topology.tenant) {
|
|
1867
|
+
throw new Error(`topology: { role: 'tenant', tenant: '${topology.tenant}' } conflicts with \`partition: '${options.partition}'\`.
|
|
1868
|
+
tenant maps 1:1 onto partition (the worker queue-routing suffix) \u2014 set only \`tenant\`, or set \`partition\` to the same value.`);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
__name(assertValidTopology, "assertValidTopology");
|
|
1872
|
+
function resolveTopology(options) {
|
|
1873
|
+
const topology = options.topology;
|
|
1874
|
+
if (topology === void 0) return options;
|
|
1875
|
+
if (topology.role === "control-plane") {
|
|
1876
|
+
if (topology.tenant === void 0 || options.namespace !== void 0) return options;
|
|
1877
|
+
return {
|
|
1878
|
+
...options,
|
|
1879
|
+
namespace: topology.tenant
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
if (options.partition !== void 0) return options;
|
|
1883
|
+
return {
|
|
1884
|
+
...options,
|
|
1885
|
+
partition: topology.tenant
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
__name(resolveTopology, "resolveTopology");
|
|
1775
1889
|
var RunGatewayBootstrap = class RunGatewayBootstrap2 {
|
|
1776
1890
|
static {
|
|
1777
1891
|
__name(this, "RunGatewayBootstrap");
|
|
@@ -1832,10 +1946,10 @@ var DurableModule = class _DurableModule {
|
|
|
1832
1946
|
__name(this, "DurableModule");
|
|
1833
1947
|
}
|
|
1834
1948
|
static forRoot(options) {
|
|
1835
|
-
|
|
1949
|
+
_DurableModule.assertValid(options);
|
|
1836
1950
|
return _DurableModule.build({
|
|
1837
1951
|
provide: import_nestjs_durable_core12.DURABLE_OPTIONS_CANONICAL,
|
|
1838
|
-
useValue: options
|
|
1952
|
+
useValue: resolveTopology(options)
|
|
1839
1953
|
});
|
|
1840
1954
|
}
|
|
1841
1955
|
static forRootAsync(options) {
|
|
@@ -1843,12 +1957,26 @@ var DurableModule = class _DurableModule {
|
|
|
1843
1957
|
provide: import_nestjs_durable_core12.DURABLE_OPTIONS_CANONICAL,
|
|
1844
1958
|
useFactory: /* @__PURE__ */ __name(async (...args) => {
|
|
1845
1959
|
const resolved = await options.useFactory(...args);
|
|
1846
|
-
|
|
1847
|
-
return resolved;
|
|
1960
|
+
_DurableModule.assertValid(resolved);
|
|
1961
|
+
return resolveTopology(resolved);
|
|
1848
1962
|
}, "useFactory"),
|
|
1849
1963
|
inject: options.inject ?? []
|
|
1850
1964
|
});
|
|
1851
1965
|
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Validates role/axis constraints, in resolution order. When {@link DurableModuleOptions.topology}
|
|
1968
|
+
* is set, it OWNS validation — {@link assertValidTopology}'s own store/transport/connection checks
|
|
1969
|
+
* are strictly stronger than {@link assertValidRole}'s, so the topology-specific, axis-teaching
|
|
1970
|
+
* message is what a `topology`-opted-in consumer sees (not the older generic one). Falls back to
|
|
1971
|
+
* {@link assertValidRole} unchanged when `topology` is absent — zero behavior change.
|
|
1972
|
+
*/
|
|
1973
|
+
static assertValid(options) {
|
|
1974
|
+
if (options.topology !== void 0) {
|
|
1975
|
+
assertValidTopology(options);
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
assertValidRole(options);
|
|
1979
|
+
}
|
|
1852
1980
|
static build(optionsProvider) {
|
|
1853
1981
|
return {
|
|
1854
1982
|
module: _DurableModule,
|
|
@@ -1990,6 +2118,9 @@ var DurableModule = class _DurableModule {
|
|
|
1990
2118
|
DurableModule = _ts_decorate12([
|
|
1991
2119
|
(0, import_common12.Module)({})
|
|
1992
2120
|
], DurableModule);
|
|
2121
|
+
|
|
2122
|
+
// src/index.ts
|
|
2123
|
+
var import_nestjs_durable_core13 = require("@dudousxd/nestjs-durable-core");
|
|
1993
2124
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1994
2125
|
0 && (module.exports = {
|
|
1995
2126
|
CONTEXT_ACCESSOR,
|
|
@@ -2027,6 +2158,7 @@ DurableModule = _ts_decorate12([
|
|
|
2027
2158
|
ThinWorkflowRegistrar,
|
|
2028
2159
|
WORKFLOW_METADATA,
|
|
2029
2160
|
Workflow,
|
|
2161
|
+
WorkflowEngine,
|
|
2030
2162
|
WorkflowService,
|
|
2031
2163
|
entityConfigFor,
|
|
2032
2164
|
getDurableStepMeta,
|