@hotmeshio/long-tail 0.25.4 → 0.27.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/README.md +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +36 -7
- package/build/lib/events/nats.d.ts +11 -0
- package/build/lib/events/nats.js +33 -2
- package/build/lib/machines/index.d.ts +6 -0
- package/build/lib/machines/index.js +22 -0
- package/build/lib/machines/line.d.ts +44 -0
- package/build/lib/machines/line.js +114 -0
- package/build/lib/machines/protocol/codec.d.ts +108 -0
- package/build/lib/machines/protocol/codec.js +80 -0
- package/build/lib/machines/protocol/constants.d.ts +120 -0
- package/build/lib/machines/protocol/constants.js +97 -0
- package/build/lib/machines/protocol/contract.d.ts +19 -0
- package/build/lib/machines/protocol/contract.js +141 -0
- package/build/lib/machines/protocol/envelope.d.ts +14 -0
- package/build/lib/machines/protocol/envelope.js +43 -0
- package/build/lib/machines/protocol/index.d.ts +5 -0
- package/build/lib/machines/protocol/index.js +21 -0
- package/build/lib/machines/protocol/topics.d.ts +33 -0
- package/build/lib/machines/protocol/topics.js +79 -0
- package/build/lib/machines/site/edge-account.d.ts +11 -0
- package/build/lib/machines/site/edge-account.js +24 -0
- package/build/lib/machines/site/mosquitto-conf.d.ts +27 -0
- package/build/lib/machines/site/mosquitto-conf.js +81 -0
- package/build/lib/machines/site/site-conf.d.ts +23 -0
- package/build/lib/machines/site/site-conf.js +62 -0
- package/build/lib/machines/transport/callback-feed.d.ts +18 -0
- package/build/lib/machines/transport/callback-feed.js +31 -0
- package/build/lib/machines/transport/delivery.d.ts +20 -0
- package/build/lib/machines/transport/delivery.js +37 -0
- package/build/lib/machines/transport/index.d.ts +13 -0
- package/build/lib/machines/transport/index.js +29 -0
- package/build/lib/machines/transport/memory-broker.d.ts +35 -0
- package/build/lib/machines/transport/memory-broker.js +145 -0
- package/build/lib/machines/transport/memory.d.ts +40 -0
- package/build/lib/machines/transport/memory.js +79 -0
- package/build/lib/machines/transport/mqtt-bus.d.ts +24 -0
- package/build/lib/machines/transport/mqtt-bus.js +43 -0
- package/build/lib/machines/transport/mqtt-link.d.ts +16 -0
- package/build/lib/machines/transport/mqtt-link.js +37 -0
- package/build/lib/machines/transport/mqtt-session.d.ts +34 -0
- package/build/lib/machines/transport/mqtt-session.js +127 -0
- package/build/lib/machines/transport/nats-bus.d.ts +36 -0
- package/build/lib/machines/transport/nats-bus.js +47 -0
- package/build/lib/machines/transport/nats-feed.d.ts +26 -0
- package/build/lib/machines/transport/nats-feed.js +49 -0
- package/build/lib/machines/transport/pubsub-feed.d.ts +17 -0
- package/build/lib/machines/transport/pubsub-feed.js +39 -0
- package/build/lib/machines/transport/serial.d.ts +21 -0
- package/build/lib/machines/transport/serial.js +54 -0
- package/build/lib/machines/transport/stream-bus.d.ts +40 -0
- package/build/lib/machines/transport/stream-bus.js +57 -0
- package/build/lib/machines/transport/streams.d.ts +24 -0
- package/build/lib/machines/transport/streams.js +49 -0
- package/build/lib/messaging/config.d.ts +25 -0
- package/build/lib/messaging/config.js +55 -0
- package/build/lib/messaging/constants.d.ts +26 -0
- package/build/lib/messaging/constants.js +26 -0
- package/build/lib/messaging/index.d.ts +24 -0
- package/build/lib/messaging/index.js +65 -0
- package/build/lib/messaging/memory.d.ts +16 -0
- package/build/lib/messaging/memory.js +147 -0
- package/build/lib/messaging/nats/permissions.d.ts +13 -0
- package/build/lib/messaging/nats/permissions.js +22 -0
- package/build/lib/messaging/nats/provider.d.ts +30 -0
- package/build/lib/messaging/nats/provider.js +77 -0
- package/build/lib/messaging/nats/render-conf.d.ts +68 -0
- package/build/lib/messaging/nats/render-conf.js +58 -0
- package/build/lib/messaging/nats/streams.d.ts +26 -0
- package/build/lib/messaging/nats/streams.js +97 -0
- package/build/lib/messaging/subjects.d.ts +8 -0
- package/build/lib/messaging/subjects.js +25 -0
- package/build/scripts/bambu-boat.d.ts +17 -0
- package/build/scripts/bambu-boat.js +134 -0
- package/build/scripts/bambu-print.d.ts +18 -0
- package/build/scripts/bambu-print.js +114 -0
- package/build/scripts/machine-line.d.ts +33 -0
- package/build/scripts/machine-line.js +301 -0
- package/build/services/machines/commands.d.ts +29 -0
- package/build/services/machines/commands.js +37 -0
- package/build/services/machines/config.d.ts +49 -0
- package/build/services/machines/config.js +94 -0
- package/build/services/machines/contracts.d.ts +210 -0
- package/build/services/machines/contracts.js +124 -0
- package/build/services/machines/forms.d.ts +55 -0
- package/build/services/machines/forms.js +37 -0
- package/build/services/machines/gateway/actions.d.ts +111 -0
- package/build/services/machines/gateway/actions.js +8 -0
- package/build/services/machines/gateway/address-book.d.ts +16 -0
- package/build/services/machines/gateway/address-book.js +24 -0
- package/build/services/machines/gateway/admission.d.ts +35 -0
- package/build/services/machines/gateway/admission.js +66 -0
- package/build/services/machines/gateway/api-escalations.d.ts +23 -0
- package/build/services/machines/gateway/api-escalations.js +101 -0
- package/build/services/machines/gateway/dispatcher.d.ts +76 -0
- package/build/services/machines/gateway/dispatcher.js +197 -0
- package/build/services/machines/gateway/escalations.d.ts +51 -0
- package/build/services/machines/gateway/escalations.js +40 -0
- package/build/services/machines/gateway/executor.d.ts +60 -0
- package/build/services/machines/gateway/executor.js +221 -0
- package/build/services/machines/gateway/gateway.d.ts +88 -0
- package/build/services/machines/gateway/gateway.js +270 -0
- package/build/services/machines/gateway/index.d.ts +10 -0
- package/build/services/machines/gateway/index.js +26 -0
- package/build/services/machines/gateway/jobs.d.ts +16 -0
- package/build/services/machines/gateway/jobs.js +24 -0
- package/build/services/machines/gateway/lifecycle.d.ts +21 -0
- package/build/services/machines/gateway/lifecycle.js +89 -0
- package/build/services/machines/gateway/log.d.ts +41 -0
- package/build/services/machines/gateway/log.js +42 -0
- package/build/services/machines/gateway/lt-http.d.ts +51 -0
- package/build/services/machines/gateway/lt-http.js +120 -0
- package/build/services/machines/gateway/pacing.d.ts +43 -0
- package/build/services/machines/gateway/pacing.js +158 -0
- package/build/services/machines/gateway/plan-escalation.d.ts +19 -0
- package/build/services/machines/gateway/plan-escalation.js +210 -0
- package/build/services/machines/gateway/plan-machine.d.ts +17 -0
- package/build/services/machines/gateway/plan-machine.js +283 -0
- package/build/services/machines/gateway/plan-shared.d.ts +18 -0
- package/build/services/machines/gateway/plan-shared.js +36 -0
- package/build/services/machines/gateway/provision.d.ts +22 -0
- package/build/services/machines/gateway/provision.js +79 -0
- package/build/services/machines/gateway/receipts.d.ts +33 -0
- package/build/services/machines/gateway/receipts.js +65 -0
- package/build/services/machines/gateway/reconcile.d.ts +35 -0
- package/build/services/machines/gateway/reconcile.js +99 -0
- package/build/services/machines/gateway/registration.d.ts +38 -0
- package/build/services/machines/gateway/registration.js +121 -0
- package/build/services/machines/gateway/reply-waiter.d.ts +24 -0
- package/build/services/machines/gateway/reply-waiter.js +65 -0
- package/build/services/machines/gateway/state.d.ts +145 -0
- package/build/services/machines/gateway/state.js +241 -0
- package/build/services/machines/gateway/trace.d.ts +16 -0
- package/build/services/machines/gateway/trace.js +43 -0
- package/build/services/machines/identities.d.ts +25 -0
- package/build/services/machines/identities.js +77 -0
- package/build/services/machines/index.d.ts +11 -0
- package/build/services/machines/index.js +27 -0
- package/build/services/machines/leadership.d.ts +35 -0
- package/build/services/machines/leadership.js +100 -0
- package/build/services/machines/roles.d.ts +13 -0
- package/build/services/machines/roles.js +62 -0
- package/build/services/machines/rows.d.ts +30 -0
- package/build/services/machines/rows.js +71 -0
- package/build/services/machines/select.d.ts +25 -0
- package/build/services/machines/select.js +76 -0
- package/build/services/machines/surface.d.ts +33 -0
- package/build/services/machines/surface.js +104 -0
- package/build/services/machines/testing/fake-long-tail.d.ts +79 -0
- package/build/services/machines/testing/fake-long-tail.js +158 -0
- package/build/services/machines/testing/index.d.ts +3 -0
- package/build/services/machines/testing/index.js +19 -0
- package/build/services/machines/testing/recording-log.d.ts +14 -0
- package/build/services/machines/testing/recording-log.js +22 -0
- package/build/services/machines/testing/wait.d.ts +3 -0
- package/build/services/machines/testing/wait.js +17 -0
- package/build/services/mcp/db-server/schemas.d.ts +2 -2
- package/build/start/adapters.js +11 -2
- package/build/start/index.js +20 -1
- package/build/system/mcp-servers/admin/schemas.d.ts +221 -221
- package/build/system/mcp-servers/db-query/schemas.d.ts +2 -2
- package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -6
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/index.d.ts +3 -1
- package/build/types/machines.d.ts +167 -0
- package/build/types/machines.js +7 -0
- package/build/types/messaging.d.ts +72 -0
- package/build/types/messaging.js +11 -0
- package/build/types/startup.d.ts +49 -1
- package/docs/events.md +50 -4
- package/docs/machines.md +322 -0
- package/package.json +13 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* One gateway per line across every replica. Each line contends for a
|
|
4
|
+
* Postgres advisory lock on a dedicated connection; `pg_advisory_lock`
|
|
5
|
+
* waits on the server, so standby replicas hold no timers and the next one
|
|
6
|
+
* takes over the moment the leader's session ends.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LineLeadership = void 0;
|
|
10
|
+
const db_1 = require("../../lib/db");
|
|
11
|
+
const LOCK_SQL = 'SELECT pg_advisory_lock(hashtext($1))';
|
|
12
|
+
const RETRY_MS = 5_000;
|
|
13
|
+
const UNLOCK_SQL = 'SELECT pg_advisory_unlock(hashtext($1))';
|
|
14
|
+
class LineLeadership {
|
|
15
|
+
constructor(key, opts = {}) {
|
|
16
|
+
this.key = key;
|
|
17
|
+
this.stopped = false;
|
|
18
|
+
this.leading = false;
|
|
19
|
+
this.connect = opts.connect ?? (() => (0, db_1.getPool)().connect());
|
|
20
|
+
this.retryMs = opts.retryMs ?? RETRY_MS;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Wait for the lock, then call `lead`. When the connection is refused,
|
|
24
|
+
* leading fails or the connection drops, `lost` runs before this replica lets
|
|
25
|
+
* the lock go, so no other replica leads alongside a gateway that is
|
|
26
|
+
* stopping; then the line contends again after a pause.
|
|
27
|
+
*/
|
|
28
|
+
async run(lead, lost) {
|
|
29
|
+
while (!this.stopped) {
|
|
30
|
+
await this.leadOnce(lead, lost);
|
|
31
|
+
if (this.stopped)
|
|
32
|
+
return;
|
|
33
|
+
await new Promise((r) => setTimeout(r, this.retryMs));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async leadOnce(lead, lost) {
|
|
37
|
+
let client;
|
|
38
|
+
try {
|
|
39
|
+
client = await this.connect();
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (!this.stopped)
|
|
43
|
+
await lost(err);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (this.stopped) {
|
|
47
|
+
client.release(true);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.client = client;
|
|
51
|
+
const err = await this.holdLock(client, lead);
|
|
52
|
+
this.leading = false;
|
|
53
|
+
this.endTurn = undefined;
|
|
54
|
+
// stop() has let the connection go.
|
|
55
|
+
if (this.stopped)
|
|
56
|
+
return;
|
|
57
|
+
await lost(err);
|
|
58
|
+
this.client = undefined;
|
|
59
|
+
client.release(true);
|
|
60
|
+
}
|
|
61
|
+
/** Lead while the lock is held; the error that ended it. */
|
|
62
|
+
async holdLock(client, lead) {
|
|
63
|
+
const ended = new Promise((resolve) => {
|
|
64
|
+
client.once('error', resolve);
|
|
65
|
+
this.endTurn = () => resolve(new Error('leadership stopped'));
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
await client.query(LOCK_SQL, [this.key]);
|
|
69
|
+
if (this.stopped)
|
|
70
|
+
return new Error('leadership stopped');
|
|
71
|
+
this.leading = true;
|
|
72
|
+
await lead();
|
|
73
|
+
return await ended;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** A leader unlocks; a replica still waiting drops its connection, which ends the wait. */
|
|
80
|
+
async stop() {
|
|
81
|
+
this.stopped = true;
|
|
82
|
+
this.endTurn?.();
|
|
83
|
+
const client = this.client;
|
|
84
|
+
this.client = undefined;
|
|
85
|
+
if (!client)
|
|
86
|
+
return;
|
|
87
|
+
if (!this.leading) {
|
|
88
|
+
client.release(true);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await client.query(UNLOCK_SQL, [this.key]);
|
|
93
|
+
client.release();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
client.release(true);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.LineLeadership = LineLeadership;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The roles a line needs, as code-owned role declarations: one station role
|
|
3
|
+
* per process (a machine resolves the handoffs into it), the exit role (a
|
|
4
|
+
* person receives finished units) and the operator role (a person decides
|
|
5
|
+
* failed handoffs and closes alarm and machine rows). Each role owns its
|
|
6
|
+
* form; only the operator role enforces it on the server, because handoff
|
|
7
|
+
* rows also collect machine records the gateway already validates.
|
|
8
|
+
*/
|
|
9
|
+
import type { LTRoleConfig } from '../../types/startup';
|
|
10
|
+
import { type LineDef } from '../../lib/machines/line';
|
|
11
|
+
export declare function machineRoleConfigs(line: LineDef): LTRoleConfig[];
|
|
12
|
+
/** The host's declarations first; line roles only where the host declared none. */
|
|
13
|
+
export declare function withMachineRoles(declared: LTRoleConfig[] | undefined, lines: LineDef[]): LTRoleConfig[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The roles a line needs, as code-owned role declarations: one station role
|
|
4
|
+
* per process (a machine resolves the handoffs into it), the exit role (a
|
|
5
|
+
* person receives finished units) and the operator role (a person decides
|
|
6
|
+
* failed handoffs and closes alarm and machine rows). Each role owns its
|
|
7
|
+
* form; only the operator role enforces it on the server, because handoff
|
|
8
|
+
* rows also collect machine records the gateway already validates.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.machineRoleConfigs = machineRoleConfigs;
|
|
12
|
+
exports.withMachineRoles = withMachineRoles;
|
|
13
|
+
const line_1 = require("../../lib/machines/line");
|
|
14
|
+
const forms_1 = require("./forms");
|
|
15
|
+
const BOARD = { sla_minutes: 10, target_per_hour: 60, priority_threshold_minutes: 10, ops_visible: true };
|
|
16
|
+
function machineRoleConfigs(line) {
|
|
17
|
+
const stations = [...new Map(line.machines.map((m) => [(0, line_1.machineRole)(m), m])).values()];
|
|
18
|
+
const chain = stations.map((m, i) => ({
|
|
19
|
+
role: (0, line_1.machineRole)(m),
|
|
20
|
+
title: `Line: ${m.process}`,
|
|
21
|
+
description: `Units handed to ${m.process}; the machine claims and resolves each handoff.`,
|
|
22
|
+
...(i > 0 ? { parent_role: (0, line_1.machineRole)(stations[i - 1]) } : {}),
|
|
23
|
+
form_schema: forms_1.HANDOFF_FORM_SCHEMA,
|
|
24
|
+
enforce_schema: false,
|
|
25
|
+
...BOARD,
|
|
26
|
+
}));
|
|
27
|
+
return [
|
|
28
|
+
...chain,
|
|
29
|
+
{
|
|
30
|
+
role: (0, line_1.exitRole)(line),
|
|
31
|
+
title: 'Line exit',
|
|
32
|
+
description: 'Finished units leaving the line; a person confirms they are in hand.',
|
|
33
|
+
...(chain.length ? { parent_role: chain[chain.length - 1].role } : {}),
|
|
34
|
+
upstream_roles: [(0, line_1.operatorRole)(line)],
|
|
35
|
+
form_schema: forms_1.HANDOFF_FORM_SCHEMA,
|
|
36
|
+
enforce_schema: false,
|
|
37
|
+
...BOARD,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
role: (0, line_1.operatorRole)(line),
|
|
41
|
+
title: 'Line operator',
|
|
42
|
+
description: 'Failed handoffs, stopping alarms, and machines that went dark or quiet.',
|
|
43
|
+
form_schema: forms_1.OPERATOR_FORM_SCHEMA,
|
|
44
|
+
enforce_schema: true,
|
|
45
|
+
...BOARD,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
/** The host's declarations first; line roles only where the host declared none. */
|
|
50
|
+
function withMachineRoles(declared = [], lines) {
|
|
51
|
+
const out = [...declared];
|
|
52
|
+
const seen = new Set(out.map((r) => r.role));
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
for (const role of machineRoleConfigs(line)) {
|
|
55
|
+
if (seen.has(role.role))
|
|
56
|
+
continue;
|
|
57
|
+
seen.add(role.role);
|
|
58
|
+
out.push(role);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which escalation rows are machine handoffs, and what each one asks for.
|
|
3
|
+
* The workflow parks an ordinary row; this adapter reads it into the task
|
|
4
|
+
* the gateway dispatches. The standard adapter reads the handoff facets
|
|
5
|
+
* (contracts.ts); a host whose rows carry its own facets supplies its own,
|
|
6
|
+
* so neither the workflow nor its rows change to be resolved by a machine.
|
|
7
|
+
*/
|
|
8
|
+
import type { LineDef } from '../../lib/machines/line';
|
|
9
|
+
import { type HandoffFacets } from './contracts';
|
|
10
|
+
/** The parts of an escalation row the adapter reads. */
|
|
11
|
+
export interface EscalationRowView {
|
|
12
|
+
id: string;
|
|
13
|
+
role: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
subtype?: string;
|
|
16
|
+
metadata?: Record<string, any>;
|
|
17
|
+
}
|
|
18
|
+
/** Open rows to read when the gateway starts leading a line. */
|
|
19
|
+
export interface OpenRowQuery {
|
|
20
|
+
type?: string;
|
|
21
|
+
role?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface HandoffRowAdapter {
|
|
24
|
+
openRows(line: LineDef): OpenRowQuery[];
|
|
25
|
+
/** The handoff a row asks for, or null when it is not a machine handoff. */
|
|
26
|
+
toHandoff(row: EscalationRowView): HandoffFacets | null;
|
|
27
|
+
}
|
|
28
|
+
/** Read standard handoff facets; null when the line, job or units are missing. */
|
|
29
|
+
export declare function readHandoffFacets(md?: Record<string, any>): HandoffFacets | null;
|
|
30
|
+
export declare const standardRows: HandoffRowAdapter;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Which escalation rows are machine handoffs, and what each one asks for.
|
|
4
|
+
* The workflow parks an ordinary row; this adapter reads it into the task
|
|
5
|
+
* the gateway dispatches. The standard adapter reads the handoff facets
|
|
6
|
+
* (contracts.ts); a host whose rows carry its own facets supplies its own,
|
|
7
|
+
* so neither the workflow nor its rows change to be resolved by a machine.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.standardRows = void 0;
|
|
11
|
+
exports.readHandoffFacets = readHandoffFacets;
|
|
12
|
+
const contracts_1 = require("./contracts");
|
|
13
|
+
const str = (v) => (v === undefined || v === null || v === '' ? null : String(v));
|
|
14
|
+
/** A plain object of parameters; anything else is not parameters. */
|
|
15
|
+
function paramsOf(v) {
|
|
16
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? v : undefined;
|
|
17
|
+
}
|
|
18
|
+
function artifactOf(v) {
|
|
19
|
+
if (!v || typeof v.url !== 'string' || !v.url || typeof v.sha256 !== 'string' || !v.sha256)
|
|
20
|
+
return undefined;
|
|
21
|
+
return {
|
|
22
|
+
url: v.url, sha256: v.sha256,
|
|
23
|
+
...(typeof v.id === 'string' && v.id ? { id: v.id } : {}),
|
|
24
|
+
...(typeof v.contentType === 'string' && v.contentType ? { contentType: v.contentType } : {}),
|
|
25
|
+
...(Number.isInteger(v.sizeBytes) && v.sizeBytes >= 0 ? { sizeBytes: v.sizeBytes } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function artifactsOf(v) {
|
|
29
|
+
const list = Array.isArray(v) ? v.map(artifactOf).filter((a) => !!a) : [];
|
|
30
|
+
return list.length ? list : undefined;
|
|
31
|
+
}
|
|
32
|
+
function unitsOf(v) {
|
|
33
|
+
if (!Array.isArray(v))
|
|
34
|
+
return [];
|
|
35
|
+
return v.filter((u) => u && u.unitId).map((u) => {
|
|
36
|
+
const artifact = artifactOf(u.artifact);
|
|
37
|
+
const artifacts = artifactsOf(u.artifacts);
|
|
38
|
+
const parameters = paramsOf(u.parameters);
|
|
39
|
+
return {
|
|
40
|
+
unitId: String(u.unitId), carrierId: str(u.carrierId),
|
|
41
|
+
...(artifact ? { artifact } : {}), ...(artifacts ? { artifacts } : {}), ...(parameters ? { parameters } : {}),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Read standard handoff facets; null when the line, job or units are missing. */
|
|
46
|
+
function readHandoffFacets(md = {}) {
|
|
47
|
+
const units = unitsOf(md.units);
|
|
48
|
+
if (!md.siteId || !md.lineId || !md.jobId || !units.length)
|
|
49
|
+
return null;
|
|
50
|
+
return {
|
|
51
|
+
siteId: String(md.siteId),
|
|
52
|
+
lineId: String(md.lineId),
|
|
53
|
+
units,
|
|
54
|
+
jobId: String(md.jobId),
|
|
55
|
+
senderJobId: str(md.senderJobId),
|
|
56
|
+
orderRef: String(md.orderRef ?? ''),
|
|
57
|
+
fromMachine: str(md.fromMachine),
|
|
58
|
+
toMachine: str(md.toMachine),
|
|
59
|
+
recipeId: str(md.recipeId),
|
|
60
|
+
recipeVersion: str(md.recipeVersion),
|
|
61
|
+
process: str(md.process),
|
|
62
|
+
artifactUrl: str(md.artifactUrl),
|
|
63
|
+
artifactSha256: str(md.artifactSha256),
|
|
64
|
+
attempt: Number(md.attempt ?? 1),
|
|
65
|
+
...(paramsOf(md.parameters) ? { parameters: paramsOf(md.parameters) } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
exports.standardRows = {
|
|
69
|
+
openRows: () => [{ type: contracts_1.ROW_TYPE.HANDOFF }],
|
|
70
|
+
toHandoff: (row) => (row.type === contracts_1.ROW_TYPE.HANDOFF ? readHandoffFacets(row.metadata) : null),
|
|
71
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport selection from config. The memory transport only exists inside
|
|
3
|
+
* one process (tests build it directly), so asking for it here throws, as
|
|
4
|
+
* does any mode missing what it needs.
|
|
5
|
+
*/
|
|
6
|
+
import { type MachineLineConfig } from './config';
|
|
7
|
+
import { type LineDef } from '../../lib/machines/line';
|
|
8
|
+
import { type LineCodec } from '../../lib/machines/protocol';
|
|
9
|
+
import { messagingRegistry } from '../../lib/messaging';
|
|
10
|
+
import type { CallbackEventAdapter } from '../../lib/events/callback';
|
|
11
|
+
import type { EscalationFeed, MachineBus, MachineLink } from '../../lib/machines/transport/delivery';
|
|
12
|
+
/** The codec for a line when the host supplies none: the line protocol under MACHINE_TOPIC_ROOT. */
|
|
13
|
+
export declare function defaultCodec(config: MachineLineConfig, line?: LineDef): LineCodec;
|
|
14
|
+
/** What the stream bus needs from Long Tail's messaging; the registry by default. */
|
|
15
|
+
export type LineMessaging = Pick<typeof messagingRegistry, 'streams' | 'pubsub'>;
|
|
16
|
+
/**
|
|
17
|
+
* The gateway's view of every machine on the line. With `nats`, events are read
|
|
18
|
+
* from Long Tail's messaging streams; MACHINE_SITE_NATS_URL instead names a NATS
|
|
19
|
+
* server of the site's own, which the bus connects to itself.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createMachineBus(config: MachineLineConfig, line: LineDef, codec?: LineCodec, messaging?: LineMessaging): MachineBus;
|
|
22
|
+
/** Long Tail's escalation events for the gateway; `in-process` needs the running instance's callback adapter. */
|
|
23
|
+
export declare function createEscalationFeed(config: MachineLineConfig, line: LineDef, callbacks?: CallbackEventAdapter): EscalationFeed;
|
|
24
|
+
/** One MQTT link per simulated machine; machines speak MQTT in every networked mode. */
|
|
25
|
+
export declare function createLinkFactory(config: MachineLineConfig): () => MachineLink;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Transport selection from config. The memory transport only exists inside
|
|
4
|
+
* one process (tests build it directly), so asking for it here throws, as
|
|
5
|
+
* does any mode missing what it needs.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.defaultCodec = defaultCodec;
|
|
9
|
+
exports.createMachineBus = createMachineBus;
|
|
10
|
+
exports.createEscalationFeed = createEscalationFeed;
|
|
11
|
+
exports.createLinkFactory = createLinkFactory;
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
const line_1 = require("../../lib/machines/line");
|
|
14
|
+
const protocol_1 = require("../../lib/machines/protocol");
|
|
15
|
+
const mqtt_bus_1 = require("../../lib/machines/transport/mqtt-bus");
|
|
16
|
+
const mqtt_link_1 = require("../../lib/machines/transport/mqtt-link");
|
|
17
|
+
const nats_bus_1 = require("../../lib/machines/transport/nats-bus");
|
|
18
|
+
const stream_bus_1 = require("../../lib/machines/transport/stream-bus");
|
|
19
|
+
const callback_feed_1 = require("../../lib/machines/transport/callback-feed");
|
|
20
|
+
const nats_feed_1 = require("../../lib/machines/transport/nats-feed");
|
|
21
|
+
const pubsub_feed_1 = require("../../lib/machines/transport/pubsub-feed");
|
|
22
|
+
const messaging_1 = require("../../lib/messaging");
|
|
23
|
+
const IN_PROCESS_ONLY = 'lives inside one process; build MemoryMachineBus and MemoryEscalationFeed directly';
|
|
24
|
+
function mqttOf(config) {
|
|
25
|
+
if (!config.mqtt)
|
|
26
|
+
throw new Error(`MACHINE_TRANSPORT=${config.transport} has no MQTT endpoint (MACHINE_MQTT_URL)`);
|
|
27
|
+
return config.mqtt;
|
|
28
|
+
}
|
|
29
|
+
/** The codec for a line when the host supplies none: the line protocol under MACHINE_TOPIC_ROOT. */
|
|
30
|
+
function defaultCodec(config, line) {
|
|
31
|
+
return (0, protocol_1.genericCodec)({ root: config.topicRoot, line });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The gateway's view of every machine on the line. With `nats`, events are read
|
|
35
|
+
* from Long Tail's messaging streams; MACHINE_SITE_NATS_URL instead names a NATS
|
|
36
|
+
* server of the site's own, which the bus connects to itself.
|
|
37
|
+
*/
|
|
38
|
+
function createMachineBus(config, line, codec = defaultCodec(config, line), messaging = messaging_1.messagingRegistry) {
|
|
39
|
+
switch (config.transport) {
|
|
40
|
+
case config_1.MACHINE_TRANSPORT.NATS: {
|
|
41
|
+
const where = { mqtt: mqttOf(config), controllerId: (0, line_1.controllerId)(line), siteId: line.siteId, lineId: line.lineId, codec };
|
|
42
|
+
if (config.siteNatsUrl) {
|
|
43
|
+
return new nats_bus_1.NatsMachineBus({ natsUrl: config.siteNatsUrl, user: config.siteNatsUser, password: config.siteNatsPassword, ...where });
|
|
44
|
+
}
|
|
45
|
+
return new stream_bus_1.StreamMachineBus({ streams: messaging.streams, pubsub: messaging.pubsub, ...where });
|
|
46
|
+
}
|
|
47
|
+
case config_1.MACHINE_TRANSPORT.MQTT:
|
|
48
|
+
return new mqtt_bus_1.MqttMachineBus(mqttOf(config), (0, line_1.controllerId)(line), (0, protocol_1.lineFilter)(codec.root, line.siteId, line.lineId));
|
|
49
|
+
default:
|
|
50
|
+
throw new Error(`MACHINE_TRANSPORT=${config.transport} ${IN_PROCESS_ONLY}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Long Tail's escalation events for the gateway; `in-process` needs the running instance's callback adapter. */
|
|
54
|
+
function createEscalationFeed(config, line, callbacks) {
|
|
55
|
+
switch (config.feed) {
|
|
56
|
+
case config_1.ESCALATION_FEED.IN_PROCESS:
|
|
57
|
+
if (!callbacks)
|
|
58
|
+
throw new Error('MACHINE_ESCALATION_FEED=in-process runs inside start({ machines })');
|
|
59
|
+
return new callback_feed_1.CallbackEscalationFeed(callbacks);
|
|
60
|
+
case config_1.ESCALATION_FEED.CORE:
|
|
61
|
+
if (messaging_1.messagingRegistry.hasProvider)
|
|
62
|
+
return new pubsub_feed_1.PubSubEscalationFeed(messaging_1.messagingRegistry.pubsub);
|
|
63
|
+
if (!config.ltNatsUrl)
|
|
64
|
+
throw new Error('MACHINE_ESCALATION_FEED=core needs Long Tail messaging (LT_MESSAGING=nats) or NATS_URL');
|
|
65
|
+
return new nats_feed_1.CoreEscalationFeed({ natsUrl: config.ltNatsUrl, token: config.ltNatsToken });
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`MACHINE_ESCALATION_FEED=${config.feed} ${IN_PROCESS_ONLY}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** One MQTT link per simulated machine; machines speak MQTT in every networked mode. */
|
|
71
|
+
function createLinkFactory(config) {
|
|
72
|
+
if (config.transport === config_1.MACHINE_TRANSPORT.MEMORY)
|
|
73
|
+
throw new Error(`MACHINE_TRANSPORT=memory ${IN_PROCESS_ONLY}`);
|
|
74
|
+
const endpoint = mqttOf(config);
|
|
75
|
+
return () => new mqtt_link_1.MqttMachineLink(endpoint);
|
|
76
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The machine surface inside Long Tail. For each line this deployment
|
|
3
|
+
* controls, one replica leads (an advisory lock), makes sure the line's bot
|
|
4
|
+
* accounts exist, and runs the gateway: escalation events in, machine
|
|
5
|
+
* messages in, verbs performed in process as each machine. Workflows never
|
|
6
|
+
* see any of it; their rows are resolved the way a form resolves them.
|
|
7
|
+
*/
|
|
8
|
+
import type { CallbackEventAdapter } from '../../lib/events/callback';
|
|
9
|
+
import type { LineDef } from '../../lib/machines/line';
|
|
10
|
+
import { type MachineLineConfig } from './config';
|
|
11
|
+
import { type GatewayLog } from './gateway/log';
|
|
12
|
+
import type { LineCodec } from '../../lib/machines/protocol';
|
|
13
|
+
import type { HandoffRowAdapter } from './rows';
|
|
14
|
+
type Env = Record<string, string | undefined>;
|
|
15
|
+
export interface MachineSurfaceOptions {
|
|
16
|
+
lines: LineDef[];
|
|
17
|
+
/** Transport settings (MACHINE_*); defaults to process.env. */
|
|
18
|
+
env?: Env;
|
|
19
|
+
callbacks?: CallbackEventAdapter;
|
|
20
|
+
log?: GatewayLog;
|
|
21
|
+
codec?: LineCodec;
|
|
22
|
+
rows?: HandoffRowAdapter;
|
|
23
|
+
}
|
|
24
|
+
/** Inside Long Tail the gateway reads escalation events in process. */
|
|
25
|
+
export declare function surfaceConfig(env: Env): MachineLineConfig;
|
|
26
|
+
export declare class MachineSurface {
|
|
27
|
+
private readonly runs;
|
|
28
|
+
private constructor();
|
|
29
|
+
/** Validate the configuration, then contend for every line in the background. */
|
|
30
|
+
static start(opts: MachineSurfaceOptions): MachineSurface;
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The machine surface inside Long Tail. For each line this deployment
|
|
4
|
+
* controls, one replica leads (an advisory lock), makes sure the line's bot
|
|
5
|
+
* accounts exist, and runs the gateway: escalation events in, machine
|
|
6
|
+
* messages in, verbs performed in process as each machine. Workflows never
|
|
7
|
+
* see any of it; their rows are resolved the way a form resolves them.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.MachineSurface = void 0;
|
|
11
|
+
exports.surfaceConfig = surfaceConfig;
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
const api_escalations_1 = require("./gateway/api-escalations");
|
|
14
|
+
const gateway_1 = require("./gateway/gateway");
|
|
15
|
+
const log_1 = require("./gateway/log");
|
|
16
|
+
const identities_1 = require("./identities");
|
|
17
|
+
const leadership_1 = require("./leadership");
|
|
18
|
+
const select_1 = require("./select");
|
|
19
|
+
/** Inside Long Tail the gateway reads escalation events in process. */
|
|
20
|
+
function surfaceConfig(env) {
|
|
21
|
+
const transport = env.MACHINE_TRANSPORT;
|
|
22
|
+
if (transport !== config_1.MACHINE_TRANSPORT.NATS && transport !== config_1.MACHINE_TRANSPORT.MQTT) {
|
|
23
|
+
throw new Error(`start({ machines }) needs MACHINE_TRANSPORT=nats or mqtt (got "${transport ?? ''}")`);
|
|
24
|
+
}
|
|
25
|
+
return (0, config_1.loadMachineLineConfig)({ ...env, MACHINE_ESCALATION_FEED: env.MACHINE_ESCALATION_FEED ?? config_1.ESCALATION_FEED.IN_PROCESS });
|
|
26
|
+
}
|
|
27
|
+
class LineRun {
|
|
28
|
+
constructor(line, config, opts, log) {
|
|
29
|
+
this.line = line;
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.opts = opts;
|
|
32
|
+
this.log = log;
|
|
33
|
+
this.stopped = false;
|
|
34
|
+
this.leadership = new leadership_1.LineLeadership(`lt-machine-line:${line.siteId}/${line.lineId}`);
|
|
35
|
+
}
|
|
36
|
+
start() {
|
|
37
|
+
const where = { site: this.line.siteId, line: this.line.lineId };
|
|
38
|
+
this.log.info(log_1.GATEWAY_LOG.STANDBY, where);
|
|
39
|
+
this.leadership.run(() => (this.leading = this.lead()), async (err) => {
|
|
40
|
+
this.log.warn(log_1.GATEWAY_LOG.LEADERSHIP_LOST, { ...where, error: err.message });
|
|
41
|
+
await this.gateway?.stop();
|
|
42
|
+
this.gateway = undefined;
|
|
43
|
+
}).catch((err) => this.log.error(log_1.GATEWAY_LOG.START_FAILED, { ...where, error: err.message }));
|
|
44
|
+
}
|
|
45
|
+
/** A gateway still starting finishes first, so none is left running after stop. */
|
|
46
|
+
async stop() {
|
|
47
|
+
this.stopped = true;
|
|
48
|
+
await this.leading?.catch(() => undefined);
|
|
49
|
+
await this.gateway?.stop();
|
|
50
|
+
await this.leadership.stop();
|
|
51
|
+
}
|
|
52
|
+
async lead() {
|
|
53
|
+
// 1. Identities, 2. gateway on the line's bus and feed, 3. reconcile and consume (gateway.start)
|
|
54
|
+
const ids = await (0, identities_1.ensureLineBots)(this.line);
|
|
55
|
+
if (this.stopped)
|
|
56
|
+
return;
|
|
57
|
+
const userFor = (as) => {
|
|
58
|
+
const id = ids.get(as);
|
|
59
|
+
if (!id)
|
|
60
|
+
throw new Error(`no bot for "${as}" on ${this.line.siteId}/${this.line.lineId}`);
|
|
61
|
+
return id;
|
|
62
|
+
};
|
|
63
|
+
const { config, line } = this;
|
|
64
|
+
const codec = this.opts.codec ?? (0, select_1.defaultCodec)(config, line);
|
|
65
|
+
this.gateway = new gateway_1.MachineGateway({
|
|
66
|
+
bus: (0, select_1.createMachineBus)(config, line, codec),
|
|
67
|
+
codec,
|
|
68
|
+
rows: this.opts.rows,
|
|
69
|
+
feed: (0, select_1.createEscalationFeed)(config, line, this.opts.callbacks),
|
|
70
|
+
lt: new api_escalations_1.ApiLineEscalations(userFor),
|
|
71
|
+
addresses: new identities_1.BotAddressBook(ids),
|
|
72
|
+
line,
|
|
73
|
+
log: this.log,
|
|
74
|
+
settings: {
|
|
75
|
+
topicRoot: config.topicRoot, replyTimeoutMs: config.replyTimeoutMs, dispatchAttempts: config.dispatchAttempts,
|
|
76
|
+
offlineDebounceMs: config.offlineDebounceMs, dispatchTtlMs: config.dispatchTtlMs, quietMs: config.quietMs,
|
|
77
|
+
receiptDelayMs: config.receiptDelayMs, lifecycle: config.lifecycle, progressStep: config.progressStep,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
await this.gateway.start();
|
|
81
|
+
this.log.info(log_1.GATEWAY_LOG.LEADING, { site: line.siteId, line: line.lineId });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
class MachineSurface {
|
|
85
|
+
constructor(runs) {
|
|
86
|
+
this.runs = runs;
|
|
87
|
+
}
|
|
88
|
+
/** Validate the configuration, then contend for every line in the background. */
|
|
89
|
+
static start(opts) {
|
|
90
|
+
if (!opts.lines.length)
|
|
91
|
+
throw new Error('start({ machines }) needs at least one line');
|
|
92
|
+
const config = surfaceConfig(opts.env ?? process.env);
|
|
93
|
+
const log = opts.log ?? log_1.consoleLog;
|
|
94
|
+
const runs = opts.lines.map((line) => new LineRun(line, config, opts, log));
|
|
95
|
+
for (const run of runs)
|
|
96
|
+
run.start();
|
|
97
|
+
return new MachineSurface(runs);
|
|
98
|
+
}
|
|
99
|
+
async stop() {
|
|
100
|
+
for (const run of this.runs)
|
|
101
|
+
await run.stop();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.MachineSurface = MachineSurface;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-memory Long Tail for the machine gateway: escalation rows with the
|
|
3
|
+
* claim / accumulate / resolve / create verbs, each publishing the lifecycle
|
|
4
|
+
* event Long Tail publishes, plus helpers that play the unit workflow and the
|
|
5
|
+
* people (exit, operator).
|
|
6
|
+
*/
|
|
7
|
+
import { type Actor, type CreateRowInput } from '../gateway/actions';
|
|
8
|
+
import { type LineEscalations, type RowItem, type VerbResult } from '../gateway/escalations';
|
|
9
|
+
import { type HandoffFacets } from '../contracts';
|
|
10
|
+
import type { MemoryEscalationFeed } from '../../../lib/machines/transport';
|
|
11
|
+
import type { OpenRowQuery } from '../rows';
|
|
12
|
+
export interface FakeRow {
|
|
13
|
+
id: string;
|
|
14
|
+
type: string;
|
|
15
|
+
subtype?: string;
|
|
16
|
+
role: string;
|
|
17
|
+
status: 'pending' | 'resolved' | 'cancelled' | 'expired';
|
|
18
|
+
assigned_to?: string;
|
|
19
|
+
metadata: Record<string, any>;
|
|
20
|
+
items: {
|
|
21
|
+
itemKey: string;
|
|
22
|
+
payload: any;
|
|
23
|
+
actor: Actor;
|
|
24
|
+
}[];
|
|
25
|
+
resolver_payload?: Record<string, any>;
|
|
26
|
+
resolved_by?: string;
|
|
27
|
+
envelope?: string;
|
|
28
|
+
escalation_payload?: string;
|
|
29
|
+
created_at?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface Call {
|
|
32
|
+
verb: string;
|
|
33
|
+
as: Actor;
|
|
34
|
+
id: string;
|
|
35
|
+
payload?: any;
|
|
36
|
+
}
|
|
37
|
+
export declare class FakeLongTail implements LineEscalations {
|
|
38
|
+
private readonly feed;
|
|
39
|
+
readonly rows: Map<string, FakeRow>;
|
|
40
|
+
readonly calls: Call[];
|
|
41
|
+
private seq;
|
|
42
|
+
private failing;
|
|
43
|
+
/** The next `times` calls of `verb` fail as a Long Tail outage would (5xx). */
|
|
44
|
+
failNext(verb: 'claim' | 'accumulate' | 'resolve' | 'create', times?: number): void;
|
|
45
|
+
/** Roles each actor holds; an actor outside a row's role gets not found, as Long Tail's RBAC answers. */
|
|
46
|
+
readonly holds: Map<string, Set<string>>;
|
|
47
|
+
private hidden;
|
|
48
|
+
private outage;
|
|
49
|
+
constructor(feed: MemoryEscalationFeed);
|
|
50
|
+
private emit;
|
|
51
|
+
private add;
|
|
52
|
+
/** What the unit workflow's conditionalAccumulator does: park a handoff row. */
|
|
53
|
+
park(role: string, facets: HandoffFacets): FakeRow;
|
|
54
|
+
/** Park any row, as a host workflow parks its own. */
|
|
55
|
+
parkRow(input: {
|
|
56
|
+
type: string;
|
|
57
|
+
role: string;
|
|
58
|
+
subtype?: string;
|
|
59
|
+
metadata: Record<string, any>;
|
|
60
|
+
}): FakeRow;
|
|
61
|
+
/** A person resolving a row on the dashboard. */
|
|
62
|
+
personResolves(id: string, payload: Record<string, any>, person?: string): void;
|
|
63
|
+
/** A person or the workflow cancelling the row. */
|
|
64
|
+
cancel(id: string): void;
|
|
65
|
+
/** The row's timeout firing (conditional `timeout`). */
|
|
66
|
+
expire(id: string): void;
|
|
67
|
+
claim(as: Actor, id: string): Promise<VerbResult>;
|
|
68
|
+
accumulate(as: Actor, id: string, itemKey: string, payload: Record<string, any>, metadata?: Record<string, any>): Promise<VerbResult>;
|
|
69
|
+
resolve(as: Actor, id: string, payload: Record<string, any>): Promise<VerbResult>;
|
|
70
|
+
create(_as: Actor, input: CreateRowInput): Promise<{
|
|
71
|
+
id: string;
|
|
72
|
+
}>;
|
|
73
|
+
itemKeys(id: string): Promise<string[]>;
|
|
74
|
+
items(id: string): Promise<RowItem[]>;
|
|
75
|
+
get(id: string): Promise<Record<string, any> | null>;
|
|
76
|
+
listOpen(query: OpenRowQuery): Promise<Record<string, any>[]>;
|
|
77
|
+
ofType(type: string): FakeRow[];
|
|
78
|
+
ended(id: string, ms?: number): Promise<FakeRow>;
|
|
79
|
+
}
|