@furystack/redis-task-runner 1.0.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.
Files changed (47) hide show
  1. package/README.md +128 -0
  2. package/esm/cross-process-smoke.spec.d.ts +2 -0
  3. package/esm/cross-process-smoke.spec.d.ts.map +1 -0
  4. package/esm/cross-process-smoke.spec.js +463 -0
  5. package/esm/cross-process-smoke.spec.js.map +1 -0
  6. package/esm/define-redis-task-runner.d.ts +38 -0
  7. package/esm/define-redis-task-runner.d.ts.map +1 -0
  8. package/esm/define-redis-task-runner.js +60 -0
  9. package/esm/define-redis-task-runner.js.map +1 -0
  10. package/esm/index.d.ts +7 -0
  11. package/esm/index.d.ts.map +1 -0
  12. package/esm/index.js +4 -0
  13. package/esm/index.js.map +1 -0
  14. package/esm/multi-service-smoke.spec.d.ts +2 -0
  15. package/esm/multi-service-smoke.spec.d.ts.map +1 -0
  16. package/esm/multi-service-smoke.spec.js +468 -0
  17. package/esm/multi-service-smoke.spec.js.map +1 -0
  18. package/esm/redis-queue-adapter.d.ts +125 -0
  19. package/esm/redis-queue-adapter.d.ts.map +1 -0
  20. package/esm/redis-queue-adapter.js +566 -0
  21. package/esm/redis-queue-adapter.js.map +1 -0
  22. package/esm/redis-queue-adapter.unit.spec.d.ts +2 -0
  23. package/esm/redis-queue-adapter.unit.spec.d.ts.map +1 -0
  24. package/esm/redis-queue-adapter.unit.spec.js +860 -0
  25. package/esm/redis-queue-adapter.unit.spec.js.map +1 -0
  26. package/esm/redis-task-runner.d.ts +33 -0
  27. package/esm/redis-task-runner.d.ts.map +1 -0
  28. package/esm/redis-task-runner.integration.spec.d.ts +2 -0
  29. package/esm/redis-task-runner.integration.spec.d.ts.map +1 -0
  30. package/esm/redis-task-runner.integration.spec.js +536 -0
  31. package/esm/redis-task-runner.integration.spec.js.map +1 -0
  32. package/esm/redis-task-runner.js +43 -0
  33. package/esm/redis-task-runner.js.map +1 -0
  34. package/esm/redis-task-runner.unit.spec.d.ts +2 -0
  35. package/esm/redis-task-runner.unit.spec.d.ts.map +1 -0
  36. package/esm/redis-task-runner.unit.spec.js +97 -0
  37. package/esm/redis-task-runner.unit.spec.js.map +1 -0
  38. package/package.json +63 -0
  39. package/src/cross-process-smoke.spec.ts +459 -0
  40. package/src/define-redis-task-runner.ts +91 -0
  41. package/src/index.ts +8 -0
  42. package/src/multi-service-smoke.spec.ts +511 -0
  43. package/src/redis-queue-adapter.ts +733 -0
  44. package/src/redis-queue-adapter.unit.spec.ts +613 -0
  45. package/src/redis-task-runner.integration.spec.ts +440 -0
  46. package/src/redis-task-runner.ts +67 -0
  47. package/src/redis-task-runner.unit.spec.ts +110 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-redis-task-runner.d.ts","sourceRoot":"","sources":["../src/define-redis-task-runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,EAA+D,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACrH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAErF;;;;GAIG;AACH,MAAM,MAAM,4BAA4B,GAAG,IAAI,CAAC,wBAAwB,EAAE,QAAQ,GAAG,aAAa,CAAC,GAAG;IACpG,MAAM,EAAE,wBAAwB,CAAC,QAAQ,CAAC,CAAA;IAC1C,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,sBAAsB,CAAA;AAE1B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,4BAA4B,KAAG,cAAc,CAAC,UAAU,CAkDtG,CAAA"}
