@hotmeshio/hotmesh 0.22.1 → 0.22.2
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 +2 -1
- package/build/index.js +3 -1
- package/build/package.json +1 -1
- package/build/services/durable/client.d.ts +8 -273
- package/build/services/durable/client.js +5 -384
- package/build/services/escalations/client.d.ts +130 -0
- package/build/services/escalations/client.js +262 -0
- package/build/services/escalations/index.d.ts +66 -0
- package/build/services/escalations/index.js +71 -0
- package/build/services/store/providers/postgres/postgres.d.ts +6 -5
- package/build/services/store/providers/postgres/postgres.js +110 -40
- package/build/types/hmsh_escalations.d.ts +15 -6
- package/index.ts +2 -0
- package/package.json +1 -1
package/build/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import * as KeyStore from './modules/key';
|
|
|
18
18
|
import { ConnectorService as Connector } from './services/connector/factory';
|
|
19
19
|
import { PostgresConnection as ConnectorPostgres } from './services/connector/providers/postgres';
|
|
20
20
|
import { NatsConnection as ConnectorNATS } from './services/connector/providers/nats';
|
|
21
|
+
import { Escalations } from './services/escalations';
|
|
21
22
|
export { Connector, //factory
|
|
22
|
-
ConnectorNATS, ConnectorPostgres, HotMesh, HotMeshConfig, Virtual, Durable, DBA, Client, Connection, proxyActivities, Search, Entity, Worker, workflow, WorkflowHandle, Enums, Errors, Utils, KeyStore, };
|
|
23
|
+
ConnectorNATS, ConnectorPostgres, HotMesh, HotMeshConfig, Virtual, Durable, Escalations, DBA, Client, Connection, proxyActivities, Search, Entity, Worker, workflow, WorkflowHandle, Enums, Errors, Utils, KeyStore, };
|
|
23
24
|
export * as Types from './types';
|
package/build/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.Types = exports.KeyStore = exports.Utils = exports.Errors = exports.Enums = exports.WorkflowHandle = exports.workflow = exports.Worker = exports.Entity = exports.Search = exports.proxyActivities = exports.Connection = exports.Client = exports.DBA = exports.Durable = exports.Virtual = exports.HotMesh = exports.ConnectorPostgres = exports.ConnectorNATS = exports.Connector = void 0;
|
|
26
|
+
exports.Types = exports.KeyStore = exports.Utils = exports.Errors = exports.Enums = exports.WorkflowHandle = exports.workflow = exports.Worker = exports.Entity = exports.Search = exports.proxyActivities = exports.Connection = exports.Client = exports.DBA = exports.Escalations = exports.Durable = exports.Virtual = exports.HotMesh = exports.ConnectorPostgres = exports.ConnectorNATS = exports.Connector = void 0;
|
|
27
27
|
const hotmesh_1 = require("./services/hotmesh");
|
|
28
28
|
Object.defineProperty(exports, "HotMesh", { enumerable: true, get: function () { return hotmesh_1.HotMesh; } });
|
|
29
29
|
const virtual_1 = require("./services/virtual");
|
|
@@ -62,4 +62,6 @@ const postgres_1 = require("./services/connector/providers/postgres");
|
|
|
62
62
|
Object.defineProperty(exports, "ConnectorPostgres", { enumerable: true, get: function () { return postgres_1.PostgresConnection; } });
|
|
63
63
|
const nats_1 = require("./services/connector/providers/nats");
|
|
64
64
|
Object.defineProperty(exports, "ConnectorNATS", { enumerable: true, get: function () { return nats_1.NatsConnection; } });
|
|
65
|
+
const escalations_1 = require("./services/escalations");
|
|
66
|
+
Object.defineProperty(exports, "Escalations", { enumerable: true, get: function () { return escalations_1.Escalations; } });
|
|
65
67
|
exports.Types = __importStar(require("./types"));
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HotMesh } from '../hotmesh';
|
|
2
2
|
import { ClientConfig, ClientWorkflow, Connection, WorkflowOptions } from '../../types/durable';
|
|
3
|
-
import {
|
|
3
|
+
import { EscalationClientService } from '../escalations/client';
|
|
4
4
|
/**
|
|
5
5
|
* Workflow client. Starts workflows, sends signals, and reads results.
|
|
6
6
|
*
|
|
@@ -49,6 +49,13 @@ export declare class ClientService {
|
|
|
49
49
|
* @private
|
|
50
50
|
*/
|
|
51
51
|
static instances: Map<string, HotMesh | Promise<HotMesh>>;
|
|
52
|
+
/**
|
|
53
|
+
* Escalation queue operations — a thin proxy to `Escalations.Client` that
|
|
54
|
+
* reuses this Durable.Client's engine pool. Calling
|
|
55
|
+
* `client.escalations.list(...)` is identical to creating a separate
|
|
56
|
+
* `new Escalations.Client({ connection })` — just more convenient.
|
|
57
|
+
*/
|
|
58
|
+
escalations: EscalationClientService;
|
|
52
59
|
/**
|
|
53
60
|
* @private
|
|
54
61
|
*/
|
|
@@ -97,278 +104,6 @@ export declare class ClientService {
|
|
|
97
104
|
* @private
|
|
98
105
|
*/
|
|
99
106
|
activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* Escalation queue operations over `public.hmsh_escalations` — a global
|
|
102
|
-
* table that surfaces workflow signal pauses as role-based, claimable,
|
|
103
|
-
* searchable queue items.
|
|
104
|
-
*
|
|
105
|
-
* When a YAML `hook` activity suspends with an `escalation:` block, or
|
|
106
|
-
* `Durable.workflow.condition(signalId, config)` fires, **one row is
|
|
107
|
-
* written atomically** with the workflow checkpoint — no enrichment step,
|
|
108
|
-
* no secondary round-trip. Every connected app shares the same table;
|
|
109
|
-
* rows are namespaced by `namespace` + `app_id`.
|
|
110
|
-
*
|
|
111
|
-
* **Status lifecycle:**
|
|
112
|
-
* ```
|
|
113
|
-
* pending → claimed → resolved
|
|
114
|
-
* ↘ cancelled (any non-terminal state)
|
|
115
|
-
* ↗ pending (via release or releaseExpired)
|
|
116
|
-
* ```
|
|
117
|
-
*
|
|
118
|
-
* **Typical human-in-the-loop flow:**
|
|
119
|
-
* ```typescript
|
|
120
|
-
* // 1. Workflow pauses and writes the escalation row automatically
|
|
121
|
-
* const decision = await Durable.workflow.condition('manager-approval', {
|
|
122
|
-
* role: 'manager',
|
|
123
|
-
* type: 'order-approval',
|
|
124
|
-
* priority: 2,
|
|
125
|
-
* metadata: { orderId },
|
|
126
|
-
* });
|
|
127
|
-
*
|
|
128
|
-
* // 2. Dashboard lists pending approvals for this role
|
|
129
|
-
* const [item] = await client.escalations.list({ role: 'manager', status: 'pending' });
|
|
130
|
-
*
|
|
131
|
-
* // 3. Reviewer claims it (sets assigned_to + expiry)
|
|
132
|
-
* await client.escalations.claim({ id: item.id, assignee: 'alice@company.com' });
|
|
133
|
-
*
|
|
134
|
-
* // 4. Resolve atomically marks it resolved AND delivers the signal
|
|
135
|
-
* await client.escalations.resolve({
|
|
136
|
-
* id: item.id,
|
|
137
|
-
* resolverPayload: { approved: true },
|
|
138
|
-
* });
|
|
139
|
-
* // workflow resumes with { approved: true }
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
|
-
escalations: {
|
|
143
|
-
/**
|
|
144
|
-
* Returns all escalation rows matching the given filters.
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```typescript
|
|
148
|
-
* // All pending approvals for the manager role
|
|
149
|
-
* const items = await client.escalations.list({ role: 'manager', status: 'pending' });
|
|
150
|
-
*
|
|
151
|
-
* // By workflow ID
|
|
152
|
-
* const items = await client.escalations.list({ workflowId: 'order-123' });
|
|
153
|
-
* ```
|
|
154
|
-
*/
|
|
155
|
-
list: (params?: ListEscalationsParams) => Promise<EscalationEntry[]>;
|
|
156
|
-
/**
|
|
157
|
-
* Returns a single escalation row by its UUID primary key.
|
|
158
|
-
* Returns `null` if not found.
|
|
159
|
-
*/
|
|
160
|
-
get: (id: string, namespace?: string) => Promise<EscalationEntry | null>;
|
|
161
|
-
/**
|
|
162
|
-
* Looks up an escalation row by its `signal_key` — the value that was
|
|
163
|
-
* passed to `condition()` or stored in the hook activity's collation rule.
|
|
164
|
-
* This is the same key used to deliver the signal via `hotMesh.signal()`.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* ```typescript
|
|
168
|
-
* const item = await client.escalations.getBySignalKey('manager-approval');
|
|
169
|
-
* ```
|
|
170
|
-
*/
|
|
171
|
-
getBySignalKey: (signalKey: string, namespace?: string) => Promise<EscalationEntry | null>;
|
|
172
|
-
/**
|
|
173
|
-
* Creates a standalone escalation row that is **not** backed by a signal.
|
|
174
|
-
* `signal_key` is `null`. Useful for external task tracking that doesn't
|
|
175
|
-
* need to resume a workflow (e.g., audit tasks, out-of-band approvals).
|
|
176
|
-
*
|
|
177
|
-
* @example
|
|
178
|
-
* ```typescript
|
|
179
|
-
* const entry = await client.escalations.create({
|
|
180
|
-
* role: 'support',
|
|
181
|
-
* type: 'data-correction',
|
|
182
|
-
* description: 'Fix the customer address',
|
|
183
|
-
* metadata: { customerId: 'cust-42' },
|
|
184
|
-
* });
|
|
185
|
-
* ```
|
|
186
|
-
*/
|
|
187
|
-
create: (params: CreateEscalationParams) => Promise<EscalationEntry>;
|
|
188
|
-
/**
|
|
189
|
-
* Patches an existing escalation row. All fields are optional — only
|
|
190
|
-
* provided fields are written. `metadata` is **merged**, not replaced.
|
|
191
|
-
*
|
|
192
|
-
* Signal routing fields (`signalKey`, `topic`, `workflowId`, …) can be
|
|
193
|
-
* enriched after the row is created — useful when the row is created
|
|
194
|
-
* before the workflow starts and routing context is not yet known.
|
|
195
|
-
*
|
|
196
|
-
* @example
|
|
197
|
-
* ```typescript
|
|
198
|
-
* await client.escalations.update({
|
|
199
|
-
* id: item.id,
|
|
200
|
-
* description: 'Updated description',
|
|
201
|
-
* metadata: { extraKey: 'value' }, // merged into existing metadata
|
|
202
|
-
* });
|
|
203
|
-
* ```
|
|
204
|
-
*/
|
|
205
|
-
update: (params: UpdateEscalationParams) => Promise<EscalationEntry | null>;
|
|
206
|
-
/**
|
|
207
|
-
* Appends one or more milestone entries to the escalation's
|
|
208
|
-
* `milestones` audit trail array. Milestones are append-only; they
|
|
209
|
-
* record events like state transitions, reviewer notes, or external
|
|
210
|
-
* system callbacks.
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* ```typescript
|
|
214
|
-
* await client.escalations.appendMilestones({
|
|
215
|
-
* id: item.id,
|
|
216
|
-
* milestones: [{ at: new Date().toISOString(), by: 'alice', note: 'Reviewed' }],
|
|
217
|
-
* });
|
|
218
|
-
* ```
|
|
219
|
-
*/
|
|
220
|
-
appendMilestones: (params: AppendMilestonesParams) => Promise<EscalationEntry | null>;
|
|
221
|
-
/**
|
|
222
|
-
* Atomically claims an escalation row by UUID. Sets `assigned_to`,
|
|
223
|
-
* `claimed_at`, and `claim_expires_at`. Returns `conflict` if another
|
|
224
|
-
* actor already holds the claim.
|
|
225
|
-
*
|
|
226
|
-
* @example
|
|
227
|
-
* ```typescript
|
|
228
|
-
* const result = await client.escalations.claim({
|
|
229
|
-
* id: item.id,
|
|
230
|
-
* assignee: 'alice@company.com',
|
|
231
|
-
* durationMinutes: 30,
|
|
232
|
-
* });
|
|
233
|
-
* if (!result.ok) console.warn('Already claimed by someone else');
|
|
234
|
-
* ```
|
|
235
|
-
*/
|
|
236
|
-
claim: (params: ClaimEscalationParams) => Promise<ClaimEscalationResult>;
|
|
237
|
-
/**
|
|
238
|
-
* Atomically claims the highest-priority pending escalation whose
|
|
239
|
-
* `metadata` contains the given key/value pair. Uses
|
|
240
|
-
* `FOR UPDATE SKIP LOCKED` so concurrent callers never double-claim.
|
|
241
|
-
*
|
|
242
|
-
* Returns `candidatesExist` to distinguish two cases:
|
|
243
|
-
* - `not-found, candidatesExist: 0` — no rows matched the metadata filter
|
|
244
|
-
* - `conflict, candidatesExist: N` — matching rows exist but all are claimed
|
|
245
|
-
*
|
|
246
|
-
* @example
|
|
247
|
-
* ```typescript
|
|
248
|
-
* const result = await client.escalations.claimByMetadata({
|
|
249
|
-
* key: 'region',
|
|
250
|
-
* value: 'west',
|
|
251
|
-
* assignee: 'bob@company.com',
|
|
252
|
-
* roles: ['manager'],
|
|
253
|
-
* });
|
|
254
|
-
* if (result.ok) console.log('Claimed:', result.entry.id);
|
|
255
|
-
* ```
|
|
256
|
-
*/
|
|
257
|
-
claimByMetadata: (params: ClaimByMetadataParams) => Promise<ClaimByMetadataResult>;
|
|
258
|
-
/**
|
|
259
|
-
* Releases a claimed escalation, returning it to `pending` status and
|
|
260
|
-
* clearing `assigned_to` and `claim_expires_at`. The row is immediately
|
|
261
|
-
* available for other actors to claim.
|
|
262
|
-
*
|
|
263
|
-
* @example
|
|
264
|
-
* ```typescript
|
|
265
|
-
* await client.escalations.release({ id: item.id });
|
|
266
|
-
* ```
|
|
267
|
-
*/
|
|
268
|
-
release: (params: ReleaseEscalationParams) => Promise<ReleaseEscalationResult>;
|
|
269
|
-
/**
|
|
270
|
-
* Reassigns the escalation to a different role, clearing any current
|
|
271
|
-
* claim and returning status to `pending`. Use when an escalation must
|
|
272
|
-
* be handled by a different team or tier.
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* ```typescript
|
|
276
|
-
* await client.escalations.escalateToRole({ id: item.id, role: 'senior-manager' });
|
|
277
|
-
* ```
|
|
278
|
-
*/
|
|
279
|
-
escalateToRole: (params: EscalateToRoleParams) => Promise<EscalationEntry | null>;
|
|
280
|
-
/**
|
|
281
|
-
* Terminates the escalation without delivering a signal. Rows in
|
|
282
|
-
* `pending` or `claimed` state move to `cancelled`. Terminal rows
|
|
283
|
-
* (`resolved`, `cancelled`) return `already-terminal`.
|
|
284
|
-
*
|
|
285
|
-
* @example
|
|
286
|
-
* ```typescript
|
|
287
|
-
* await client.escalations.cancel(item.id);
|
|
288
|
-
* ```
|
|
289
|
-
*/
|
|
290
|
-
cancel: (id: string, namespace?: string) => Promise<CancelEscalationResult>;
|
|
291
|
-
/**
|
|
292
|
-
* Atomically marks the escalation `resolved` **and** delivers the
|
|
293
|
-
* signal to the waiting workflow — one round-trip, no separate
|
|
294
|
-
* `signal()` call required. If `signal_key` is null (standalone
|
|
295
|
-
* escalation), only the row is updated.
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* ```typescript
|
|
299
|
-
* const result = await client.escalations.resolve({
|
|
300
|
-
* id: item.id,
|
|
301
|
-
* resolverPayload: { approved: true, note: 'LGTM' },
|
|
302
|
-
* });
|
|
303
|
-
* if (!result.ok) console.error(result.reason); // 'not-found' | 'already-resolved' | 'signal-failed'
|
|
304
|
-
* // workflow resumes with { approved: true, note: 'LGTM' }
|
|
305
|
-
* ```
|
|
306
|
-
*/
|
|
307
|
-
resolve: (params: ResolveEscalationParams, namespace?: string) => Promise<ResolveEscalationResult>;
|
|
308
|
-
/**
|
|
309
|
-
* Resolves the highest-priority matching escalation by metadata filter,
|
|
310
|
-
* then delivers its signal. Identical semantics to `resolve()` but
|
|
311
|
-
* selects the target row by metadata key/value instead of UUID.
|
|
312
|
-
*
|
|
313
|
-
* @example
|
|
314
|
-
* ```typescript
|
|
315
|
-
* await client.escalations.resolveByMetadata({
|
|
316
|
-
* key: 'orderId',
|
|
317
|
-
* value: 'order-123',
|
|
318
|
-
* resolverPayload: { approved: true },
|
|
319
|
-
* });
|
|
320
|
-
* ```
|
|
321
|
-
*/
|
|
322
|
-
resolveByMetadata: (params: ResolveByMetadataParams, namespace?: string) => Promise<ResolveEscalationResult>;
|
|
323
|
-
/**
|
|
324
|
-
* Full-fidelity migration: inserts an escalation row preserving the original
|
|
325
|
-
* UUID and all lifecycle state. Returns the inserted row, or `null` if the
|
|
326
|
-
* UUID already exists (idempotent — safe to call multiple times with the same
|
|
327
|
-
* `params.id`). Use this to migrate rows from a legacy escalation table to
|
|
328
|
-
* `hmsh_escalations` without losing original IDs or state.
|
|
329
|
-
*
|
|
330
|
-
* @example
|
|
331
|
-
* ```typescript
|
|
332
|
-
* const entry = await client.escalations.migrate({
|
|
333
|
-
* id: 'original-uuid',
|
|
334
|
-
* status: 'resolved',
|
|
335
|
-
* resolvedAt: new Date('2025-01-01'),
|
|
336
|
-
* type: 'order-approval',
|
|
337
|
-
* role: 'approver',
|
|
338
|
-
* });
|
|
339
|
-
* // null on subsequent calls with the same id — idempotent
|
|
340
|
-
* ```
|
|
341
|
-
*/
|
|
342
|
-
migrate: (params: MigrateEscalationParams, namespace?: string) => Promise<EscalationEntry | null>;
|
|
343
|
-
/**
|
|
344
|
-
* Releases all claimed escalations whose `claim_expires_at` has lapsed,
|
|
345
|
-
* returning them to `pending` so they can be claimed again. Returns the
|
|
346
|
-
* number of rows released. Call periodically from a maintenance job or
|
|
347
|
-
* cron to prevent stale claims from blocking the queue.
|
|
348
|
-
*
|
|
349
|
-
* @example
|
|
350
|
-
* ```typescript
|
|
351
|
-
* const released = await client.escalations.releaseExpired();
|
|
352
|
-
* console.log(`Released ${released} expired claims`);
|
|
353
|
-
* ```
|
|
354
|
-
*/
|
|
355
|
-
releaseExpired: (namespace?: string) => Promise<number>;
|
|
356
|
-
};
|
|
357
|
-
/**
|
|
358
|
-
* Delivers a signal to the registered escalation topic.
|
|
359
|
-
*
|
|
360
|
-
* When the topic is known (stored at condition() time), we deliver only to that
|
|
361
|
-
* topic. This avoids writing a stale pending entry to the alternate stream, which
|
|
362
|
-
* would interfere with concurrent consumers in other workflows or tests.
|
|
363
|
-
*
|
|
364
|
-
* When topic is null (legacy/standalone rows with no registered topic), we try
|
|
365
|
-
* both wfs.signal and wfs.wait unconditionally — engine.signal() on the wrong
|
|
366
|
-
* topic stores pending without throwing, so a sequential fallback would skip
|
|
367
|
-
* the second topic and leave single-condition workflows permanently suspended.
|
|
368
|
-
*
|
|
369
|
-
* @private
|
|
370
|
-
*/
|
|
371
|
-
private _deliverEscalationSignal;
|
|
372
107
|
/**
|
|
373
108
|
* @private
|
|
374
109
|
*/
|