@hotmeshio/hotmesh 0.22.2 → 0.22.4
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/build/index.d.ts +1 -0
- package/build/package.json +3 -1
- package/build/services/activities/hook.js +34 -5
- package/build/services/durable/client.d.ts +12 -0
- package/build/services/durable/client.js +13 -1
- package/build/services/durable/worker.d.ts +10 -0
- package/build/services/durable/worker.js +33 -0
- package/build/services/escalations/client.d.ts +69 -12
- package/build/services/escalations/client.js +137 -19
- package/build/services/hotmesh/index.d.ts +9 -0
- package/build/services/hotmesh/index.js +37 -0
- package/build/services/store/providers/postgres/kvtables.js +165 -6
- package/build/services/store/providers/postgres/postgres.d.ts +12 -16
- package/build/services/store/providers/postgres/postgres.js +279 -207
- package/build/types/durable.d.ts +11 -0
- package/build/types/hmsh_escalations.d.ts +71 -0
- package/build/types/hotmesh.d.ts +17 -0
- package/build/types/index.d.ts +1 -0
- package/build/types/system_events.d.ts +178 -0
- package/build/types/system_events.js +104 -0
- package/index.ts +1 -0
- package/package.json +3 -2
package/build/types/durable.d.ts
CHANGED
|
@@ -395,6 +395,12 @@ type WorkflowDataType = {
|
|
|
395
395
|
type Connection = ProviderConfig | ProvidersConfig;
|
|
396
396
|
type ClientConfig = {
|
|
397
397
|
connection: Connection;
|
|
398
|
+
/**
|
|
399
|
+
* Optional system-event sink. When set, `client.escalations.*` operations
|
|
400
|
+
* call `events.publish` post-commit from the invoking process. Wires the
|
|
401
|
+
* same hook as `HotMeshConfig.events` for direct-client callers.
|
|
402
|
+
*/
|
|
403
|
+
events?: import('./system_events').EventsConfig;
|
|
398
404
|
};
|
|
399
405
|
type Registry = {
|
|
400
406
|
[key: string]: Function;
|
|
@@ -442,6 +448,11 @@ type WorkerConfig = {
|
|
|
442
448
|
user: string;
|
|
443
449
|
password: string;
|
|
444
450
|
};
|
|
451
|
+
/**
|
|
452
|
+
* Optional system-event sink. When set, the worker fires `events.publish`
|
|
453
|
+
* on `system.worker.{taskQueue}.started` and `system.worker.{taskQueue}.stopped`.
|
|
454
|
+
*/
|
|
455
|
+
events?: import('./system_events').EventsConfig;
|
|
445
456
|
};
|
|
446
457
|
type FindWhereQuery = {
|
|
447
458
|
field: string;
|
|
@@ -54,6 +54,8 @@ export interface EscalationEntry {
|
|
|
54
54
|
trace_id: string | null;
|
|
55
55
|
span_id: string | null;
|
|
56
56
|
expires_at: Date | null;
|
|
57
|
+
/** Nullable passthrough column — populated when downstream needs task-level context. */
|
|
58
|
+
task_id: string | null;
|
|
57
59
|
created_at: Date;
|
|
58
60
|
updated_at: Date;
|
|
59
61
|
/** Computed by list(): true when the row is claimable (no active assignee or expired claim). */
|
|
@@ -68,6 +70,7 @@ export interface EscalationEntry {
|
|
|
68
70
|
export type ClaimEscalationResult = {
|
|
69
71
|
ok: true;
|
|
70
72
|
entry: EscalationEntry;
|
|
73
|
+
isExtension: boolean;
|
|
71
74
|
} | {
|
|
72
75
|
ok: false;
|
|
73
76
|
reason: 'not-found' | 'conflict';
|
|
@@ -89,18 +92,21 @@ export type ClaimByMetadataResult = {
|
|
|
89
92
|
};
|
|
90
93
|
export type ResolveEscalationResult = {
|
|
91
94
|
ok: true;
|
|
95
|
+
entry: EscalationEntry;
|
|
92
96
|
} | {
|
|
93
97
|
ok: false;
|
|
94
98
|
reason: 'not-found' | 'already-resolved' | 'already-cancelled';
|
|
95
99
|
};
|
|
96
100
|
export type ReleaseEscalationResult = {
|
|
97
101
|
ok: true;
|
|
102
|
+
entry: EscalationEntry;
|
|
98
103
|
} | {
|
|
99
104
|
ok: false;
|
|
100
105
|
reason: 'not-found' | 'wrong-assignee';
|
|
101
106
|
};
|
|
102
107
|
export type CancelEscalationResult = {
|
|
103
108
|
ok: true;
|
|
109
|
+
entry: EscalationEntry;
|
|
104
110
|
} | {
|
|
105
111
|
ok: false;
|
|
106
112
|
reason: 'not-found' | 'already-terminal';
|
|
@@ -119,11 +125,51 @@ export interface ListEscalationsParams {
|
|
|
119
125
|
originId?: string;
|
|
120
126
|
/** When true, returns only rows without an active claim. When false, returns only actively claimed rows. */
|
|
121
127
|
available?: boolean;
|
|
128
|
+
/** Exact priority match. */
|
|
129
|
+
priority?: number;
|
|
130
|
+
/** JSONB containment filter — rows whose `metadata` contains all provided keys/values. */
|
|
131
|
+
metadata?: Record<string, unknown>;
|
|
132
|
+
/** Filter by a set of UUIDs. */
|
|
133
|
+
ids?: string[];
|
|
134
|
+
/** Filter by `task_id` column. */
|
|
135
|
+
taskId?: string;
|
|
122
136
|
sortBy?: 'created_at' | 'priority' | 'updated_at';
|
|
123
137
|
sortOrder?: 'asc' | 'desc';
|
|
138
|
+
/**
|
|
139
|
+
* Multi-column sort. When provided, supersedes `sortBy`/`sortOrder`.
|
|
140
|
+
* Columns are applied left to right.
|
|
141
|
+
*/
|
|
142
|
+
orderBy?: Array<{
|
|
143
|
+
column: 'priority' | 'created_at' | 'updated_at' | 'resolved_at' | 'role' | 'type';
|
|
144
|
+
direction: 'asc' | 'desc';
|
|
145
|
+
}>;
|
|
124
146
|
limit?: number;
|
|
125
147
|
offset?: number;
|
|
126
148
|
}
|
|
149
|
+
export interface StatsEscalationsParams {
|
|
150
|
+
namespace?: string;
|
|
151
|
+
/** RBAC scope — when an empty array is provided, all counts are zero. */
|
|
152
|
+
roles?: string[];
|
|
153
|
+
/** Counting window for created/resolved. Default: '24h'. */
|
|
154
|
+
period?: '1h' | '24h' | '7d' | '30d';
|
|
155
|
+
}
|
|
156
|
+
export interface EscalationStats {
|
|
157
|
+
pending: number;
|
|
158
|
+
claimed: number;
|
|
159
|
+
created: number;
|
|
160
|
+
resolved: number;
|
|
161
|
+
by_role: Array<{
|
|
162
|
+
role: string;
|
|
163
|
+
pending: number;
|
|
164
|
+
claimed: number;
|
|
165
|
+
}>;
|
|
166
|
+
by_type: Array<{
|
|
167
|
+
type: string;
|
|
168
|
+
pending: number;
|
|
169
|
+
claimed: number;
|
|
170
|
+
resolved: number;
|
|
171
|
+
}>;
|
|
172
|
+
}
|
|
127
173
|
export interface CreateEscalationParams {
|
|
128
174
|
namespace?: string;
|
|
129
175
|
appId?: string;
|
|
@@ -144,6 +190,7 @@ export interface CreateEscalationParams {
|
|
|
144
190
|
createdBy?: string;
|
|
145
191
|
traceId?: string;
|
|
146
192
|
spanId?: string;
|
|
193
|
+
taskId?: string;
|
|
147
194
|
escalationPayload?: Record<string, unknown>;
|
|
148
195
|
metadata?: Record<string, unknown>;
|
|
149
196
|
envelope?: Record<string, unknown>;
|
|
@@ -161,6 +208,7 @@ export interface UpdateEscalationParams {
|
|
|
161
208
|
description?: string;
|
|
162
209
|
priority?: number;
|
|
163
210
|
role?: string;
|
|
211
|
+
taskId?: string;
|
|
164
212
|
/** Merged into existing metadata (keys overwritten, others preserved) */
|
|
165
213
|
metadata?: Record<string, unknown>;
|
|
166
214
|
/** Replaces existing envelope */
|
|
@@ -195,6 +243,8 @@ export interface ClaimByMetadataParams {
|
|
|
195
243
|
assignee?: string;
|
|
196
244
|
durationMinutes?: number;
|
|
197
245
|
roles?: string[];
|
|
246
|
+
/** Merged (not replaced) into the claimed row's metadata in the same atomic UPDATE. */
|
|
247
|
+
metadata?: Record<string, unknown>;
|
|
198
248
|
}
|
|
199
249
|
export interface ReleaseEscalationParams {
|
|
200
250
|
id: string;
|
|
@@ -219,6 +269,27 @@ export interface EscalateToRoleParams {
|
|
|
219
269
|
targetRole: string;
|
|
220
270
|
namespace?: string;
|
|
221
271
|
}
|
|
272
|
+
export interface ClaimManyParams {
|
|
273
|
+
ids: string[];
|
|
274
|
+
namespace?: string;
|
|
275
|
+
assignee: string;
|
|
276
|
+
durationMinutes?: number;
|
|
277
|
+
}
|
|
278
|
+
export interface EscalateManyToRoleParams {
|
|
279
|
+
ids: string[];
|
|
280
|
+
namespace?: string;
|
|
281
|
+
targetRole: string;
|
|
282
|
+
}
|
|
283
|
+
export interface UpdateManyPriorityParams {
|
|
284
|
+
ids: string[];
|
|
285
|
+
namespace?: string;
|
|
286
|
+
priority: number;
|
|
287
|
+
}
|
|
288
|
+
export interface ResolveManyParams {
|
|
289
|
+
ids: string[];
|
|
290
|
+
namespace?: string;
|
|
291
|
+
resolverPayload?: Record<string, unknown>;
|
|
292
|
+
}
|
|
222
293
|
/**
|
|
223
294
|
* Full-fidelity migration params. Extends `CreateEscalationParams` with:
|
|
224
295
|
* - `id` (required) — preserves the original UUID; no auto-generation
|
package/build/types/hotmesh.d.ts
CHANGED
|
@@ -309,6 +309,23 @@ type HotMeshConfig = {
|
|
|
309
309
|
taskQueue?: string;
|
|
310
310
|
engine?: HotMeshEngine;
|
|
311
311
|
workers?: HotMeshWorker[];
|
|
312
|
+
/**
|
|
313
|
+
* Optional system-event sink. When provided, the engine calls
|
|
314
|
+
* `events.publish` post-commit for each durable transition it performs
|
|
315
|
+
* (escalation lifecycle, engine start/stop, deploy). Fire-and-forget.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* const hotMesh = await HotMesh.init({
|
|
320
|
+
* appId: 'myapp',
|
|
321
|
+
* engine: { connection: { class: Postgres, options: { ... } } },
|
|
322
|
+
* events: {
|
|
323
|
+
* publish: (e) => nats.publish(e.type, JSON.stringify(e)),
|
|
324
|
+
* },
|
|
325
|
+
* });
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
events?: import('./system_events').EventsConfig;
|
|
312
329
|
};
|
|
313
330
|
type HotMeshGraph = {
|
|
314
331
|
/**
|
package/build/types/index.d.ts
CHANGED
|
@@ -26,3 +26,4 @@ export { context, Context, Counter, Meter, metrics, propagation, SpanContext, Sp
|
|
|
26
26
|
export { WorkListTaskType } from './task';
|
|
27
27
|
export { TransitionMatch, TransitionRule, Transitions } from './transition';
|
|
28
28
|
export { ConditionQueueConfig, EscalationEntry, ClaimEscalationResult, ClaimByMetadataResult, ReleaseEscalationResult, ResolveEscalationResult, CancelEscalationResult, ListEscalationsParams, CreateEscalationParams, UpdateEscalationParams, AppendMilestonesParams, ClaimEscalationParams, ClaimByMetadataParams, ReleaseEscalationParams, ResolveEscalationParams, ResolveByMetadataParams, EscalateToRoleParams, MigrateEscalationParams, } from './hmsh_escalations';
|
|
29
|
+
export { EscalationVerb, EngineVerb, WorkerVerb, SystemEvent, EventsConfig } from './system_events';
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-event emission surface for HotMesh lifecycle transitions.
|
|
3
|
+
*
|
|
4
|
+
* The performing actor — the one engine/SDK call that commits a durable
|
|
5
|
+
* transition — fires `EventsConfig.publish` exactly once, inline,
|
|
6
|
+
* post-commit. The hook is fire-and-forget; the SDK never awaits it.
|
|
7
|
+
*
|
|
8
|
+
* ## Ontology
|
|
9
|
+
*
|
|
10
|
+
* Every event has a canonical `type` string and a `data` payload.
|
|
11
|
+
* Consumers pattern-match on `type` and cherry-pick fields from `data`.
|
|
12
|
+
*
|
|
13
|
+
* ### Escalation lifecycle (`system.escalation.{id}.{verb}`)
|
|
14
|
+
*
|
|
15
|
+
* | verb | trigger | `data` shape |
|
|
16
|
+
* |-------------|----------------------------------------------------------|---------------------|
|
|
17
|
+
* | `created` | `client.create()` or hook Leg1 INSERT | full `EscalationEntry` row |
|
|
18
|
+
* | `claimed` | `client.claim()` / `client.claimByMetadata()` | full `EscalationEntry` row |
|
|
19
|
+
* | `released` | `client.release()` | full `EscalationEntry` row |
|
|
20
|
+
* | `reassigned`| `client.escalateToRole()` / role change | full `EscalationEntry` row |
|
|
21
|
+
* | `resolved` | `client.resolve()` / `client.resolveByMetadata()` | full `EscalationEntry` row |
|
|
22
|
+
* | `cancelled` | `client.cancel()` | full `EscalationEntry` row |
|
|
23
|
+
*
|
|
24
|
+
* ### Engine lifecycle (`system.engine.{appId}.{verb}`)
|
|
25
|
+
*
|
|
26
|
+
* | verb | trigger | `data` shape |
|
|
27
|
+
* |------------|----------------------------------|----------------------------------|
|
|
28
|
+
* | `started` | `HotMesh.init()` completes | `{ appId: string, guid: string }` |
|
|
29
|
+
* | `stopped` | `hotMesh.stop()` called | `{ appId: string, guid: string }` |
|
|
30
|
+
* | `deployed` | `kvTables.deploy()` completes | `{ appId: string }` |
|
|
31
|
+
*
|
|
32
|
+
* ### Worker lifecycle (`system.worker.{taskQueue}.{verb}`)
|
|
33
|
+
*
|
|
34
|
+
* | verb | trigger | `data` shape |
|
|
35
|
+
* |------------|----------------------------------|----------------------------------------------|
|
|
36
|
+
* | `started` | `Durable.Worker.create()` ready | `{ taskQueue: string, appId: string }` |
|
|
37
|
+
* | `stopped` | `worker.stop()` called | `{ taskQueue: string, appId: string }` |
|
|
38
|
+
*
|
|
39
|
+
* ## Registration — three construction sites
|
|
40
|
+
*
|
|
41
|
+
* **Site 1 — YAML DAG / hook Leg1 (escalation `created` + engine events):**
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { HotMesh } from '@hotmeshio/hotmesh';
|
|
44
|
+
* const hm = await HotMesh.init({
|
|
45
|
+
* appId: 'myapp',
|
|
46
|
+
* engine: { connection },
|
|
47
|
+
* events: { publish: (e) => bus.emit(e.type, e) },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* **Site 2 — Standalone `EscalationClientService` (all 6 verbs):**
|
|
52
|
+
* ```typescript
|
|
53
|
+
* import { Escalations } from '@hotmeshio/hotmesh';
|
|
54
|
+
* const client = new Escalations.Client({
|
|
55
|
+
* connection,
|
|
56
|
+
* events: { publish: (e) => bus.emit(e.type, e) },
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* **Site 3 — `Durable.Client` (all 6 verbs via `.escalations`):**
|
|
61
|
+
* ```typescript
|
|
62
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
63
|
+
* const client = new Durable.Client({
|
|
64
|
+
* connection,
|
|
65
|
+
* events: { publish: (e) => bus.emit(e.type, e) },
|
|
66
|
+
* });
|
|
67
|
+
* // client.escalations.* operations now emit lifecycle events
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* ## Event ID format
|
|
71
|
+
*
|
|
72
|
+
* `event_id` is collision-proof across recurrences:
|
|
73
|
+
* - Escalation: `${id}:${verb}:${updated_at_iso}` — claim→release→reclaim
|
|
74
|
+
* produces distinct IDs because `updated_at` changes on each transition.
|
|
75
|
+
* - Engine / worker: `${app_id_or_queue}:${verb}:${ts}`.
|
|
76
|
+
*
|
|
77
|
+
* ## Fire-and-forget contract
|
|
78
|
+
*
|
|
79
|
+
* The SDK wraps every `publish` call in
|
|
80
|
+
* `void Promise.resolve(publish(event)).catch(() => {})`.
|
|
81
|
+
* A slow or throwing `publish` implementation never blocks or fails the
|
|
82
|
+
* committed operation. Use an in-process buffer or async queue if you need
|
|
83
|
+
* back-pressure.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
88
|
+
* import { Client as Postgres } from 'pg';
|
|
89
|
+
*
|
|
90
|
+
* const client = new Durable.Client({
|
|
91
|
+
* connection: { class: Postgres, options: { connectionString: process.env.DATABASE_URL } },
|
|
92
|
+
* events: {
|
|
93
|
+
* publish: (event) => {
|
|
94
|
+
* if (event.type.endsWith('.created')) {
|
|
95
|
+
* // new escalation — route to the right team
|
|
96
|
+
* dispatch(event.data as EscalationEntry);
|
|
97
|
+
* }
|
|
98
|
+
* },
|
|
99
|
+
* },
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
/** Verbs for escalation lifecycle transitions. */
|
|
104
|
+
export type EscalationVerb = 'created' | 'claimed' | 'released' | 'reassigned' | 'resolved' | 'cancelled';
|
|
105
|
+
/** Verbs for engine lifecycle transitions. */
|
|
106
|
+
export type EngineVerb = 'started' | 'stopped' | 'deployed';
|
|
107
|
+
/** Verbs for worker lifecycle transitions. */
|
|
108
|
+
export type WorkerVerb = 'started' | 'stopped';
|
|
109
|
+
/**
|
|
110
|
+
* Canonical lifecycle event emitted by the SDK post-commit.
|
|
111
|
+
*
|
|
112
|
+
* `event_id` is stable across replays:
|
|
113
|
+
* - Escalation transitions: `${id}:${verb}:${updated_at_iso}` — unique
|
|
114
|
+
* per transition; a re-claim after release gets a new `updated_at`.
|
|
115
|
+
* - Engine / worker events: `${app_id}:${verb}:${ts}`.
|
|
116
|
+
*
|
|
117
|
+
* `data` carries the full committed row (escalation entry) or lifecycle
|
|
118
|
+
* metadata (engine/worker). Consumers cherry-pick what they need; nothing
|
|
119
|
+
* is pre-projected so the shape is future-proof.
|
|
120
|
+
*/
|
|
121
|
+
export interface SystemEvent {
|
|
122
|
+
/** Stable, unique ID per durable transition. */
|
|
123
|
+
event_id: string;
|
|
124
|
+
/**
|
|
125
|
+
* Canonical topic string.
|
|
126
|
+
*
|
|
127
|
+
* | Class | Pattern |
|
|
128
|
+
* |-------------|---------------------------------------|
|
|
129
|
+
* | escalation | `system.escalation.{id}.{verb}` |
|
|
130
|
+
* | engine | `system.engine.{appId}.{verb}` |
|
|
131
|
+
* | worker | `system.worker.{taskQueue}.{verb}` |
|
|
132
|
+
*/
|
|
133
|
+
type: string;
|
|
134
|
+
/** ISO timestamp at emit time (wall-clock, post-commit). */
|
|
135
|
+
ts: string;
|
|
136
|
+
namespace: string;
|
|
137
|
+
app_id: string;
|
|
138
|
+
workflow_id?: string;
|
|
139
|
+
topic?: string;
|
|
140
|
+
origin_id?: string;
|
|
141
|
+
parent_id?: string;
|
|
142
|
+
trace_id?: string;
|
|
143
|
+
span_id?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Full committed row for escalation events; lifecycle metadata for
|
|
146
|
+
* engine/worker events. Long-tail and hike-mono each cherry-pick fields
|
|
147
|
+
* for their own event shape.
|
|
148
|
+
*/
|
|
149
|
+
data: Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* System-event sink configuration. Attach to `HotMeshConfig.events`,
|
|
153
|
+
* `EscalationClientConfig.events`, or `ClientConfig.events` to receive
|
|
154
|
+
* lifecycle events from the SDK.
|
|
155
|
+
*
|
|
156
|
+
* The SDK calls `publish` after each durable transition commits, from the
|
|
157
|
+
* single actor that performed the commit. In a multi-container fleet every
|
|
158
|
+
* container's SDK calls its own `publish` hook — and only for the work it
|
|
159
|
+
* performed — so exactly one container's `publish` fires per real event.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* const events: EventsConfig = {
|
|
164
|
+
* publish: (event) => {
|
|
165
|
+
* // map SystemEvent → your LTEvent shape and hand to NATS/Socket.IO
|
|
166
|
+
* eventRegistry.publish(mapToLTEvent(event));
|
|
167
|
+
* },
|
|
168
|
+
* };
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export interface EventsConfig {
|
|
172
|
+
/**
|
|
173
|
+
* Called post-commit by the performing actor. Fire-and-forget — the SDK
|
|
174
|
+
* does not await the return value; a thrown/rejected promise is silently
|
|
175
|
+
* swallowed so the committed call is never failed by a publish error.
|
|
176
|
+
*/
|
|
177
|
+
publish: (event: SystemEvent) => void | Promise<void>;
|
|
178
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* System-event emission surface for HotMesh lifecycle transitions.
|
|
4
|
+
*
|
|
5
|
+
* The performing actor — the one engine/SDK call that commits a durable
|
|
6
|
+
* transition — fires `EventsConfig.publish` exactly once, inline,
|
|
7
|
+
* post-commit. The hook is fire-and-forget; the SDK never awaits it.
|
|
8
|
+
*
|
|
9
|
+
* ## Ontology
|
|
10
|
+
*
|
|
11
|
+
* Every event has a canonical `type` string and a `data` payload.
|
|
12
|
+
* Consumers pattern-match on `type` and cherry-pick fields from `data`.
|
|
13
|
+
*
|
|
14
|
+
* ### Escalation lifecycle (`system.escalation.{id}.{verb}`)
|
|
15
|
+
*
|
|
16
|
+
* | verb | trigger | `data` shape |
|
|
17
|
+
* |-------------|----------------------------------------------------------|---------------------|
|
|
18
|
+
* | `created` | `client.create()` or hook Leg1 INSERT | full `EscalationEntry` row |
|
|
19
|
+
* | `claimed` | `client.claim()` / `client.claimByMetadata()` | full `EscalationEntry` row |
|
|
20
|
+
* | `released` | `client.release()` | full `EscalationEntry` row |
|
|
21
|
+
* | `reassigned`| `client.escalateToRole()` / role change | full `EscalationEntry` row |
|
|
22
|
+
* | `resolved` | `client.resolve()` / `client.resolveByMetadata()` | full `EscalationEntry` row |
|
|
23
|
+
* | `cancelled` | `client.cancel()` | full `EscalationEntry` row |
|
|
24
|
+
*
|
|
25
|
+
* ### Engine lifecycle (`system.engine.{appId}.{verb}`)
|
|
26
|
+
*
|
|
27
|
+
* | verb | trigger | `data` shape |
|
|
28
|
+
* |------------|----------------------------------|----------------------------------|
|
|
29
|
+
* | `started` | `HotMesh.init()` completes | `{ appId: string, guid: string }` |
|
|
30
|
+
* | `stopped` | `hotMesh.stop()` called | `{ appId: string, guid: string }` |
|
|
31
|
+
* | `deployed` | `kvTables.deploy()` completes | `{ appId: string }` |
|
|
32
|
+
*
|
|
33
|
+
* ### Worker lifecycle (`system.worker.{taskQueue}.{verb}`)
|
|
34
|
+
*
|
|
35
|
+
* | verb | trigger | `data` shape |
|
|
36
|
+
* |------------|----------------------------------|----------------------------------------------|
|
|
37
|
+
* | `started` | `Durable.Worker.create()` ready | `{ taskQueue: string, appId: string }` |
|
|
38
|
+
* | `stopped` | `worker.stop()` called | `{ taskQueue: string, appId: string }` |
|
|
39
|
+
*
|
|
40
|
+
* ## Registration — three construction sites
|
|
41
|
+
*
|
|
42
|
+
* **Site 1 — YAML DAG / hook Leg1 (escalation `created` + engine events):**
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { HotMesh } from '@hotmeshio/hotmesh';
|
|
45
|
+
* const hm = await HotMesh.init({
|
|
46
|
+
* appId: 'myapp',
|
|
47
|
+
* engine: { connection },
|
|
48
|
+
* events: { publish: (e) => bus.emit(e.type, e) },
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* **Site 2 — Standalone `EscalationClientService` (all 6 verbs):**
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import { Escalations } from '@hotmeshio/hotmesh';
|
|
55
|
+
* const client = new Escalations.Client({
|
|
56
|
+
* connection,
|
|
57
|
+
* events: { publish: (e) => bus.emit(e.type, e) },
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* **Site 3 — `Durable.Client` (all 6 verbs via `.escalations`):**
|
|
62
|
+
* ```typescript
|
|
63
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
64
|
+
* const client = new Durable.Client({
|
|
65
|
+
* connection,
|
|
66
|
+
* events: { publish: (e) => bus.emit(e.type, e) },
|
|
67
|
+
* });
|
|
68
|
+
* // client.escalations.* operations now emit lifecycle events
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* ## Event ID format
|
|
72
|
+
*
|
|
73
|
+
* `event_id` is collision-proof across recurrences:
|
|
74
|
+
* - Escalation: `${id}:${verb}:${updated_at_iso}` — claim→release→reclaim
|
|
75
|
+
* produces distinct IDs because `updated_at` changes on each transition.
|
|
76
|
+
* - Engine / worker: `${app_id_or_queue}:${verb}:${ts}`.
|
|
77
|
+
*
|
|
78
|
+
* ## Fire-and-forget contract
|
|
79
|
+
*
|
|
80
|
+
* The SDK wraps every `publish` call in
|
|
81
|
+
* `void Promise.resolve(publish(event)).catch(() => {})`.
|
|
82
|
+
* A slow or throwing `publish` implementation never blocks or fails the
|
|
83
|
+
* committed operation. Use an in-process buffer or async queue if you need
|
|
84
|
+
* back-pressure.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
89
|
+
* import { Client as Postgres } from 'pg';
|
|
90
|
+
*
|
|
91
|
+
* const client = new Durable.Client({
|
|
92
|
+
* connection: { class: Postgres, options: { connectionString: process.env.DATABASE_URL } },
|
|
93
|
+
* events: {
|
|
94
|
+
* publish: (event) => {
|
|
95
|
+
* if (event.type.endsWith('.created')) {
|
|
96
|
+
* // new escalation — route to the right team
|
|
97
|
+
* dispatch(event.data as EscalationEntry);
|
|
98
|
+
* }
|
|
99
|
+
* },
|
|
100
|
+
* },
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.4",
|
|
4
4
|
"description": "Durable Workflow",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -86,9 +86,10 @@
|
|
|
86
86
|
"test:trigger": "vitest run tests/unit/services/activities/trigger.test.ts",
|
|
87
87
|
"test:virtual": "vitest run tests/virtual",
|
|
88
88
|
"test:unit": "vitest run tests/unit",
|
|
89
|
-
|
|
90
89
|
"prove": "docker compose exec hotmesh npx vitest run tests/durable 2>&1 | tee /tmp/hmsh-durable.txt && grep -E 'FAIL|Tests |Files ' /tmp/hmsh-durable.txt | tail -5",
|
|
91
90
|
"prove:escalations": "docker compose exec hotmesh npx vitest run tests/durable/escalations/postgres.test.ts 2>&1 | tee /tmp/hmsh-escalations.txt && grep -E 'FAIL|Tests |Files ' /tmp/hmsh-escalations.txt | tail -5",
|
|
91
|
+
"prove:migrations": "docker compose exec hotmesh npx vitest run tests/durable/migrations/postgres.test.ts 2>&1 | tee /tmp/hmsh-migrations.txt && grep -E 'FAIL|Tests |Files ' /tmp/hmsh-migrations.txt | tail -5",
|
|
92
|
+
"prove:events": "docker compose exec hotmesh npx vitest run tests/durable/events/postgres.test.ts 2>&1 | tee /tmp/hmsh-events.txt && grep -E 'FAIL|Tests |Files ' /tmp/hmsh-events.txt | tail -5",
|
|
92
93
|
"prove:functional": "docker compose exec hotmesh npx vitest run tests/functional 2>&1 | tee /tmp/hmsh-functional.txt && grep -E 'FAIL|Tests |Files ' /tmp/hmsh-functional.txt | tail -5",
|
|
93
94
|
"prove:all": "docker compose exec hotmesh npx vitest run tests/ 2>&1 | tee /tmp/hmsh-all.txt && grep -E 'FAIL|Tests |Files ' /tmp/hmsh-all.txt | tail -5",
|
|
94
95
|
"prove:file": "f() { docker compose exec hotmesh npx vitest run \"$@\" 2>&1 | tee /tmp/hmsh-file.txt && grep -E 'FAIL|Tests |Files ' /tmp/hmsh-file.txt | tail -5; }; f"
|