@@ -0,0 +1,60 @@
1
+ import { BlobStore as BlobStoreToken } from '@furystack/blob-store';
2
+ import { CrossNodeBus as CrossNodeBusToken } from '@furystack/cross-node-bus';
3
+ import { TaskDataSet, TaskReplayLogDataSet, TaskRunnerTelemetryToken } from '@furystack/task-runner';
4
+ import { RedisTaskRunner } from './redis-task-runner.js';
5
+ /**
6
+ * Returns a {@link ServiceFactory} for {@link TaskRunner}, backed by
7
+ * Redis Streams. Override the (intentionally throwing) default
8
+ * binding at boot:
9
+ *
10
+ * ```ts
11
+ * const client = createClient({ url: process.env.REDIS_URL })
12
+ * await client.connect()
13
+ *
14
+ * injector.bind(
15
+ * TaskRunner,
16
+ * defineRedisTaskRunner({
17
+ * client,
18
+ * serviceName: 'svc-a',
19
+ * topicPrefix: 'svc-a/',
20
+ * visibilityTimeoutMs: 60_000,
21
+ * }),
22
+ * )
23
+ * ```
24
+ *
25
+ * Caller still owns `client.connect()` / `client.quit()` —
26
+ * the adapter never closes it.
27
+ */
28
+ export const defineRedisTaskRunner = (options) => {
29
+ const { client, serviceName, topicPrefix, consumerGroup, visibilityTimeoutMs, visibilityTimeoutByType, blockTimeoutMs, retryBackoffMs, idempotencyTtlSec, schedulerIntervalMs, concurrencyLimits, reconcilerIntervalMs, sweepIntervalMs, } = options;
30
+ return ({ inject, injector, onDispose }) => {
31
+ const bus = inject(CrossNodeBusToken);
32
+ const blobStore = inject(BlobStoreToken);
33
+ const taskDs = inject(TaskDataSet);
34
+ const replayDs = inject(TaskReplayLogDataSet);
35
+ const telemetry = inject(TaskRunnerTelemetryToken);
36
+ const runner = new RedisTaskRunner({
37
+ injector,
38
+ bus,
39
+ blobStore,
40
+ taskDs,
41
+ replayDs,
42
+ telemetry,
43
+ client,
44
+ serviceName,
45
+ topicPrefix,
46
+ consumerGroup,
47
+ visibilityTimeoutMs,
48
+ visibilityTimeoutByType,
49
+ blockTimeoutMs,
50
+ retryBackoffMs,
51
+ idempotencyTtlSec,
52
+ schedulerIntervalMs,
53
+ concurrencyLimits,
54
+ }, { reconcilerIntervalMs, sweepIntervalMs });
55
+ // eslint-disable-next-line furystack/prefer-using-wrapper -- delegated to onDispose
56
+ onDispose(() => runner[Symbol.dispose]());
57
+ return runner;
58
+ };
59
+ };
60
+ //# sourceMappingURL=define-redis-task-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-redis-task-runner.js","sourceRoot":"","sources":["../src/define-redis-task-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAE,YAAY,IAAI,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAE7E,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,wBAAwB,EAAmB,MAAM,wBAAwB,CAAA;AAErH,OAAO,EAAE,eAAe,EAA+B,MAAM,wBAAwB,CAAA;AAYrF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,OAAqC,EAA8B,EAAE;IACzG,MAAM,EACJ,MAAM,EACN,WAAW,EACX,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,GAChB,GAAG,OAAO,CAAA;IAEX,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;QACzC,MAAM,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAA;QACrC,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,CAAA;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;QAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAA;QAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,wBAAwB,CAAC,CAAA;QAElD,MAAM,MAAM,GAAG,IAAI,eAAe,CAChC;YACE,QAAQ;YACR,GAAG;YACH,SAAS;YACT,MAAM;YACN,QAAQ;YACR,SAAS;YACT,MAAM;YACN,WAAW;YACX,WAAW;YACX,aAAa;YACb,mBAAmB;YACnB,uBAAuB;YACvB,cAAc;YACd,cAAc;YACd,iBAAiB;YACjB,mBAAmB;YACnB,iBAAiB;SAClB,EACD,EAAE,oBAAoB,EAAE,eAAe,EAAE,CAC1C,CAAA;QACD,oFAAoF;QACpF,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACzC,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;AACH,CAAC,CAAA"}
package/esm/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { RedisQueueAdapter } from './redis-queue-adapter.js';
2
+ export type { RedisQueueAdapterOptions } from './redis-queue-adapter.js';
3
+ export { RedisTaskRunner } from './redis-task-runner.js';
4
+ export type { RedisTaskRunnerOptions, RedisTaskRunnerDeps } from './redis-task-runner.js';
5
+ export { defineRedisTaskRunner } from './define-redis-task-runner.js';
6
+ export type { DefineRedisTaskRunnerOptions } from './define-redis-task-runner.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAEzF,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AACrE,YAAY,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAA"}
package/esm/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { RedisQueueAdapter } from './redis-queue-adapter.js';
2
+ export { RedisTaskRunner } from './redis-task-runner.js';
3
+ export { defineRedisTaskRunner } from './define-redis-task-runner.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAG5D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAGxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=multi-service-smoke.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-service-smoke.spec.d.ts","sourceRoot":"","sources":["../src/multi-service-smoke.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,468 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ /**
54
+ * Multi-service smoke (PRD Milestone 6).
55
+ *
56
+ * Two services × two worker pods each, a single Redis broker, an S3
57
+ * (MinIO) blob store, and a per-service `topicPrefix`. Same-process by
58
+ * design — every pod is its own injector with its own Redis clients,
59
+ * `RedisTaskRunner`, `RedisCrossNodeBus`, and Redis-store-backed Task
60
+ * datasets. This mirrors the accepted shape of the cross-node-bus
61
+ * multi-service smoke: cross-process isolation only proves V8 matches
62
+ * injector isolation, which is uninteresting; the value is the full DI +
63
+ * adapter wiring against real infrastructure.
64
+ *
65
+ * Per service, isolation lives in four prefixes sharing one run root so a
66
+ * single `KEYS ${runId}*` sweeps the broker clean:
67
+ * - queue `${runId}:${svc}:q/` (RedisTaskRunner streams / ZSETs)
68
+ * - store `${runId}:${svc}:s/` (RedisStore Task + replay rows)
69
+ * - bus `${runId}:${svc}:bus/` (RedisCrossNodeBus topics)
70
+ * - count `${runId}:cnt:${svc}:` (shared assertion counters)
71
+ * S3 blobs are scoped per service via `keyPrefix` inside one run bucket.
72
+ *
73
+ * Assertions (PRD §13 M6):
74
+ * 1. Tasks submitted to service A run only on service A's workers.
75
+ * 2. Cross-service submission is explicit — B's store never sees A's task.
76
+ * 3. Fleet caps bind fleet-wide across a service's pods.
77
+ * 4. Draining one pod does not lose work (the other pod picks it up).
78
+ * + S3-backed produced blobs round-trip across the fleet.
79
+ * + §4 success metrics (submit-to-claim, progress delivery) measured.
80
+ *
81
+ * Gated on a reachable Redis at `REDIS_URL` (default redis://localhost:6379)
82
+ * and MinIO at `MINIO_URL` (default http://localhost:9000).
83
+ */
84
+ import { randomUUID } from 'node:crypto';
85
+ import { CreateBucketCommand, DeleteBucketCommand, S3Client } from '@aws-sdk/client-s3';
86
+ import { BlobStore } from '@furystack/blob-store';
87
+ import { CrossNodeBus } from '@furystack/cross-node-bus';
88
+ import { createInjector } from '@furystack/inject';
89
+ import { defineRedisCrossNodeBusAdapter, RedisCrossNodeBus } from '@furystack/redis-cross-node-bus';
90
+ import { RedisStore } from '@furystack/redis-store';
91
+ import { defineS3BlobStore } from '@furystack/s3-blob-store';
92
+ import { defineTaskHandler, isTerminalStatus, Task, TaskReplayLogEntry, TaskReplayLogStore, TaskRunner, TaskStore, } from '@furystack/task-runner';
93
+ import { createClient } from 'redis';
94
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
95
+ import { defineRedisTaskRunner } from './define-redis-task-runner.js';
96
+ const REDIS_URL = process.env.REDIS_URL ?? 'redis://localhost:6379';
97
+ const MINIO_URL = process.env.MINIO_URL ?? 'http://localhost:9000';
98
+ const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY ?? 'minioadmin';
99
+ const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY ?? 'minioadmin';
100
+ /** §4 budgets. The targets are 50 ms (submit→claim) and 100 ms (progress);
101
+ * the assert budget is intentionally generous so the smoke does not flake on
102
+ * loaded CI — the real measured p95 is logged for the record either way. */
103
+ const SUBMIT_TO_CLAIM_BUDGET_MS = 1000;
104
+ const PROGRESS_DELIVERY_BUDGET_MS = 1000;
105
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
106
+ const MAX_SCRIPT = `
107
+ local m = tonumber(redis.call('GET', KEYS[1]) or '0')
108
+ local v = tonumber(ARGV[1])
109
+ if v > m then redis.call('SET', KEYS[1], v) end
110
+ return v
111
+ `;
112
+ const evalScript = (client, script, keys, args) => {
113
+ const evalFn = client.eval;
114
+ return evalFn.call(client, script, { keys, arguments: args });
115
+ };
116
+ const percentile = (samples, p) => {
117
+ if (samples.length === 0)
118
+ return Number.NaN;
119
+ const sorted = [...samples].sort((a, b) => a - b);
120
+ const rank = Math.ceil((p / 100) * sorted.length) - 1;
121
+ return sorted[Math.max(0, Math.min(sorted.length - 1, rank))];
122
+ };
123
+ const buildHandlers = (control, ns, podId) => {
124
+ const echo = defineTaskHandler({
125
+ type: 'echo',
126
+ version: 1,
127
+ handler: async (ctx) => {
128
+ await control.set(`${ns}start:${ctx.taskId}`, String(ctx.now().getTime()));
129
+ await control.set(`${ns}ranBy:${ctx.taskId}`, podId);
130
+ await control.incr(`${ns}execBy:${podId}`);
131
+ return { ok: true };
132
+ },
133
+ });
134
+ const capped = defineTaskHandler({
135
+ type: 'capped',
136
+ version: 1,
137
+ handler: async () => {
138
+ const live = await control.incr(`${ns}live`);
139
+ await evalScript(control, MAX_SCRIPT, [`${ns}max`], [String(live)]);
140
+ await sleep(250);
141
+ await control.decr(`${ns}live`);
142
+ },
143
+ });
144
+ const slow = defineTaskHandler({
145
+ type: 'slow',
146
+ version: 1,
147
+ handler: async (ctx) => {
148
+ await control.incr(`${ns}slow:started`);
149
+ await control.set(`${ns}slowBy:${ctx.taskId}`, podId);
150
+ await sleep(700);
151
+ await control.incr(`${ns}slow:done`);
152
+ },
153
+ });
154
+ const blobbed = defineTaskHandler({
155
+ type: 'blobbed',
156
+ version: 1,
157
+ handler: async (ctx) => {
158
+ await control.set(`${ns}start:${ctx.taskId}`, String(ctx.now().getTime()));
159
+ ctx.reportProgress({ percent: 50, meta: { sentAt: ctx.now().getTime() } });
160
+ const key = `out/${ctx.taskId}.txt`;
161
+ await ctx.blobStore.put(key, Buffer.from(`blob-${ctx.taskId}`));
162
+ return { key };
163
+ },
164
+ });
165
+ return [echo, capped, slow, blobbed];
166
+ };
167
+ const setupPod = async (options) => {
168
+ const client = createClient({ url: REDIS_URL });
169
+ const busClient = createClient({ url: REDIS_URL });
170
+ await client.connect();
171
+ await busClient.connect();
172
+ const injector = createInjector();
173
+ injector.bind(BlobStore, defineS3BlobStore({
174
+ client: options.s3,
175
+ bucket: options.bucket,
176
+ keyPrefix: options.prefixes.blob,
177
+ name: `${options.serviceName}-s3`,
178
+ manageLifecycle: false,
179
+ }));
180
+ injector.bind(CrossNodeBus, defineRedisCrossNodeBusAdapter({
181
+ client: busClient,
182
+ serviceName: options.serviceName,
183
+ topicPrefix: options.prefixes.bus,
184
+ nodeId: options.podId,
185
+ }));
186
+ // `as PhysicalStore` bridges the RedisStore `TWriteableData` (id required)
187
+ // vs. the StoreToken's `WithOptionalId` param-variance gap — the same shape
188
+ // `defineStore` accepts via bivariance.
189
+ injector.bind(TaskStore, () => new RedisStore({
190
+ model: Task,
191
+ primaryKey: 'id',
192
+ client,
193
+ keyPrefix: `${options.prefixes.store}t`,
194
+ }));
195
+ injector.bind(TaskReplayLogStore, () => new RedisStore({
196
+ model: TaskReplayLogEntry,
197
+ primaryKey: 'id',
198
+ client,
199
+ keyPrefix: `${options.prefixes.store}r`,
200
+ }));
201
+ injector.bind(TaskRunner, defineRedisTaskRunner({
202
+ client,
203
+ serviceName: options.serviceName,
204
+ topicPrefix: options.prefixes.queue,
205
+ visibilityTimeoutMs: options.visibilityTimeoutMs ?? 60_000,
206
+ blockTimeoutMs: 25,
207
+ retryBackoffMs: 50,
208
+ schedulerIntervalMs: 50,
209
+ reconcilerIntervalMs: 1_000_000,
210
+ sweepIntervalMs: 200,
211
+ concurrencyLimits: options.concurrencyLimits,
212
+ }));
213
+ const runner = injector.get(TaskRunner);
214
+ const worker = runner.registerWorker({
215
+ name: options.podId,
216
+ handlers: buildHandlers(options.control, options.prefixes.count, options.podId),
217
+ concurrency: options.concurrency,
218
+ tags: [],
219
+ compatibleVersions: {},
220
+ });
221
+ return {
222
+ podId: options.podId,
223
+ injector,
224
+ runner,
225
+ client,
226
+ busClient,
227
+ drain: () => worker.drain({ timeoutMs: 5000 }),
228
+ async [Symbol.asyncDispose]() {
229
+ worker[Symbol.dispose]();
230
+ await injector[Symbol.asyncDispose]();
231
+ if (client.isOpen)
232
+ await client.quit();
233
+ if (busClient.isOpen)
234
+ await busClient.quit();
235
+ },
236
+ };
237
+ };
238
+ const setupObserver = async (serviceName, prefixes) => {
239
+ const client = createClient({ url: REDIS_URL });
240
+ await client.connect();
241
+ const injector = createInjector();
242
+ injector.bind(CrossNodeBus, defineRedisCrossNodeBusAdapter({ client, serviceName, topicPrefix: prefixes.bus, nodeId: `${serviceName}-obs` }));
243
+ const bus = injector.get(CrossNodeBus);
244
+ if (!(bus instanceof RedisCrossNodeBus))
245
+ throw new Error('expected RedisCrossNodeBus binding');
246
+ return {
247
+ bus,
248
+ injector,
249
+ client,
250
+ async [Symbol.asyncDispose]() {
251
+ await injector[Symbol.asyncDispose]();
252
+ if (client.isOpen)
253
+ await client.quit();
254
+ },
255
+ };
256
+ };
257
+ const prefixesFor = (runId, svc) => ({
258
+ queue: `${runId}:${svc}:q/`,
259
+ store: `${runId}:${svc}:s/`,
260
+ bus: `${runId}:${svc}:bus/`,
261
+ count: `${runId}:cnt:${svc}:`,
262
+ blob: `${runId}/${svc}/blobs/`,
263
+ });
264
+ const makeS3 = () => new S3Client({
265
+ endpoint: MINIO_URL,
266
+ region: 'us-east-1',
267
+ forcePathStyle: true,
268
+ credentials: { accessKeyId: MINIO_ACCESS_KEY, secretAccessKey: MINIO_SECRET_KEY },
269
+ });
270
+ const cleanupRedis = async (runId) => {
271
+ const client = createClient({ url: REDIS_URL });
272
+ try {
273
+ await client.connect();
274
+ const keys = await client.keys(`${runId}*`);
275
+ if (keys.length > 0)
276
+ await client.del(keys);
277
+ }
278
+ catch {
279
+ // Redis unreachable — the test bodies already failed loudly.
280
+ }
281
+ finally {
282
+ if (client.isOpen)
283
+ await client.quit();
284
+ }
285
+ };
286
+ const num = (value) => (value ? Number(value) : 0);
287
+ const pollStatus = async (runner, taskId, timeoutMs) => {
288
+ const deadline = Date.now() + timeoutMs;
289
+ while (Date.now() < deadline) {
290
+ const task = await runner.get(taskId);
291
+ if (task && isTerminalStatus(task.status))
292
+ return task.status;
293
+ await sleep(60);
294
+ }
295
+ return (await runner.get(taskId))?.status;
296
+ };
297
+ const waitFor = async (predicate, timeoutMs) => {
298
+ const deadline = Date.now() + timeoutMs;
299
+ while (Date.now() < deadline) {
300
+ if (await predicate())
301
+ return;
302
+ await sleep(60);
303
+ }
304
+ throw new Error(`waitFor: timed out after ${timeoutMs}ms`);
305
+ };
306
+ describe('multi-service smoke (PRD M6)', () => {
307
+ let runId;
308
+ let control;
309
+ let s3;
310
+ let bucket;
311
+ let aPods;
312
+ let bPods;
313
+ let aObserver;
314
+ let bObserver;
315
+ let aPrefixes;
316
+ let bPrefixes;
317
+ beforeAll(async () => {
318
+ runId = `m6-${randomUUID().slice(0, 8)}`;
319
+ control = createClient({ url: REDIS_URL });
320
+ await control.connect();
321
+ s3 = makeS3();
322
+ bucket = `furystack-m6-${runId}`;
323
+ await s3.send(new CreateBucketCommand({ Bucket: bucket }));
324
+ aPrefixes = prefixesFor(runId, 'svc-a');
325
+ bPrefixes = prefixesFor(runId, 'svc-b');
326
+ aPods = await Promise.all(['a0', 'a1'].map((podId) => setupPod({
327
+ serviceName: 'svc-a',
328
+ podId,
329
+ prefixes: aPrefixes,
330
+ control,
331
+ s3,
332
+ bucket,
333
+ concurrency: 2,
334
+ concurrencyLimits: { capped: 2 },
335
+ })));
336
+ bPods = await Promise.all(['b0', 'b1'].map((podId) => setupPod({ serviceName: 'svc-b', podId, prefixes: bPrefixes, control, s3, bucket, concurrency: 2 })));
337
+ aObserver = await setupObserver('svc-a', aPrefixes);
338
+ bObserver = await setupObserver('svc-b', bPrefixes);
339
+ // Give the Redis Streams consumer groups + bus cursors a beat to go live.
340
+ await sleep(300);
341
+ }, 40_000);
342
+ afterAll(async () => {
343
+ await Promise.all([...(aPods ?? []), ...(bPods ?? [])].map((p) => p[Symbol.asyncDispose]()));
344
+ if (aObserver)
345
+ await aObserver[Symbol.asyncDispose]();
346
+ if (bObserver)
347
+ await bObserver[Symbol.asyncDispose]();
348
+ if (control?.isOpen)
349
+ await control.quit();
350
+ if (s3 && bucket) {
351
+ try {
352
+ await s3.send(new DeleteBucketCommand({ Bucket: bucket }));
353
+ }
354
+ catch {
355
+ // Bucket may still hold sweeper-orphaned objects; best-effort.
356
+ }
357
+ s3.destroy();
358
+ }
359
+ await cleanupRedis(runId);
360
+ }, 40_000);
361
+ it('runs each task only on the submitting service, and B never sees A tasks', async () => {
362
+ const aTasks = await Promise.all(Array.from({ length: 6 }, () => aPods[0].runner.submit({ type: 'echo', payload: {}, handlerVersion: 1 })));
363
+ const bTasks = await Promise.all(Array.from({ length: 6 }, () => bPods[0].runner.submit({ type: 'echo', payload: {}, handlerVersion: 1 })));
364
+ for (const task of aTasks)
365
+ expect(await pollStatus(aPods[0].runner, task.id, 20_000)).toBe('succeeded');
366
+ for (const task of bTasks)
367
+ expect(await pollStatus(bPods[0].runner, task.id, 20_000)).toBe('succeeded');
368
+ for (const task of aTasks) {
369
+ const ranBy = await control.get(`${aPrefixes.count}ranBy:${task.id}`);
370
+ expect(['a0', 'a1']).toContain(ranBy);
371
+ // Cross-service: service B's store has no record of an A-submitted task.
372
+ expect(await bPods[0].runner.get(task.id)).toBeUndefined();
373
+ }
374
+ for (const task of bTasks) {
375
+ const ranBy = await control.get(`${bPrefixes.count}ranBy:${task.id}`);
376
+ expect(['b0', 'b1']).toContain(ranBy);
377
+ expect(await aPods[0].runner.get(task.id)).toBeUndefined();
378
+ }
379
+ }, 60_000);
380
+ it('enforces a fleet-wide concurrency cap across a service\u2019s pods', async () => {
381
+ await control.del(`${aPrefixes.count}max`);
382
+ await control.del(`${aPrefixes.count}live`);
383
+ const tasks = await Promise.all(Array.from({ length: 6 }, () => aPods[0].runner.submit({ type: 'capped', payload: {}, handlerVersion: 1 })));
384
+ for (const task of tasks)
385
+ expect(await pollStatus(aPods[0].runner, task.id, 25_000)).toBe('succeeded');
386
+ const maxConcurrent = num(await control.get(`${aPrefixes.count}max`));
387
+ expect(maxConcurrent).toBeLessThanOrEqual(2);
388
+ expect(maxConcurrent).toBe(2);
389
+ }, 60_000);
390
+ it('round-trips an S3-backed produced blob and measures \u00a74 metrics', async () => {
391
+ const env_1 = { stack: [], error: void 0, hasError: false };
392
+ try {
393
+ await aObserver.bus.whenReady(`tasks/progress/blobbed`);
394
+ const progressLags = [];
395
+ const _sub = __addDisposableResource(env_1, aObserver.bus.subscribe(`tasks/progress/blobbed`, (msg) => {
396
+ const payload = msg.payload;
397
+ if (payload.kind !== 'progress')
398
+ return;
399
+ const sentAt = payload.meta?.sentAt;
400
+ if (typeof sentAt === 'number')
401
+ progressLags.push(Date.now() - sentAt);
402
+ }), false);
403
+ const SAMPLES = 24;
404
+ const submitTimes = new Map();
405
+ const blobbedTasks = await Promise.all(Array.from({ length: SAMPLES }, async () => {
406
+ const task = await aPods[0].runner.submit({ type: 'blobbed', payload: {}, handlerVersion: 1 });
407
+ submitTimes.set(task.id, Date.now());
408
+ return task;
409
+ }));
410
+ for (const task of blobbedTasks)
411
+ expect(await pollStatus(aPods[0].runner, task.id, 30_000)).toBe('succeeded');
412
+ // S3 round-trip: read a produced blob straight from the service's store.
413
+ const sample = blobbedTasks[0];
414
+ const finished = await aPods[0].runner.get(sample.id);
415
+ const producedKey = (finished?.result).key;
416
+ const blobStore = aPods[1].injector.get(BlobStore);
417
+ const got = await blobStore.get(producedKey);
418
+ const body = await new Response(got.stream).text();
419
+ expect(body).toBe(`blob-${sample.id}`);
420
+ // §4 metric: submit → claim (proxied by submit → handler entry).
421
+ const claimLags = [];
422
+ for (const task of blobbedTasks) {
423
+ const startedAt = num(await control.get(`${aPrefixes.count}start:${task.id}`));
424
+ const submittedAt = submitTimes.get(task.id);
425
+ if (startedAt > 0 && submittedAt !== undefined)
426
+ claimLags.push(startedAt - submittedAt);
427
+ }
428
+ // Wait briefly for any in-flight progress frames to land on the observer.
429
+ await waitFor(async () => Promise.resolve(progressLags.length >= 1), 5_000);
430
+ const claimP95 = percentile(claimLags, 95);
431
+ const progressP95 = percentile(progressLags, 95);
432
+ console.log(`[M6 §4 metrics] submit→claim: p50=${percentile(claimLags, 50)}ms p95=${claimP95}ms (target <50ms) | ` +
433
+ `progress delivery: p50=${percentile(progressLags, 50)}ms p95=${progressP95}ms (target <100ms) | ` +
434
+ `samples claim=${claimLags.length} progress=${progressLags.length}`);
435
+ expect(claimLags.length).toBeGreaterThan(0);
436
+ expect(progressLags.length).toBeGreaterThan(0);
437
+ expect(claimP95).toBeLessThan(SUBMIT_TO_CLAIM_BUDGET_MS);
438
+ expect(progressP95).toBeLessThan(PROGRESS_DELIVERY_BUDGET_MS);
439
+ }
440
+ catch (e_1) {
441
+ env_1.error = e_1;
442
+ env_1.hasError = true;
443
+ }
444
+ finally {
445
+ __disposeResources(env_1);
446
+ }
447
+ }, 60_000);
448
+ it('does not lose work when a pod drains mid-flight', async () => {
449
+ await control.del(`${aPrefixes.count}slow:started`);
450
+ await control.del(`${aPrefixes.count}slow:done`);
451
+ const inFlight = await aPods[0].runner.submit({ type: 'slow', payload: {}, handlerVersion: 1 });
452
+ await waitFor(async () => num(await control.get(`${aPrefixes.count}slow:started`)) >= 1, 15_000);
453
+ const ranBy = await control.get(`${aPrefixes.count}slowBy:${inFlight.id}`);
454
+ const drainingPod = aPods.find((p) => p.podId === ranBy) ?? aPods[0];
455
+ // Drain the pod holding the in-flight task; submit a second task that the
456
+ // sibling pod must pick up.
457
+ const drained = drainingPod.drain();
458
+ const queued = await aPods[0].runner.submit({ type: 'slow', payload: {}, handlerVersion: 1 });
459
+ await drained;
460
+ // The in-flight task completed despite the drain.
461
+ expect((await aPods[0].runner.get(inFlight.id))?.status).toBe('succeeded');
462
+ // The queued task is picked up by the other pod and finishes.
463
+ expect(await pollStatus(aPods[0].runner, queued.id, 25_000)).toBe('succeeded');
464
+ const queuedRanBy = await control.get(`${aPrefixes.count}slowBy:${queued.id}`);
465
+ expect(queuedRanBy).not.toBe(drainingPod.podId);
466
+ }, 60_000);
467
+ });
468
+ //# sourceMappingURL=multi-service-smoke.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-service-smoke.spec.js","sourceRoot":"","sources":["../src/multi-service-smoke.spec.ts"],"names":[],"mappings":"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"}