@volter/twin-temporal 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/LICENSE +202 -0
- package/README.md +104 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +36 -0
- package/dist/src/generated/hpack.json +1 -0
- package/dist/src/generated/temporal-api.json +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +70 -0
- package/dist/src/temporal-budget.d.ts +31 -0
- package/dist/src/temporal-budget.js +50 -0
- package/dist/src/temporal-capabilities.d.ts +7 -0
- package/dist/src/temporal-capabilities.js +650 -0
- package/dist/src/temporal-conformance.d.ts +8 -0
- package/dist/src/temporal-conformance.js +54 -0
- package/dist/src/temporal-connector.d.ts +32 -0
- package/dist/src/temporal-connector.js +121 -0
- package/dist/src/temporal-engine.d.ts +274 -0
- package/dist/src/temporal-engine.js +2132 -0
- package/dist/src/temporal-grpc.d.ts +51 -0
- package/dist/src/temporal-grpc.js +124 -0
- package/dist/src/temporal-h2.d.ts +40 -0
- package/dist/src/temporal-h2.js +644 -0
- package/dist/src/temporal-history.d.ts +27 -0
- package/dist/src/temporal-history.js +85 -0
- package/dist/src/temporal-http.d.ts +17 -0
- package/dist/src/temporal-http.js +160 -0
- package/dist/src/temporal-json.d.ts +7 -0
- package/dist/src/temporal-json.js +68 -0
- package/dist/src/temporal-proto.d.ts +17 -0
- package/dist/src/temporal-proto.js +46 -0
- package/dist/src/temporal-server.d.ts +25 -0
- package/dist/src/temporal-server.js +56 -0
- package/dist/src/temporal-twin.d.ts +25 -0
- package/dist/src/temporal-twin.js +100 -0
- package/dist/src/temporal-visibility.d.ts +49 -0
- package/dist/src/temporal-visibility.js +293 -0
- package/package.json +59 -0
- package/src/cli.ts +36 -0
- package/src/generated/hpack.json +1 -0
- package/src/generated/temporal-api.json +1 -0
- package/src/index.ts +81 -0
- package/src/temporal-budget.ts +75 -0
- package/src/temporal-capabilities.ts +679 -0
- package/src/temporal-conformance.ts +52 -0
- package/src/temporal-connector.ts +118 -0
- package/src/temporal-engine.ts +2176 -0
- package/src/temporal-grpc.ts +139 -0
- package/src/temporal-h2.ts +614 -0
- package/src/temporal-history.ts +99 -0
- package/src/temporal-http.ts +146 -0
- package/src/temporal-json.ts +77 -0
- package/src/temporal-proto.ts +53 -0
- package/src/temporal-server.ts +67 -0
- package/src/temporal-twin.ts +102 -0
- package/src/temporal-visibility.ts +245 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Conformance: the twin's wire, probed as a client finds it — a real socket, HTTP/2, gRPC framing —
|
|
2
|
+
// against a fresh World root. Dev-only (cli.ts imports it lazily).
|
|
3
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { liveTemporalExecute } from "./temporal-connector.js";
|
|
7
|
+
import { createTemporalTwinServer } from "./temporal-server.js";
|
|
8
|
+
import { GrpcError } from "./temporal-grpc.js";
|
|
9
|
+
import { OPERATOR_SERVICE, WORKFLOW_SERVICE, HEALTH_SERVICE } from "./temporal-proto.js";
|
|
10
|
+
export async function checkTemporalConformance(options = {}) {
|
|
11
|
+
const root = options.root ?? mkdtempSync(join(tmpdir(), 'temporal-conformance-'));
|
|
12
|
+
const server = await createTemporalTwinServer({ root });
|
|
13
|
+
const failures = [];
|
|
14
|
+
let checksRun = 0;
|
|
15
|
+
const check = async (name, probe) => {
|
|
16
|
+
checksRun += 1;
|
|
17
|
+
try {
|
|
18
|
+
if (!(await probe()))
|
|
19
|
+
failures.push(name);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
failures.push(`${name}: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const call = liveTemporalExecute({ address: `127.0.0.1:${server.port}`, tls: false, budgetOptions: { root } });
|
|
26
|
+
const refusal = async (service, method, request) => {
|
|
27
|
+
try {
|
|
28
|
+
await call(service, method, request);
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return error instanceof GrpcError ? error.code : -1;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
try {
|
|
36
|
+
await check('GetSystemInfo answers a server version over h2c', async () => /^\d+\.\d+\.\d+$/.test(String((await call(WORKFLOW_SERVICE, 'GetSystemInfo', {})).serverVersion)));
|
|
37
|
+
await check('the default namespace exists', async () => (await call(WORKFLOW_SERVICE, 'DescribeNamespace', { namespace: 'default' })).namespaceInfo?.name === 'default');
|
|
38
|
+
await check('an unknown namespace is NOT_FOUND', async () => (await refusal(WORKFLOW_SERVICE, 'DescribeNamespace', { namespace: 'nope' })) === 5);
|
|
39
|
+
await check('an RPC the twin does not serve is UNIMPLEMENTED', async () => (await refusal(WORKFLOW_SERVICE, 'ListSchedules', { namespace: 'default' })) === 12);
|
|
40
|
+
await check('an OperatorService RPC the twin does not serve is UNIMPLEMENTED', async () => (await refusal(OPERATOR_SERVICE, 'ListClusters', {})) === 12);
|
|
41
|
+
await check('a start answers a run id, and a second start of the id is ALREADY_EXISTS', async () => {
|
|
42
|
+
const start = { namespace: 'default', workflowId: 'conformance', workflowType: { name: 'wf' }, taskQueue: { name: 'q' } };
|
|
43
|
+
const first = await call(WORKFLOW_SERVICE, 'StartWorkflowExecution', { ...start, requestId: 'a' });
|
|
44
|
+
return /^[0-9a-f-]{36}$/.test(String(first.runId)) && (await refusal(WORKFLOW_SERVICE, 'StartWorkflowExecution', { ...start, requestId: 'b' })) === 6;
|
|
45
|
+
});
|
|
46
|
+
await check('health Check answers SERVING', async () => (await call(HEALTH_SERVICE, 'Check', {})).status === 'SERVING');
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await server.stop();
|
|
50
|
+
if (!options.root)
|
|
51
|
+
rmSync(root, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
return { ok: failures.length === 0, checksRun, failures };
|
|
54
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PushOutcome, type RemoteExecute, type TwinAction } from '@volter/world-core';
|
|
2
|
+
import { TemporalBudget, type TemporalBudgetOptions } from './temporal-budget.js';
|
|
3
|
+
type Obj = Record<string, unknown>;
|
|
4
|
+
/** One unary call on a Temporal frontend: (service, method, request) → response, in the engine's plain form. */
|
|
5
|
+
export type TemporalExecute = (service: string, method: string, request: Obj) => Promise<Obj>;
|
|
6
|
+
/** THE CHOKE POINT — the one place this pack reaches a real Temporal frontend. `address` is host:port
|
|
7
|
+
* (TEMPORAL_ADDRESS); `apiKey` (TEMPORAL_API_KEY) is sent as a bearer token, and a key implies TLS,
|
|
8
|
+
* as the SDKs do. Every call is charged to the rate budget BEFORE it connects (a refused budget
|
|
9
|
+
* throws and nothing is sent) and settled after, even when the call fails; a stand-in budget whose
|
|
10
|
+
* guard methods are not the kernel's is refused rather than trusted. */
|
|
11
|
+
export declare function liveTemporalExecute(options?: {
|
|
12
|
+
address?: string;
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
tls?: boolean;
|
|
15
|
+
namespace?: string;
|
|
16
|
+
budget?: TemporalBudget;
|
|
17
|
+
budgetOptions?: TemporalBudgetOptions;
|
|
18
|
+
}): TemporalExecute;
|
|
19
|
+
/** Pull a real frontend's namespaces and their custom search attributes into the tree. */
|
|
20
|
+
export declare function syncTemporalFromReal(execute: TemporalExecute, options?: {
|
|
21
|
+
root?: string;
|
|
22
|
+
occurredAt?: string;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
pulled: number;
|
|
25
|
+
}>;
|
|
26
|
+
/** The real-system half of the state system (head.ts): performing one landed entry against a real
|
|
27
|
+
* cluster. Refused, by name, for every entry — see the header; a real head over this twin stops its
|
|
28
|
+
* deploy at the first entry with this reason rather than pretending the entry landed. */
|
|
29
|
+
export declare function performTemporalAction(_execute: RemoteExecute, action: TwinAction): Promise<PushOutcome>;
|
|
30
|
+
/** Deploying a World's local runs to a real cluster is refused, by name (see the header). */
|
|
31
|
+
export declare function pushPendingTemporalActions(): Promise<never>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// The connector: pull a real Temporal frontend's namespace configuration into the World, over an
|
|
2
|
+
// INJECTED unary-call function (the real network exists only inside liveTemporalExecute).
|
|
3
|
+
//
|
|
4
|
+
// What a pull observes is what a World needs to stand in for a real cluster before any app runs:
|
|
5
|
+
// its namespaces (DescribeNamespaceResponse, the vendor's shape) and each namespace's custom search
|
|
6
|
+
// attributes (an app's starts are refused over an attribute the namespace does not define). A real
|
|
7
|
+
// cluster's workflow runs are NOT pulled: a run's state is its history, written by the workers that
|
|
8
|
+
// execute it, and a copied history without those workers is a run nothing can advance.
|
|
9
|
+
//
|
|
10
|
+
// Push refuses by name. A World's local runs cannot be performed on a real cluster — a start or a
|
|
11
|
+
// signal could be resent, but everything after it (workflow and activity tasks) is the work of
|
|
12
|
+
// workers, which a deploy does not move.
|
|
13
|
+
import { assertBudgetGuardIntact, observeResource, worldNow } from '@volter/world-core';
|
|
14
|
+
import { TemporalBudget, temporalCallWeight } from "./temporal-budget.js";
|
|
15
|
+
import http2 from 'node:http2';
|
|
16
|
+
import { decodeMessage, encodeMessage, serviceMethods, OPERATOR_SERVICE, WORKFLOW_SERVICE } from "./temporal-proto.js";
|
|
17
|
+
import { GrpcError, grpcFrame, splitGrpcFrames } from "./temporal-grpc.js";
|
|
18
|
+
/** THE CHOKE POINT — the one place this pack reaches a real Temporal frontend. `address` is host:port
|
|
19
|
+
* (TEMPORAL_ADDRESS); `apiKey` (TEMPORAL_API_KEY) is sent as a bearer token, and a key implies TLS,
|
|
20
|
+
* as the SDKs do. Every call is charged to the rate budget BEFORE it connects (a refused budget
|
|
21
|
+
* throws and nothing is sent) and settled after, even when the call fails; a stand-in budget whose
|
|
22
|
+
* guard methods are not the kernel's is refused rather than trusted. */
|
|
23
|
+
export function liveTemporalExecute(options = {}) {
|
|
24
|
+
const address = options.address ?? process.env.TEMPORAL_ADDRESS;
|
|
25
|
+
if (!address)
|
|
26
|
+
throw new Error('liveTemporalExecute: TEMPORAL_ADDRESS is not set (host:port of a Temporal frontend)');
|
|
27
|
+
const apiKey = options.apiKey ?? process.env.TEMPORAL_API_KEY;
|
|
28
|
+
const tls = options.tls ?? Boolean(apiKey);
|
|
29
|
+
if (options.budget !== undefined && options.budget !== null)
|
|
30
|
+
assertBudgetGuardIntact(options.budget, TemporalBudget, 'liveTemporalExecute');
|
|
31
|
+
const budget = options.budget instanceof TemporalBudget ? options.budget : new TemporalBudget({ token: address, ...(options.budgetOptions ?? {}) });
|
|
32
|
+
return async (service, method, request) => {
|
|
33
|
+
const weight = temporalCallWeight(service, method);
|
|
34
|
+
const reservation = budget.checkBudget(weight);
|
|
35
|
+
try {
|
|
36
|
+
return await unaryCall(address, tls, apiKey, options.namespace, service, method, request);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
budget.recordCall(weight, {}, { reservation });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function unaryCall(address, tls, apiKey, namespace, service, method, request) {
|
|
44
|
+
const options = { namespace };
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const table = serviceMethods(service).get(method);
|
|
47
|
+
if (!table) {
|
|
48
|
+
reject(new Error(`liveTemporalExecute: ${service}/${method} is not in the vendored API`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const session = http2.connect(`${tls ? 'https' : 'http'}://${address}`);
|
|
52
|
+
session.on('error', (error) => { session.destroy(); reject(error); });
|
|
53
|
+
const stream = session.request({
|
|
54
|
+
':method': 'POST', ':path': `/${service}/${method}`, 'content-type': 'application/grpc', te: 'trailers',
|
|
55
|
+
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
|
|
56
|
+
...(options.namespace ? { 'temporal-namespace': options.namespace } : {}),
|
|
57
|
+
});
|
|
58
|
+
const chunks = [];
|
|
59
|
+
let status = '';
|
|
60
|
+
let message = '';
|
|
61
|
+
stream.on('response', (headers) => { status = String(headers['grpc-status'] ?? status); message = String(headers['grpc-message'] ?? message); });
|
|
62
|
+
stream.on('trailers', (trailers) => { status = String(trailers['grpc-status'] ?? status); message = String(trailers['grpc-message'] ?? message); });
|
|
63
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
64
|
+
stream.on('error', (error) => { session.destroy(); reject(error); });
|
|
65
|
+
stream.on('end', () => {
|
|
66
|
+
session.close();
|
|
67
|
+
if (status !== '0') {
|
|
68
|
+
let text = message;
|
|
69
|
+
try {
|
|
70
|
+
text = decodeURIComponent(message);
|
|
71
|
+
}
|
|
72
|
+
catch { /* a grpc-message that is not percent-encoding is shown as sent */ }
|
|
73
|
+
reject(new GrpcError(Number(status || 2), text));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const [frame] = splitGrpcFrames(new Uint8Array(Buffer.concat(chunks)));
|
|
78
|
+
resolve(frame ? decodeMessage(table.responseType, frame.message) : {});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
reject(error);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
stream.end(Buffer.from(grpcFrame(encodeMessage(table.requestType, request))));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Pull a real frontend's namespaces and their custom search attributes into the tree. */
|
|
88
|
+
export async function syncTemporalFromReal(execute, options = {}) {
|
|
89
|
+
const at = options.occurredAt ?? worldNow();
|
|
90
|
+
const scope = { ...(options.root !== undefined ? { root: options.root } : {}), at };
|
|
91
|
+
// read everything first: a call that fails part way leaves the tree as it was
|
|
92
|
+
const namespaces = [];
|
|
93
|
+
let pageToken;
|
|
94
|
+
do {
|
|
95
|
+
const listing = await execute(WORKFLOW_SERVICE, 'ListNamespaces', { pageSize: 100, ...(pageToken ? { nextPageToken: pageToken } : {}) });
|
|
96
|
+
namespaces.push(...(listing.namespaces ?? []));
|
|
97
|
+
pageToken = listing.nextPageToken;
|
|
98
|
+
} while (pageToken);
|
|
99
|
+
const observed = [];
|
|
100
|
+
for (const described of namespaces) {
|
|
101
|
+
const name = String(described.namespaceInfo?.name ?? '');
|
|
102
|
+
if (!name)
|
|
103
|
+
throw new Error('temporal: a listed namespace has no name — refusing to fold a partial pull');
|
|
104
|
+
const attributes = await execute(OPERATOR_SERVICE, 'ListSearchAttributes', { namespace: name });
|
|
105
|
+
// the namespace's custom search attributes ride its subject as bookkeeping, as the engine keeps them
|
|
106
|
+
observed.push({ name, fields: { ...described, _searchAttributes: (attributes.customAttributes ?? {}) } });
|
|
107
|
+
}
|
|
108
|
+
for (const { name, fields } of observed)
|
|
109
|
+
observeResource('temporal', { type: 'namespace', id: name, fields }, scope);
|
|
110
|
+
return { pulled: observed.length };
|
|
111
|
+
}
|
|
112
|
+
/** The real-system half of the state system (head.ts): performing one landed entry against a real
|
|
113
|
+
* cluster. Refused, by name, for every entry — see the header; a real head over this twin stops its
|
|
114
|
+
* deploy at the first entry with this reason rather than pretending the entry landed. */
|
|
115
|
+
export async function performTemporalAction(_execute, action) {
|
|
116
|
+
throw new Error(`temporal: performing ${action.operation} on a real Temporal cluster is not supported — a run's history is written by the workers that execute it, which a deploy does not move`);
|
|
117
|
+
}
|
|
118
|
+
/** Deploying a World's local runs to a real cluster is refused, by name (see the header). */
|
|
119
|
+
export async function pushPendingTemporalActions() {
|
|
120
|
+
throw new Error('temporal: pushing local workflow runs to a real Temporal cluster is not supported — a run\'s history is written by the workers that execute it, which a deploy does not move');
|
|
121
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { TemporalStore, type HistoryEvent } from './temporal-history.js';
|
|
2
|
+
type Obj = Record<string, unknown>;
|
|
3
|
+
type Duration = {
|
|
4
|
+
seconds?: number;
|
|
5
|
+
nanos?: number;
|
|
6
|
+
};
|
|
7
|
+
type Timestamp = {
|
|
8
|
+
seconds: number;
|
|
9
|
+
nanos?: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const DEFAULT_NAMESPACE = "default";
|
|
12
|
+
export declare function durationMs(value: unknown): number;
|
|
13
|
+
export declare function toDuration(ms: number): Duration | undefined;
|
|
14
|
+
export declare function toTimestamp(ms: number): Timestamp;
|
|
15
|
+
/** The World's now, in epoch milliseconds. */
|
|
16
|
+
export declare function nowMs(): number;
|
|
17
|
+
/** A payload's value, when its encoding is one this server can read (json/plain, binary/null). */
|
|
18
|
+
export declare function payloadValue(payload: unknown): unknown;
|
|
19
|
+
export declare function jsonPayload(value: unknown, extra?: Record<string, string>): Obj;
|
|
20
|
+
type RetryPolicy = {
|
|
21
|
+
initialInterval?: Duration;
|
|
22
|
+
backoffCoefficient?: number;
|
|
23
|
+
maximumInterval?: Duration;
|
|
24
|
+
maximumAttempts?: number;
|
|
25
|
+
nonRetryableErrorTypes?: string[];
|
|
26
|
+
};
|
|
27
|
+
type ActivityState = {
|
|
28
|
+
scheduledEventId: number;
|
|
29
|
+
activityId: string;
|
|
30
|
+
activityType: Obj;
|
|
31
|
+
taskQueue: string;
|
|
32
|
+
input?: Obj;
|
|
33
|
+
header?: Obj;
|
|
34
|
+
priority?: Obj;
|
|
35
|
+
retryPolicy: RetryPolicy;
|
|
36
|
+
scheduleToCloseMs: number;
|
|
37
|
+
scheduleToStartMs: number;
|
|
38
|
+
startToCloseMs: number;
|
|
39
|
+
heartbeatMs: number;
|
|
40
|
+
attempt: number;
|
|
41
|
+
scheduledAt: number;
|
|
42
|
+
attemptScheduledAt: number;
|
|
43
|
+
availableAt: number;
|
|
44
|
+
startedAt?: number;
|
|
45
|
+
identity?: string;
|
|
46
|
+
requestId?: string;
|
|
47
|
+
heartbeatDetails?: Obj;
|
|
48
|
+
lastHeartbeatAt?: number;
|
|
49
|
+
lastFailure?: Obj;
|
|
50
|
+
lastAttemptCompletedAt?: number;
|
|
51
|
+
cancelRequestedEventId?: number;
|
|
52
|
+
};
|
|
53
|
+
type ChildState = {
|
|
54
|
+
initiatedEventId: number;
|
|
55
|
+
namespace: string;
|
|
56
|
+
workflowId: string;
|
|
57
|
+
workflowType: Obj;
|
|
58
|
+
parentClosePolicy: string;
|
|
59
|
+
runId?: string;
|
|
60
|
+
startedEventId?: number;
|
|
61
|
+
};
|
|
62
|
+
type WftState = {
|
|
63
|
+
scheduledEventId: number;
|
|
64
|
+
attempt: number;
|
|
65
|
+
scheduledAt: number;
|
|
66
|
+
availableAt: number;
|
|
67
|
+
startedEventId?: number;
|
|
68
|
+
startedAt?: number;
|
|
69
|
+
timeoutAt?: number;
|
|
70
|
+
identity?: string;
|
|
71
|
+
};
|
|
72
|
+
/** An event that arrived while a workflow task was in flight, appended once it closes (Temporal's
|
|
73
|
+
* buffered events). `linkPrevious` names an attribute set to the id of the event appended before it
|
|
74
|
+
* (an activity's close names the ActivityTaskStarted written in the same step). */
|
|
75
|
+
type BufferedEvent = {
|
|
76
|
+
eventType: string;
|
|
77
|
+
key: string;
|
|
78
|
+
attributes: Obj;
|
|
79
|
+
linkPrevious?: string;
|
|
80
|
+
wakes: boolean;
|
|
81
|
+
};
|
|
82
|
+
type RunState = {
|
|
83
|
+
/** creation order among the World's runs: the latest run of a workflow id is its current one */
|
|
84
|
+
seq: number;
|
|
85
|
+
nextEventId: number;
|
|
86
|
+
history: string[];
|
|
87
|
+
historyBytes: number;
|
|
88
|
+
requestIds: string[];
|
|
89
|
+
/** the request id that started this run (StartWorkflowExecution / SignalWithStart), apart from signal and cancel ids */
|
|
90
|
+
startRequestIds?: string[];
|
|
91
|
+
workflowTaskTimeoutMs: number;
|
|
92
|
+
runTimeoutMs: number;
|
|
93
|
+
executionTimeoutMs: number;
|
|
94
|
+
runExpiresAt?: number;
|
|
95
|
+
executionExpiresAt?: number;
|
|
96
|
+
retryPolicy?: RetryPolicy;
|
|
97
|
+
attempt: number;
|
|
98
|
+
input?: Obj;
|
|
99
|
+
header?: Obj;
|
|
100
|
+
cronSchedule?: string;
|
|
101
|
+
firstWftAt?: number;
|
|
102
|
+
wft: WftState | null;
|
|
103
|
+
previousStartedEventId: number;
|
|
104
|
+
buffered: BufferedEvent[];
|
|
105
|
+
activities: Record<string, ActivityState>;
|
|
106
|
+
timers: Record<string, {
|
|
107
|
+
startedEventId: number;
|
|
108
|
+
fireAt: number;
|
|
109
|
+
}>;
|
|
110
|
+
children: Record<string, ChildState>;
|
|
111
|
+
parent?: {
|
|
112
|
+
namespace: string;
|
|
113
|
+
workflowId: string;
|
|
114
|
+
runId: string;
|
|
115
|
+
initiatedEventId: number;
|
|
116
|
+
};
|
|
117
|
+
cancelRequested: boolean;
|
|
118
|
+
closeEventId?: number;
|
|
119
|
+
newRunId?: string;
|
|
120
|
+
nextDue?: number;
|
|
121
|
+
};
|
|
122
|
+
/** A run as the engine handles it: WorkflowExecutionInfo fields at the top, `namespace`, `_state`. */
|
|
123
|
+
export type Run = Obj & {
|
|
124
|
+
id: string;
|
|
125
|
+
namespace: string;
|
|
126
|
+
execution: {
|
|
127
|
+
workflowId: string;
|
|
128
|
+
runId: string;
|
|
129
|
+
};
|
|
130
|
+
type: {
|
|
131
|
+
name: string;
|
|
132
|
+
};
|
|
133
|
+
status: string;
|
|
134
|
+
taskQueue: string;
|
|
135
|
+
firstRunId: string;
|
|
136
|
+
historyLength: number;
|
|
137
|
+
startTime: Timestamp;
|
|
138
|
+
executionTime?: Timestamp;
|
|
139
|
+
closeTime?: Timestamp;
|
|
140
|
+
searchAttributes?: {
|
|
141
|
+
indexedFields?: Record<string, Obj>;
|
|
142
|
+
};
|
|
143
|
+
memo?: {
|
|
144
|
+
fields?: Record<string, Obj>;
|
|
145
|
+
};
|
|
146
|
+
parentExecution?: {
|
|
147
|
+
workflowId: string;
|
|
148
|
+
runId: string;
|
|
149
|
+
};
|
|
150
|
+
rootExecution?: {
|
|
151
|
+
workflowId: string;
|
|
152
|
+
runId: string;
|
|
153
|
+
};
|
|
154
|
+
_state: RunState;
|
|
155
|
+
};
|
|
156
|
+
/** Temporal's predefined search attributes (https://docs.temporal.io/search-attribute#default-search-attribute). */
|
|
157
|
+
export declare const SYSTEM_SEARCH_ATTRIBUTES: Record<string, string>;
|
|
158
|
+
export type EngineOptions = {
|
|
159
|
+
root?: string;
|
|
160
|
+
readOnly?: boolean;
|
|
161
|
+
};
|
|
162
|
+
export declare class TemporalEngine {
|
|
163
|
+
readonly store: TemporalStore;
|
|
164
|
+
readonly readOnly: boolean;
|
|
165
|
+
/** holders from engineForRoot (see releaseEngine) */
|
|
166
|
+
users: number;
|
|
167
|
+
private lock;
|
|
168
|
+
private waiters;
|
|
169
|
+
private queries;
|
|
170
|
+
private querySeq;
|
|
171
|
+
/** runs minted in the current transition and not yet written (see newRun) */
|
|
172
|
+
private pendingRuns;
|
|
173
|
+
private ticker;
|
|
174
|
+
private ticking;
|
|
175
|
+
private closedSticky;
|
|
176
|
+
constructor(options?: EngineOptions);
|
|
177
|
+
/** Start the clock-driven work: timers, timeouts, retries and delayed tasks falling due. */
|
|
178
|
+
start(intervalMs?: number): void;
|
|
179
|
+
stop(): void;
|
|
180
|
+
private serial;
|
|
181
|
+
private notify;
|
|
182
|
+
/** Park until the next state change, the deadline, or the caller going away. */
|
|
183
|
+
private wait;
|
|
184
|
+
private assertWritable;
|
|
185
|
+
runs(namespace?: string): Run[];
|
|
186
|
+
run(runId: string): Run | undefined;
|
|
187
|
+
/** The run a request names: that run id, or the workflow id's current (latest) run. */
|
|
188
|
+
resolveRun(namespace: string, execution: {
|
|
189
|
+
workflowId?: string;
|
|
190
|
+
runId?: string;
|
|
191
|
+
} | undefined, firstExecutionRunId?: string): Run;
|
|
192
|
+
history(run: Run): Promise<HistoryEvent[]>;
|
|
193
|
+
private commit;
|
|
194
|
+
/** Every namespace, stored fields included (bookkeeping too). */
|
|
195
|
+
private namespaceRecords;
|
|
196
|
+
private namespaceRecord;
|
|
197
|
+
/** The namespaces as DescribeNamespace answers them (bookkeeping never reaches the wire). */
|
|
198
|
+
namespaces(): Obj[];
|
|
199
|
+
namespace(name: string): Obj;
|
|
200
|
+
private requireNamespace;
|
|
201
|
+
registerNamespace(request: Obj): Promise<Obj>;
|
|
202
|
+
customSearchAttributes(namespace: string): Record<string, string>;
|
|
203
|
+
/** Write a namespace's custom search attributes (the whole namespace subject, `default` included). */
|
|
204
|
+
private putSearchAttributes;
|
|
205
|
+
addSearchAttributes(request: Obj): Promise<Obj>;
|
|
206
|
+
removeSearchAttributes(request: Obj): Promise<Obj>;
|
|
207
|
+
listSearchAttributes(request: Obj): Obj;
|
|
208
|
+
/** INVALID_ARGUMENT for a search attribute the namespace does not define (Temporal refuses these). */
|
|
209
|
+
private validateSearchAttributes;
|
|
210
|
+
private workerError;
|
|
211
|
+
/** Create a run and its first events (inside a transition). */
|
|
212
|
+
private newRun;
|
|
213
|
+
private startSpec;
|
|
214
|
+
/** WorkflowIdConflictPolicy / WorkflowIdReusePolicy against the workflow id's latest run. Answers
|
|
215
|
+
* `existing` when the start resolves to a run that is already running (USE_EXISTING). */
|
|
216
|
+
private admitStart;
|
|
217
|
+
startWorkflowExecution(request: Obj): Promise<Obj>;
|
|
218
|
+
signalWithStartWorkflowExecution(request: Obj): Promise<Obj>;
|
|
219
|
+
private signalInto;
|
|
220
|
+
signalWorkflowExecution(request: Obj): Promise<Obj>;
|
|
221
|
+
requestCancelWorkflowExecution(request: Obj): Promise<Obj>;
|
|
222
|
+
private cancelRequestInto;
|
|
223
|
+
terminateWorkflowExecution(request: Obj): Promise<Obj>;
|
|
224
|
+
private terminateRun;
|
|
225
|
+
/** Close a run: retire its tasks, apply its children's parent-close policy, tell its parent. */
|
|
226
|
+
private closeRun;
|
|
227
|
+
private notifyParent;
|
|
228
|
+
pollWorkflowTaskQueue(request: Obj, signal: AbortSignal, deadline?: number): Promise<Obj>;
|
|
229
|
+
private takeWorkflowTask;
|
|
230
|
+
/** The run and in-flight task a workflow task token names, or NOT_FOUND. */
|
|
231
|
+
private workflowTaskOf;
|
|
232
|
+
respondWorkflowTaskCompleted(request: Obj): Promise<Obj>;
|
|
233
|
+
/** One command → its events (and, for cross-run effects, a step run after this run is written). */
|
|
234
|
+
private applyCommand;
|
|
235
|
+
/** The run a continue-as-new or a retry hands the workflow to (built now, written after this run). */
|
|
236
|
+
private nextRunOf;
|
|
237
|
+
private startChild;
|
|
238
|
+
private signalExternal;
|
|
239
|
+
private cancelExternal;
|
|
240
|
+
/** Fail the in-flight workflow task and schedule the next attempt. */
|
|
241
|
+
private failWorkflowTask;
|
|
242
|
+
respondWorkflowTaskFailed(request: Obj): Promise<Obj>;
|
|
243
|
+
queryWorkflow(request: Obj, signal: AbortSignal, deadline?: number): Promise<Obj>;
|
|
244
|
+
respondQueryTaskCompleted(request: Obj): Promise<Obj>;
|
|
245
|
+
pollActivityTaskQueue(request: Obj, signal: AbortSignal, deadline?: number): Promise<Obj>;
|
|
246
|
+
private takeActivityTask;
|
|
247
|
+
/** The run and started attempt an activity token (or an id triple) names, or NOT_FOUND. */
|
|
248
|
+
private activityOf;
|
|
249
|
+
private activityTarget;
|
|
250
|
+
/** Close an activity with its final event (Started is written now, as Temporal writes it at the close). */
|
|
251
|
+
private closeActivity;
|
|
252
|
+
respondActivityTaskCompleted(request: Obj, byId?: boolean): Promise<Obj>;
|
|
253
|
+
respondActivityTaskFailed(request: Obj, byId?: boolean): Promise<Obj>;
|
|
254
|
+
private retryActivity;
|
|
255
|
+
respondActivityTaskCanceled(request: Obj, byId?: boolean): Promise<Obj>;
|
|
256
|
+
recordActivityTaskHeartbeat(request: Obj, byId?: boolean): Promise<Obj>;
|
|
257
|
+
/** Fire what the World clock says is due: timers, timeouts, delayed first tasks, expirations. */
|
|
258
|
+
tick(): Promise<void>;
|
|
259
|
+
private fireDue;
|
|
260
|
+
getWorkflowExecutionHistory(request: Obj, signal: AbortSignal, deadline?: number): Promise<Obj>;
|
|
261
|
+
describeWorkflowExecution(request: Obj): Promise<Obj>;
|
|
262
|
+
private visibility;
|
|
263
|
+
listWorkflowExecutions(request: Obj): Obj;
|
|
264
|
+
countWorkflowExecutions(request: Obj): Obj;
|
|
265
|
+
shutdownWorker(request: Obj): Promise<Obj>;
|
|
266
|
+
}
|
|
267
|
+
/** The process's engine for a root (created on first use). Two engines on one root would each
|
|
268
|
+
* serialize only their own transitions and could lose one another's updates. */
|
|
269
|
+
export declare function engineForRoot(options?: EngineOptions): TemporalEngine;
|
|
270
|
+
/** A user of a root's engine is done with it; the last one stops it and drops it from the registry. */
|
|
271
|
+
export declare function releaseEngine(engine: TemporalEngine): void;
|
|
272
|
+
/** A run's WorkflowExecutionInfo: its stored fields without the server's bookkeeping. */
|
|
273
|
+
export declare function executionInfo(run: Run): Obj;
|
|
274
|
+
export {};
|