@codemation/eventbus-redis 0.0.43 → 0.1.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/CHANGELOG.md +22 -0
- package/dist/index.d.ts +29 -102
- package/dist/index.js +47 -1
- package/package.json +2 -2
- package/src/SignedRunEventRelayPublisher.ts +49 -0
- package/src/index.ts +3 -0
- package/src/signedRelay.types.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @codemation/eventbus-redis
|
|
2
2
|
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#281](https://github.com/MadeRelevant/codemation/pull/281) [`b0b1b5e`](https://github.com/MadeRelevant/codemation/commit/b0b1b5e945e93d43230c9e2bfe935ea59f48e10e) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(relay): publish HMAC-signed run events to redis for the control-plane realtime relay ([#318](https://github.com/MadeRelevant/codemation/issues/318))
|
|
8
|
+
|
|
9
|
+
In managed mode (a paired workspace running the redis-backed event bus), the host now relays every `RunEvent` to a workspace+workflow-keyed redis channel `codemation.ws.<workspaceId>.run-events.<workflowId>` as an HMAC-signed envelope (`SignedRunEventRelayPublisher`). The signature is keyed by the workspace pairing secret so the control plane can verify authenticity and which workspace published the event. This lets run events escape the originating HTTP request so externally-triggered (loop/webhook) runs can stream live to the canvas. Part of scale-to-zero ([#261](https://github.com/MadeRelevant/codemation/issues/261)); the control-plane subscribe/verify/SSE side and the canvas subscription land separately.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#247](https://github.com/MadeRelevant/codemation/pull/247) [`bfdd759`](https://github.com/MadeRelevant/codemation/commit/bfdd7590b4903676b223c2f302b9bcd0f4a4583c) Thanks [@cblokland90](https://github.com/cblokland90)! - Remove all human-written comments from TypeScript source files and add `codemation/no-comments` ESLint rule to enforce self-describing code going forward.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`60f23a3`](https://github.com/MadeRelevant/codemation/commit/60f23a37660cdda34e3d61acca8b2bf581a9db0e), [`a5457bb`](https://github.com/MadeRelevant/codemation/commit/a5457bb58edafec01e85cdcf6d30f9ca54521e27), [`510e0d8`](https://github.com/MadeRelevant/codemation/commit/510e0d8a5f137a4fe10f43cbf46e40d05fea4977), [`d5f49f7`](https://github.com/MadeRelevant/codemation/commit/d5f49f7f80deafc109dbc29f16ff0d571ee8591a), [`364507a`](https://github.com/MadeRelevant/codemation/commit/364507a9fba45fd66cb208d5a41ee1f3bdc68311), [`0dc8a9d`](https://github.com/MadeRelevant/codemation/commit/0dc8a9def376e4cd1821f3b92b0f887720e0c842), [`3facadf`](https://github.com/MadeRelevant/codemation/commit/3facadf3bc09d21f33e698213a521fb64168d5dd), [`c08cf33`](https://github.com/MadeRelevant/codemation/commit/c08cf33ead44fec64d3d43b9294fccfdf6674156), [`597fa8f`](https://github.com/MadeRelevant/codemation/commit/597fa8f697300d8d861a02c11e9819892f7947fa), [`b15f8c6`](https://github.com/MadeRelevant/codemation/commit/b15f8c68125e3ec3082bab8d79414871f942e409), [`bfdd759`](https://github.com/MadeRelevant/codemation/commit/bfdd7590b4903676b223c2f302b9bcd0f4a4583c), [`0a681b3`](https://github.com/MadeRelevant/codemation/commit/0a681b357afd7b15ba3925788c732fd439d8e6b0), [`ab5185a`](https://github.com/MadeRelevant/codemation/commit/ab5185a839118868eda1aab42b82f7a921037f37), [`fd188a2`](https://github.com/MadeRelevant/codemation/commit/fd188a2bac65c86dd62cf2f540034769c5bc0ac7), [`f2aa0c4`](https://github.com/MadeRelevant/codemation/commit/f2aa0c42b2f9d2e9eb7bc4f3393f74342f7ef460), [`cf2b146`](https://github.com/MadeRelevant/codemation/commit/cf2b146b452317e1ccaa1dfeaaa1a0e153181e30)]:
|
|
16
|
+
- @codemation/core@0.15.0
|
|
17
|
+
|
|
18
|
+
## 0.0.44
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [[`675260a`](https://github.com/MadeRelevant/codemation/commit/675260aa7a30c55a34b5a6107ad2222937f4a769), [`13f9f11`](https://github.com/MadeRelevant/codemation/commit/13f9f11454855f44cd0849ea7e8f0c60f4a28964), [`675260a`](https://github.com/MadeRelevant/codemation/commit/675260aa7a30c55a34b5a6107ad2222937f4a769), [`675260a`](https://github.com/MadeRelevant/codemation/commit/675260aa7a30c55a34b5a6107ad2222937f4a769), [`0881b46`](https://github.com/MadeRelevant/codemation/commit/0881b4676d2db29f36415d9b47709128d4c15e0e)]:
|
|
23
|
+
- @codemation/core@0.14.0
|
|
24
|
+
|
|
3
25
|
## 0.0.43
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import IORedis from "ioredis";
|
|
2
2
|
|
|
3
3
|
//#region ../core/src/contracts/testTriggerTypes.d.ts
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Identifier minted by the host (or in-memory test runner) for one execution of a test suite.
|
|
7
|
-
* One TestSuiteRun produces N child workflow runs, one per item yielded by `generateItems`.
|
|
8
|
-
*/
|
|
9
4
|
type TestSuiteRunId = string;
|
|
10
5
|
//#endregion
|
|
11
6
|
//#region ../core/src/contracts/baseTypes.d.ts
|
|
12
|
-
/**
|
|
13
|
-
* Minimal base types that have no dependencies on other contracts.
|
|
14
|
-
* Used by credentialTypes, workflowTypes, and other contract layers
|
|
15
|
-
* to avoid circular dependencies.
|
|
16
|
-
*/
|
|
17
7
|
type WorkflowId = string;
|
|
18
8
|
type NodeId = string;
|
|
19
9
|
type OutputPortKey = string;
|
|
@@ -22,15 +12,7 @@ type PersistedTokenId = string;
|
|
|
22
12
|
type NodeConnectionName = string;
|
|
23
13
|
//#endregion
|
|
24
14
|
//#region ../core/src/events/runEvents.d.ts
|
|
25
|
-
/**
|
|
26
|
-
* Outcome of a single test case (one workflow run dispatched by the test-suite orchestrator).
|
|
27
|
-
* - `running`: workflow still in flight
|
|
28
|
-
* - `succeeded`: workflow completed AND all assertions passed (or no assertions)
|
|
29
|
-
* - `failed`: workflow failed OR (workflow completed but ≥1 assertion failed)
|
|
30
|
-
* - `errored` / `cancelled`: workflow itself errored or was cancelled
|
|
31
|
-
*/
|
|
32
15
|
type TestCaseRunStatus = "running" | "succeeded" | "failed" | "errored" | "cancelled";
|
|
33
|
-
/** Aggregate outcome of a TestSuiteRun. */
|
|
34
16
|
type TestSuiteRunStatus = "succeeded" | "failed" | "partial" | "errored" | "cancelled";
|
|
35
17
|
type RunEvent = Readonly<{
|
|
36
18
|
kind: "runCreated";
|
|
@@ -138,42 +120,24 @@ interface RunEventBus {
|
|
|
138
120
|
}
|
|
139
121
|
//#endregion
|
|
140
122
|
//#region ../core/src/contracts/runTypes.d.ts
|
|
141
|
-
/**
|
|
142
|
-
* Test-suite linkage for a run. When set, this run was started by a TestSuiteOrchestrator
|
|
143
|
-
* as one test case inside a TestSuiteRun. The `IsTestRun` node and host-side persisters key
|
|
144
|
-
* off the presence of this field. Subworkflow runs inherit it from their parent run.
|
|
145
|
-
*/
|
|
146
123
|
interface RunTestContext {
|
|
147
124
|
readonly testSuiteRunId: string;
|
|
148
125
|
readonly testCaseIndex: number;
|
|
149
|
-
/**
|
|
150
|
-
* Optional human-friendly label for this test case (e.g. an email subject when fixtures
|
|
151
|
-
* are loaded from a mailbox). Resolved per item by `TestTrigger.caseLabel(item)` if set,
|
|
152
|
-
* persisted on `Run.test_case_label` so the Tests-tab tree-table can show "RFQ for batch 14"
|
|
153
|
-
* instead of "run_1777755971399_bbb86beac1396".
|
|
154
|
-
*/
|
|
155
126
|
readonly testCaseLabel?: string;
|
|
156
127
|
}
|
|
157
128
|
interface RunExecutionOptions {
|
|
158
|
-
/** Run-intent override: force the inline scheduler and bypass node-level offload decisions. */
|
|
159
129
|
localOnly?: boolean;
|
|
160
|
-
/** Marks runs started from webhook handling so orchestration can apply webhook-specific continuation rules. */
|
|
161
130
|
webhook?: boolean;
|
|
162
131
|
mode?: "manual" | "debug";
|
|
163
132
|
sourceWorkflowId?: WorkflowId;
|
|
164
133
|
sourceRunId?: RunId;
|
|
165
134
|
derivedFromRunId?: RunId;
|
|
166
135
|
isMutable?: boolean;
|
|
167
|
-
/** Set by the engine for this run: 0 = root, 1 = first child subworkflow, … */
|
|
168
136
|
subworkflowDepth?: number;
|
|
169
|
-
/** Effective cap after engine policy merge (successful node completions per run). */
|
|
170
137
|
maxNodeActivations?: number;
|
|
171
|
-
/** Effective cap after engine policy merge (subworkflow nesting). */
|
|
172
138
|
maxSubworkflowDepth?: number;
|
|
173
|
-
/** Present iff started by a TestSuiteOrchestrator; propagates to subworkflow runs via {@link ParentExecutionRef.testContext}. */
|
|
174
139
|
testContext?: RunTestContext;
|
|
175
140
|
}
|
|
176
|
-
/** Engine-owned counters persisted with the run (worker-safe). */
|
|
177
141
|
interface EngineRunCounters {
|
|
178
142
|
completedNodeActivations: number;
|
|
179
143
|
}
|
|
@@ -195,7 +159,6 @@ interface PersistedWorkflowSnapshotNode {
|
|
|
195
159
|
tokenName?: string;
|
|
196
160
|
configTokenName?: string;
|
|
197
161
|
config: unknown;
|
|
198
|
-
/** Pre-computed static configuration summary; populated by WorkflowSnapshotCodec. */
|
|
199
162
|
inspectorSummary?: ReadonlyArray<Readonly<{
|
|
200
163
|
label: string;
|
|
201
164
|
value: string;
|
|
@@ -206,9 +169,7 @@ interface PersistedWorkflowSnapshot {
|
|
|
206
169
|
name: string;
|
|
207
170
|
nodes: ReadonlyArray<PersistedWorkflowSnapshotNode>;
|
|
208
171
|
edges: ReadonlyArray<Edge>;
|
|
209
|
-
/** When the snapshot was built from a live workflow definition that configured a workflow error handler. */
|
|
210
172
|
workflowErrorHandlerConfigured?: boolean;
|
|
211
|
-
/** Connection metadata for child nodes not in the execution graph (e.g. AI agent attachments). */
|
|
212
173
|
connections?: ReadonlyArray<WorkflowNodeConnection>;
|
|
213
174
|
}
|
|
214
175
|
type PinnedNodeOutputsByPort = Readonly<Record<OutputPortKey, Items>>;
|
|
@@ -256,18 +217,9 @@ interface NodeExecutionSnapshot {
|
|
|
256
217
|
inputsByPort?: NodeInputsByPort;
|
|
257
218
|
outputs?: NodeOutputs;
|
|
258
219
|
error?: NodeExecutionError;
|
|
259
|
-
/**
|
|
260
|
-
* When the node is a SubWorkflow invocation, the run id of the child run it spawned.
|
|
261
|
-
* Populated after the child run completes so the UI can deep-link to that specific execution.
|
|
262
|
-
*/
|
|
263
220
|
childRunId?: RunId;
|
|
264
221
|
}
|
|
265
|
-
/** Stable id for a single connection invocation row in {@link ConnectionInvocationRecord}. */
|
|
266
222
|
type ConnectionInvocationId = string;
|
|
267
|
-
/**
|
|
268
|
-
* One logical LLM or tool call under an owning workflow node (e.g. AI agent).
|
|
269
|
-
* The owning node defines what {@link managedInput} and {@link managedOutput} contain.
|
|
270
|
-
*/
|
|
271
223
|
interface ConnectionInvocationRecord {
|
|
272
224
|
readonly invocationId: ConnectionInvocationId;
|
|
273
225
|
readonly runId: RunId;
|
|
@@ -278,24 +230,18 @@ interface ConnectionInvocationRecord {
|
|
|
278
230
|
readonly status: NodeExecutionStatus;
|
|
279
231
|
readonly managedInput?: JsonValue;
|
|
280
232
|
readonly managedOutput?: JsonValue;
|
|
281
|
-
/** Short human-readable description of what this invocation is doing right now (e.g. `"calling search_messages"`). Rendered as a sub-line on the canvas node card. */
|
|
282
233
|
readonly statusLabel?: string;
|
|
283
|
-
/** Stable identifier for the thing this invocation acts on (e.g. an MCP tool name like `"search_messages"`). Persists across status transitions so the inspector can show it on completed/failed entries too. Connection nodes that ARE the tool (e.g. node-backed agent tools) leave this unset — the parent node id already identifies the subject. */
|
|
284
234
|
readonly subjectName?: string;
|
|
285
235
|
readonly error?: NodeExecutionError;
|
|
286
236
|
readonly queuedAt?: string;
|
|
287
237
|
readonly startedAt?: string;
|
|
288
238
|
readonly finishedAt?: string;
|
|
289
239
|
readonly updatedAt: string;
|
|
290
|
-
/** Per-item iteration id minted by the engine when this invocation occurred inside a runnable node's per-item loop. */
|
|
291
240
|
readonly iterationId?: NodeIterationId;
|
|
292
|
-
/** Item index (0-based) of the iteration that produced this invocation. */
|
|
293
241
|
readonly itemIndex?: number;
|
|
294
|
-
/** When set, this invocation was produced inside a sub-agent triggered by the named parent invocation. */
|
|
295
242
|
readonly parentInvocationId?: ConnectionInvocationId;
|
|
296
243
|
}
|
|
297
244
|
type RunStatus = "running" | "pending" | "completed" | "failed" | "suspended" | "halted";
|
|
298
|
-
/** Reason a run transitioned to {@link RunStatus} `"halted"`. */
|
|
299
245
|
type RunHaltReason = "hitl-rejected" | "hitl-timeout" | "hitl-cancelled";
|
|
300
246
|
interface PendingNodeExecution {
|
|
301
247
|
runId: RunId;
|
|
@@ -309,70 +255,42 @@ interface PendingNodeExecution {
|
|
|
309
255
|
batchId?: string;
|
|
310
256
|
enqueuedAt: string;
|
|
311
257
|
}
|
|
312
|
-
/** One persisted suspension entry per suspended item. */
|
|
313
258
|
interface PersistedSuspensionEntry {
|
|
314
|
-
/** Opaque task identifier (UUID v4). */
|
|
315
259
|
readonly taskId: string;
|
|
316
260
|
readonly nodeId: NodeId;
|
|
317
261
|
readonly activationId: NodeActivationId;
|
|
318
262
|
readonly itemIndex: number;
|
|
319
|
-
/** SHA-256 hex digest of the decision schema JSON (for schema-drift detection). */
|
|
320
263
|
readonly decisionSchemaHash: string;
|
|
321
|
-
/** Serialized return value from `SuspensionRequest.deliver` (stored on the HumanTask row). */
|
|
322
264
|
readonly deliveryRef: JsonValue;
|
|
323
|
-
/** ISO timestamp when the task expires. */
|
|
324
265
|
readonly timeoutAt: string;
|
|
325
266
|
readonly onTimeout: "halt" | "auto-accept";
|
|
326
267
|
}
|
|
327
|
-
/**
|
|
328
|
-
* When a node is re-activated after suspension, the engine writes the resume context here
|
|
329
|
-
* so `NodeExecutionRequestHandlerService` can splice `resumeContext` into ctx.
|
|
330
|
-
* Cleared once the re-activation is consumed.
|
|
331
|
-
*/
|
|
332
268
|
interface PendingResumeEntry {
|
|
333
269
|
readonly activationId: NodeActivationId;
|
|
334
270
|
readonly nodeId: NodeId;
|
|
335
|
-
/**
|
|
336
|
-
* Typed as `unknown` here to avoid a circular import between runTypes ↔ runtimeTypes.
|
|
337
|
-
* `NodeExecutionRequestHandlerService` casts this to `ResumeContext` from runtimeTypes.
|
|
338
|
-
*/
|
|
339
271
|
readonly resumeContext: unknown;
|
|
340
272
|
}
|
|
341
273
|
interface PersistedRunState {
|
|
342
274
|
runId: RunId;
|
|
343
275
|
workflowId: WorkflowId;
|
|
344
276
|
startedAt: string;
|
|
345
|
-
/** Canonical terminal time for listings and retention when persisted on the run root. */
|
|
346
277
|
finishedAt?: string;
|
|
347
|
-
/** Optimistic concurrency / CAS on the run aggregate (repository may increment on save). */
|
|
348
278
|
revision?: number;
|
|
349
279
|
parent?: ParentExecutionRef;
|
|
350
280
|
executionOptions?: RunExecutionOptions;
|
|
351
281
|
control?: PersistedRunControlState;
|
|
352
282
|
workflowSnapshot?: PersistedWorkflowSnapshot;
|
|
353
283
|
mutableState?: PersistedMutableRunState;
|
|
354
|
-
/** Frozen at createRun from workflow + runtime defaults for prune/storage decisions. */
|
|
355
284
|
policySnapshot?: PersistedRunPolicySnapshot;
|
|
356
|
-
/** Successful node completions so far (for activation budget). */
|
|
357
285
|
engineCounters?: EngineRunCounters;
|
|
358
286
|
status: RunStatus;
|
|
359
|
-
/** Populated when `status === "halted"` to discriminate why the run was halted. */
|
|
360
287
|
reason?: RunHaltReason;
|
|
361
288
|
pending?: PendingNodeExecution;
|
|
362
289
|
queue: RunQueueEntry[];
|
|
363
290
|
outputsByNode: Record<NodeId, NodeOutputs>;
|
|
364
291
|
nodeSnapshotsByNodeId: Record<NodeId, NodeExecutionSnapshot>;
|
|
365
|
-
/** Append-only history of connection invocations (LLM/tool) nested under owning nodes. */
|
|
366
292
|
connectionInvocations?: ReadonlyArray<ConnectionInvocationRecord>;
|
|
367
|
-
/**
|
|
368
|
-
* One entry per outstanding HITL suspension (per-item).
|
|
369
|
-
* Present and non-empty iff `status === "suspended"`.
|
|
370
|
-
*/
|
|
371
293
|
suspension?: ReadonlyArray<PersistedSuspensionEntry>;
|
|
372
|
-
/**
|
|
373
|
-
* Written by `resumeRun()` so `NodeExecutionRequestHandlerService` can splice `resumeContext`
|
|
374
|
-
* into the ctx when re-executing the suspended node. Cleared once consumed.
|
|
375
|
-
*/
|
|
376
294
|
pendingResume?: PendingResumeEntry;
|
|
377
295
|
}
|
|
378
296
|
//#endregion
|
|
@@ -394,11 +312,6 @@ interface Edge {
|
|
|
394
312
|
input: InputPortKey;
|
|
395
313
|
};
|
|
396
314
|
}
|
|
397
|
-
/**
|
|
398
|
-
* Named connection from a parent node to child nodes that exist in {@link WorkflowDefinition.nodes}
|
|
399
|
-
* but are not traversed by the main execution graph. Parents are commonly executable nodes, but may
|
|
400
|
-
* also be connection-owned nodes for recursive agent attachments.
|
|
401
|
-
*/
|
|
402
315
|
interface WorkflowNodeConnection {
|
|
403
316
|
readonly parentNodeId: NodeId;
|
|
404
317
|
readonly connectionName: NodeConnectionName;
|
|
@@ -436,30 +349,16 @@ type Items<TJson = unknown> = ReadonlyArray<Item<TJson>>;
|
|
|
436
349
|
type NodeOutputs = Partial<Record<OutputPortKey, Items>>;
|
|
437
350
|
type RunId = string;
|
|
438
351
|
type NodeActivationId = string;
|
|
439
|
-
/**
|
|
440
|
-
* One per-item iteration of a runnable node's execute loop. Refines `NodeActivationId` for
|
|
441
|
-
* per-item connection invocations and telemetry. Undefined when the executing node is a batch
|
|
442
|
-
* node or trigger that does not iterate items.
|
|
443
|
-
*/
|
|
444
352
|
type NodeIterationId = string;
|
|
445
353
|
interface ParentExecutionRef {
|
|
446
354
|
runId: RunId;
|
|
447
355
|
workflowId: WorkflowId;
|
|
448
356
|
nodeId: NodeId;
|
|
449
|
-
/** Subworkflow depth of the **spawning** run (0 = root). Passed when starting a child run. */
|
|
450
357
|
subworkflowDepth?: number;
|
|
451
|
-
/** Effective max node activations from the parent run (propagated to child policy merge). */
|
|
452
358
|
engineMaxNodeActivations?: number;
|
|
453
|
-
/** Effective max subworkflow depth from the parent run (propagated to child policy merge). */
|
|
454
359
|
engineMaxSubworkflowDepth?: number;
|
|
455
|
-
/**
|
|
456
|
-
* Test-suite linkage inherited by the child subworkflow run. Set by whichever node
|
|
457
|
-
* spawns the subworkflow when its own `ctx.testContext` is present, so assertions
|
|
458
|
-
* emitted inside a subworkflow land under the correct parent test case.
|
|
459
|
-
*/
|
|
460
360
|
testContext?: RunTestContext;
|
|
461
361
|
}
|
|
462
|
-
/** Whether to persist run execution data after the workflow finishes. */
|
|
463
362
|
type WorkflowStoragePolicyMode = "ALL" | "SUCCESS" | "ERROR" | "NEVER";
|
|
464
363
|
interface PersistedRunPolicySnapshot {
|
|
465
364
|
readonly retentionSeconds?: number;
|
|
@@ -486,4 +385,32 @@ declare class RedisRunEventBus implements RunEventBus {
|
|
|
486
385
|
private parseEvent;
|
|
487
386
|
}
|
|
488
387
|
//#endregion
|
|
489
|
-
|
|
388
|
+
//#region src/signedRelay.types.d.ts
|
|
389
|
+
declare const SIGNED_RUN_EVENT_CHANNEL_PREFIX = "codemation";
|
|
390
|
+
interface SignedRunEventEnvelope {
|
|
391
|
+
readonly workspaceId: string;
|
|
392
|
+
readonly ts: number;
|
|
393
|
+
readonly sig: string;
|
|
394
|
+
readonly payload: string;
|
|
395
|
+
}
|
|
396
|
+
interface RunEventEnvelopeSigner {
|
|
397
|
+
readonly workspaceId: string;
|
|
398
|
+
sign(payload: string): SignedRunEventEnvelope;
|
|
399
|
+
}
|
|
400
|
+
declare function signedRunEventChannel(channelPrefix: string, workspaceId: string, workflowId: string): string;
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/SignedRunEventRelayPublisher.d.ts
|
|
403
|
+
declare class SignedRunEventRelayPublisher {
|
|
404
|
+
private readonly redisUrl;
|
|
405
|
+
private readonly localBus;
|
|
406
|
+
private readonly signer;
|
|
407
|
+
private publisher;
|
|
408
|
+
private subscription;
|
|
409
|
+
constructor(redisUrl: string, localBus: RunEventBus, signer: RunEventEnvelopeSigner);
|
|
410
|
+
start(): Promise<void>;
|
|
411
|
+
stop(): Promise<void>;
|
|
412
|
+
private relay;
|
|
413
|
+
private ensurePublisher;
|
|
414
|
+
}
|
|
415
|
+
//#endregion
|
|
416
|
+
export { RedisRunEventBus, type RunEventEnvelopeSigner, SIGNED_RUN_EVENT_CHANNEL_PREFIX, type SignedRunEventEnvelope, SignedRunEventRelayPublisher, signedRunEventChannel };
|
package/dist/index.js
CHANGED
|
@@ -61,4 +61,50 @@ var RedisRunEventBus = class {
|
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
//#endregion
|
|
64
|
-
|
|
64
|
+
//#region src/signedRelay.types.ts
|
|
65
|
+
const SIGNED_RUN_EVENT_CHANNEL_PREFIX = "codemation";
|
|
66
|
+
function signedRunEventChannel(channelPrefix, workspaceId, workflowId) {
|
|
67
|
+
return `${channelPrefix}.ws.${workspaceId}.run-events.${workflowId}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/SignedRunEventRelayPublisher.ts
|
|
72
|
+
var SignedRunEventRelayPublisher = class {
|
|
73
|
+
publisher;
|
|
74
|
+
subscription;
|
|
75
|
+
constructor(redisUrl, localBus, signer) {
|
|
76
|
+
this.redisUrl = redisUrl;
|
|
77
|
+
this.localBus = localBus;
|
|
78
|
+
this.signer = signer;
|
|
79
|
+
}
|
|
80
|
+
async start() {
|
|
81
|
+
if (this.subscription) return;
|
|
82
|
+
this.subscription = await this.localBus.subscribe((event) => {
|
|
83
|
+
this.relay(event);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async stop() {
|
|
87
|
+
if (this.subscription) {
|
|
88
|
+
await this.subscription.close();
|
|
89
|
+
this.subscription = void 0;
|
|
90
|
+
}
|
|
91
|
+
if (this.publisher) {
|
|
92
|
+
this.publisher.disconnect();
|
|
93
|
+
this.publisher = void 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async relay(event) {
|
|
97
|
+
const payload = JSON.stringify(event);
|
|
98
|
+
const envelope = this.signer.sign(payload);
|
|
99
|
+
const channel = signedRunEventChannel(SIGNED_RUN_EVENT_CHANNEL_PREFIX, this.signer.workspaceId, event.workflowId);
|
|
100
|
+
await this.ensurePublisher().publish(channel, JSON.stringify(envelope));
|
|
101
|
+
}
|
|
102
|
+
ensurePublisher() {
|
|
103
|
+
if (this.publisher) return this.publisher;
|
|
104
|
+
this.publisher = new IORedis(this.redisUrl);
|
|
105
|
+
return this.publisher;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
export { RedisRunEventBus, SIGNED_RUN_EVENT_CHANNEL_PREFIX, SignedRunEventRelayPublisher, signedRunEventChannel };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemation/eventbus-redis",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"ioredis": "^5.7.0",
|
|
32
|
-
"@codemation/core": "0.
|
|
32
|
+
"@codemation/core": "0.15.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^25.3.5",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { RunEvent, RunEventBus, RunEventSubscription } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
import IORedis from "ioredis";
|
|
4
|
+
|
|
5
|
+
import type { RunEventEnvelopeSigner } from "./signedRelay.types";
|
|
6
|
+
import { signedRunEventChannel, SIGNED_RUN_EVENT_CHANNEL_PREFIX } from "./signedRelay.types";
|
|
7
|
+
|
|
8
|
+
export class SignedRunEventRelayPublisher {
|
|
9
|
+
private publisher: IORedis | undefined;
|
|
10
|
+
private subscription: RunEventSubscription | undefined;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly redisUrl: string,
|
|
14
|
+
private readonly localBus: RunEventBus,
|
|
15
|
+
private readonly signer: RunEventEnvelopeSigner,
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
async start(): Promise<void> {
|
|
19
|
+
if (this.subscription) return;
|
|
20
|
+
this.subscription = await this.localBus.subscribe((event) => {
|
|
21
|
+
void this.relay(event);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async stop(): Promise<void> {
|
|
26
|
+
if (this.subscription) {
|
|
27
|
+
await this.subscription.close();
|
|
28
|
+
this.subscription = undefined;
|
|
29
|
+
}
|
|
30
|
+
if (this.publisher) {
|
|
31
|
+
this.publisher.disconnect();
|
|
32
|
+
this.publisher = undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async relay(event: RunEvent): Promise<void> {
|
|
37
|
+
const payload = JSON.stringify(event);
|
|
38
|
+
const envelope = this.signer.sign(payload);
|
|
39
|
+
const channel = signedRunEventChannel(SIGNED_RUN_EVENT_CHANNEL_PREFIX, this.signer.workspaceId, event.workflowId);
|
|
40
|
+
await this.ensurePublisher().publish(channel, JSON.stringify(envelope));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private ensurePublisher(): IORedis {
|
|
44
|
+
if (this.publisher) return this.publisher;
|
|
45
|
+
// eslint-disable-next-line codemation/no-manual-di-new -- IORedis is an external connection client, not a DI-managed class (same pattern as RedisRunEventBus).
|
|
46
|
+
this.publisher = new IORedis(this.redisUrl);
|
|
47
|
+
return this.publisher;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
1
|
export { RedisRunEventBus } from "./RedisRunEventBusRegistry";
|
|
2
|
+
export { SignedRunEventRelayPublisher } from "./SignedRunEventRelayPublisher";
|
|
3
|
+
export { signedRunEventChannel, SIGNED_RUN_EVENT_CHANNEL_PREFIX } from "./signedRelay.types";
|
|
4
|
+
export type { RunEventEnvelopeSigner, SignedRunEventEnvelope } from "./signedRelay.types";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const SIGNED_RUN_EVENT_CHANNEL_PREFIX = "codemation";
|
|
2
|
+
|
|
3
|
+
export interface SignedRunEventEnvelope {
|
|
4
|
+
readonly workspaceId: string;
|
|
5
|
+
readonly ts: number;
|
|
6
|
+
readonly sig: string;
|
|
7
|
+
readonly payload: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RunEventEnvelopeSigner {
|
|
11
|
+
readonly workspaceId: string;
|
|
12
|
+
sign(payload: string): SignedRunEventEnvelope;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function signedRunEventChannel(channelPrefix: string, workspaceId: string, workflowId: string): string {
|
|
16
|
+
return `${channelPrefix}.ws.${workspaceId}.run-events.${workflowId}`;
|
|
17
|
+
}
|