@nanobpm/nano-sdk 1.0.0 → 1.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/README.md +37 -6
- package/dist/index.cjs +168 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -10
- package/dist/index.d.ts +115 -10
- package/dist/index.js +165 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,77 @@
|
|
|
1
1
|
import { JobActionReceiptSymbol, createCamundaClient as createCamundaClient$1 } from '@camunda8/orchestration-cluster-api';
|
|
2
2
|
export * from '@camunda8/orchestration-cluster-api';
|
|
3
3
|
|
|
4
|
+
/** A job handed to a worker handler. */
|
|
5
|
+
interface EmbeddedJob {
|
|
6
|
+
jobKey: string;
|
|
7
|
+
type: string;
|
|
8
|
+
processInstanceKey: string;
|
|
9
|
+
elementId: string;
|
|
10
|
+
retries: number;
|
|
11
|
+
variables: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/** The engine bridge an embedded app supplies (implemented by the app template's
|
|
14
|
+
* Deno host around μ-nano.wasm). Engine-core stays clock-free; the host injects now(). */
|
|
15
|
+
interface EmbeddedHost {
|
|
16
|
+
deploy(xml: string): Promise<{
|
|
17
|
+
processIds: string[];
|
|
18
|
+
}>;
|
|
19
|
+
createInstance(input: {
|
|
20
|
+
processDefinitionId?: string;
|
|
21
|
+
variables?: Record<string, unknown>;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
processInstanceKey: string;
|
|
24
|
+
}>;
|
|
25
|
+
activateJobs(type: string, max: number, timeoutMs: number, worker: string): Promise<EmbeddedJob[]>;
|
|
26
|
+
completeJob(jobKey: string, variables?: Record<string, unknown>): Promise<void>;
|
|
27
|
+
failJob(jobKey: string, retries: number, errorMessage?: string): Promise<void>;
|
|
28
|
+
instanceCompleted(key: string): boolean;
|
|
29
|
+
instanceVariables(key: string): Record<string, unknown>;
|
|
30
|
+
/** Drive timers + dispatch at wall-clock now. Returns true if anything changed. */
|
|
31
|
+
tick(): void;
|
|
32
|
+
}
|
|
33
|
+
/** Pull-based dispatch over an in-process host. Structurally compatible with the
|
|
34
|
+
* FalconTransport surface used by NanoJobWorker + the client proxy. */
|
|
35
|
+
declare class EmbeddedTransport {
|
|
36
|
+
private host;
|
|
37
|
+
private pollMs;
|
|
38
|
+
private subs;
|
|
39
|
+
private timer;
|
|
40
|
+
private closed;
|
|
41
|
+
constructor(host: EmbeddedHost, pollMs?: number);
|
|
42
|
+
createInstance(input: {
|
|
43
|
+
processDefinitionId?: string;
|
|
44
|
+
variables?: Record<string, unknown>;
|
|
45
|
+
awaitCompletion?: boolean;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
status: number;
|
|
48
|
+
body: unknown;
|
|
49
|
+
completion?: {
|
|
50
|
+
processCompleted: boolean;
|
|
51
|
+
variables: unknown;
|
|
52
|
+
processInstanceKey: string;
|
|
53
|
+
};
|
|
54
|
+
}>;
|
|
55
|
+
subscribe(sub: {
|
|
56
|
+
jobType: string;
|
|
57
|
+
worker: string;
|
|
58
|
+
credits: number;
|
|
59
|
+
timeoutMs: number;
|
|
60
|
+
onJob: (j: any) => void;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
unsubscribe(jobType: string): void;
|
|
63
|
+
completeJob(jobKey: string, variables?: Record<string, unknown>): void;
|
|
64
|
+
failJob(jobKey: string, retries?: number, errorMessage?: string): void;
|
|
65
|
+
throwError(jobKey: string, _errorCode: string, errorMessage?: string): void;
|
|
66
|
+
grantCredits(jobType: string, n: number): void;
|
|
67
|
+
private pump;
|
|
68
|
+
close(): void;
|
|
69
|
+
}
|
|
70
|
+
|
|
4
71
|
interface NanoInfo {
|
|
5
72
|
engine: string;
|
|
6
73
|
version?: string;
|
|
7
|
-
|
|
74
|
+
falconPath: string;
|
|
8
75
|
}
|
|
9
76
|
/**
|
|
10
77
|
* Probes a REST address once (cached) and returns the Nano engine info if the
|
|
@@ -29,7 +96,19 @@ interface Subscription {
|
|
|
29
96
|
fetchVariables: string[] | null;
|
|
30
97
|
onJob: (job: JobFrame) => void;
|
|
31
98
|
}
|
|
32
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Thrown by {@link FalconTransport.createInstance} when the gateway does
|
|
101
|
+
* not acknowledge a create within `submitTimeoutMs`. On the Falcon protocol,
|
|
102
|
+
* admission backpressure is expressed by the server withholding submission
|
|
103
|
+
* credits (no `503`, no retry), so a create otherwise waits indefinitely for
|
|
104
|
+
* intake capacity. This turns that stall into a typed rejection — treat it as
|
|
105
|
+
* "the server is backpressured" and back off; do not tight-loop retry.
|
|
106
|
+
*/
|
|
107
|
+
declare class SubmissionTimeoutError extends Error {
|
|
108
|
+
readonly timeoutMs: number;
|
|
109
|
+
constructor(timeoutMs: number);
|
|
110
|
+
}
|
|
111
|
+
declare class FalconTransport {
|
|
33
112
|
private url;
|
|
34
113
|
private ws;
|
|
35
114
|
private open;
|
|
@@ -40,13 +119,29 @@ declare class CommandStreamTransport {
|
|
|
40
119
|
private heartbeat;
|
|
41
120
|
private connectPromise;
|
|
42
121
|
private closed;
|
|
43
|
-
|
|
122
|
+
private defaultSubmitTimeoutMs?;
|
|
123
|
+
/**
|
|
124
|
+
* Server-granted submission-credit window (seeded by `welcome`, topped up by
|
|
125
|
+
* `submissionCredits`). Mirrors the engine's intake metering so creates queue
|
|
126
|
+
* client-side under admission backpressure instead of flooding the gateway.
|
|
127
|
+
*/
|
|
128
|
+
private credits;
|
|
129
|
+
private creditWaiters;
|
|
130
|
+
constructor(restAddress: string, path: string, defaultSubmitTimeoutMs?: number);
|
|
44
131
|
private nextCorr;
|
|
45
132
|
private send;
|
|
46
133
|
connect(): Promise<void>;
|
|
47
134
|
private reconnect;
|
|
48
135
|
private handle;
|
|
49
136
|
private sendSubscribe;
|
|
137
|
+
/**
|
|
138
|
+
* Take one submission credit, waiting for the gateway to replenish when the
|
|
139
|
+
* window is exhausted (admission backpressure — no 503, no retry). When
|
|
140
|
+
* `timeoutMs` elapses first, rejects with {@link SubmissionTimeoutError} and
|
|
141
|
+
* removes the queued waiter so no credit slot leaks.
|
|
142
|
+
*/
|
|
143
|
+
private acquireCredit;
|
|
144
|
+
private releaseCreditWaiters;
|
|
50
145
|
/** Create a process instance over the stream. */
|
|
51
146
|
createInstance(input: {
|
|
52
147
|
processDefinitionId?: string;
|
|
@@ -55,6 +150,13 @@ declare class CommandStreamTransport {
|
|
|
55
150
|
awaitCompletion?: boolean;
|
|
56
151
|
fetchVariables?: string[];
|
|
57
152
|
requestTimeoutMs?: number;
|
|
153
|
+
/**
|
|
154
|
+
* Client-side bound (ms) on how long to wait for a submission credit before
|
|
155
|
+
* rejecting with {@link SubmissionTimeoutError}. Overrides the transport-wide
|
|
156
|
+
* default. Omit to wait indefinitely under backpressure. Never sent on the
|
|
157
|
+
* wire — the server is unaware of it.
|
|
158
|
+
*/
|
|
159
|
+
submitTimeoutMs?: number;
|
|
58
160
|
}): Promise<{
|
|
59
161
|
status: number;
|
|
60
162
|
body: unknown;
|
|
@@ -90,9 +192,9 @@ declare class NanoJobWorker {
|
|
|
90
192
|
private stopped;
|
|
91
193
|
readonly jobType: string;
|
|
92
194
|
readonly name: string;
|
|
93
|
-
constructor(transport:
|
|
195
|
+
constructor(transport: FalconTransport, cfg: NanoJobWorkerConfig);
|
|
94
196
|
/** Bind the transport after async Nano detection. */
|
|
95
|
-
bindTransport(transport:
|
|
197
|
+
bindTransport(transport: FalconTransport): void;
|
|
96
198
|
start(): Promise<void>;
|
|
97
199
|
private enrich;
|
|
98
200
|
private dispatch;
|
|
@@ -100,20 +202,23 @@ declare class NanoJobWorker {
|
|
|
100
202
|
close(): void;
|
|
101
203
|
}
|
|
102
204
|
|
|
103
|
-
/** auto: upgrade only on Nano.
|
|
104
|
-
type NanoTransport = "auto" | "
|
|
205
|
+
/** auto: upgrade only on Nano. falcon: force. rest: never upgrade. embedded: in-process μ-nano. */
|
|
206
|
+
type NanoTransport = "auto" | "falcon" | "rest" | "embedded";
|
|
105
207
|
type AnyOpts = Parameters<typeof createCamundaClient$1>[0] & {
|
|
106
208
|
config?: Record<string, unknown> & {
|
|
107
209
|
CAMUNDA_TRANSPORT?: NanoTransport;
|
|
108
210
|
CAMUNDA_REST_ADDRESS?: string;
|
|
211
|
+
CAMUNDA_NANO_SUBMIT_TIMEOUT_MS?: string | number;
|
|
109
212
|
};
|
|
213
|
+
/** Embedded (ADR 0005) in-process engine host; required when transport is "embedded". */
|
|
214
|
+
embeddedHost?: EmbeddedHost;
|
|
110
215
|
};
|
|
111
216
|
/**
|
|
112
217
|
* Drop-in replacement for the upstream createCamundaClient. Returns the upstream
|
|
113
218
|
* client wrapped in a Proxy that upgrades createProcessInstance + createJobWorker
|
|
114
|
-
* to the
|
|
115
|
-
* CAMUNDA_TRANSPORT config: "auto" | "
|
|
219
|
+
* to the Falcon protocol when connected to a Nano server (overridable via the
|
|
220
|
+
* CAMUNDA_TRANSPORT config: "auto" | "falcon" | "rest").
|
|
116
221
|
*/
|
|
117
222
|
declare function createCamundaClient(opts?: AnyOpts): ReturnType<typeof createCamundaClient$1>;
|
|
118
223
|
|
|
119
|
-
export {
|
|
224
|
+
export { type EmbeddedHost, type EmbeddedJob, EmbeddedTransport, FalconTransport, type NanoInfo, NanoJobWorker, type NanoTransport, SubmissionTimeoutError, createCamundaClient, createCamundaClient as default, detectNano };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,77 @@
|
|
|
1
1
|
import { JobActionReceiptSymbol, createCamundaClient as createCamundaClient$1 } from '@camunda8/orchestration-cluster-api';
|
|
2
2
|
export * from '@camunda8/orchestration-cluster-api';
|
|
3
3
|
|
|
4
|
+
/** A job handed to a worker handler. */
|
|
5
|
+
interface EmbeddedJob {
|
|
6
|
+
jobKey: string;
|
|
7
|
+
type: string;
|
|
8
|
+
processInstanceKey: string;
|
|
9
|
+
elementId: string;
|
|
10
|
+
retries: number;
|
|
11
|
+
variables: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/** The engine bridge an embedded app supplies (implemented by the app template's
|
|
14
|
+
* Deno host around μ-nano.wasm). Engine-core stays clock-free; the host injects now(). */
|
|
15
|
+
interface EmbeddedHost {
|
|
16
|
+
deploy(xml: string): Promise<{
|
|
17
|
+
processIds: string[];
|
|
18
|
+
}>;
|
|
19
|
+
createInstance(input: {
|
|
20
|
+
processDefinitionId?: string;
|
|
21
|
+
variables?: Record<string, unknown>;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
processInstanceKey: string;
|
|
24
|
+
}>;
|
|
25
|
+
activateJobs(type: string, max: number, timeoutMs: number, worker: string): Promise<EmbeddedJob[]>;
|
|
26
|
+
completeJob(jobKey: string, variables?: Record<string, unknown>): Promise<void>;
|
|
27
|
+
failJob(jobKey: string, retries: number, errorMessage?: string): Promise<void>;
|
|
28
|
+
instanceCompleted(key: string): boolean;
|
|
29
|
+
instanceVariables(key: string): Record<string, unknown>;
|
|
30
|
+
/** Drive timers + dispatch at wall-clock now. Returns true if anything changed. */
|
|
31
|
+
tick(): void;
|
|
32
|
+
}
|
|
33
|
+
/** Pull-based dispatch over an in-process host. Structurally compatible with the
|
|
34
|
+
* FalconTransport surface used by NanoJobWorker + the client proxy. */
|
|
35
|
+
declare class EmbeddedTransport {
|
|
36
|
+
private host;
|
|
37
|
+
private pollMs;
|
|
38
|
+
private subs;
|
|
39
|
+
private timer;
|
|
40
|
+
private closed;
|
|
41
|
+
constructor(host: EmbeddedHost, pollMs?: number);
|
|
42
|
+
createInstance(input: {
|
|
43
|
+
processDefinitionId?: string;
|
|
44
|
+
variables?: Record<string, unknown>;
|
|
45
|
+
awaitCompletion?: boolean;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
status: number;
|
|
48
|
+
body: unknown;
|
|
49
|
+
completion?: {
|
|
50
|
+
processCompleted: boolean;
|
|
51
|
+
variables: unknown;
|
|
52
|
+
processInstanceKey: string;
|
|
53
|
+
};
|
|
54
|
+
}>;
|
|
55
|
+
subscribe(sub: {
|
|
56
|
+
jobType: string;
|
|
57
|
+
worker: string;
|
|
58
|
+
credits: number;
|
|
59
|
+
timeoutMs: number;
|
|
60
|
+
onJob: (j: any) => void;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
unsubscribe(jobType: string): void;
|
|
63
|
+
completeJob(jobKey: string, variables?: Record<string, unknown>): void;
|
|
64
|
+
failJob(jobKey: string, retries?: number, errorMessage?: string): void;
|
|
65
|
+
throwError(jobKey: string, _errorCode: string, errorMessage?: string): void;
|
|
66
|
+
grantCredits(jobType: string, n: number): void;
|
|
67
|
+
private pump;
|
|
68
|
+
close(): void;
|
|
69
|
+
}
|
|
70
|
+
|
|
4
71
|
interface NanoInfo {
|
|
5
72
|
engine: string;
|
|
6
73
|
version?: string;
|
|
7
|
-
|
|
74
|
+
falconPath: string;
|
|
8
75
|
}
|
|
9
76
|
/**
|
|
10
77
|
* Probes a REST address once (cached) and returns the Nano engine info if the
|
|
@@ -29,7 +96,19 @@ interface Subscription {
|
|
|
29
96
|
fetchVariables: string[] | null;
|
|
30
97
|
onJob: (job: JobFrame) => void;
|
|
31
98
|
}
|
|
32
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Thrown by {@link FalconTransport.createInstance} when the gateway does
|
|
101
|
+
* not acknowledge a create within `submitTimeoutMs`. On the Falcon protocol,
|
|
102
|
+
* admission backpressure is expressed by the server withholding submission
|
|
103
|
+
* credits (no `503`, no retry), so a create otherwise waits indefinitely for
|
|
104
|
+
* intake capacity. This turns that stall into a typed rejection — treat it as
|
|
105
|
+
* "the server is backpressured" and back off; do not tight-loop retry.
|
|
106
|
+
*/
|
|
107
|
+
declare class SubmissionTimeoutError extends Error {
|
|
108
|
+
readonly timeoutMs: number;
|
|
109
|
+
constructor(timeoutMs: number);
|
|
110
|
+
}
|
|
111
|
+
declare class FalconTransport {
|
|
33
112
|
private url;
|
|
34
113
|
private ws;
|
|
35
114
|
private open;
|
|
@@ -40,13 +119,29 @@ declare class CommandStreamTransport {
|
|
|
40
119
|
private heartbeat;
|
|
41
120
|
private connectPromise;
|
|
42
121
|
private closed;
|
|
43
|
-
|
|
122
|
+
private defaultSubmitTimeoutMs?;
|
|
123
|
+
/**
|
|
124
|
+
* Server-granted submission-credit window (seeded by `welcome`, topped up by
|
|
125
|
+
* `submissionCredits`). Mirrors the engine's intake metering so creates queue
|
|
126
|
+
* client-side under admission backpressure instead of flooding the gateway.
|
|
127
|
+
*/
|
|
128
|
+
private credits;
|
|
129
|
+
private creditWaiters;
|
|
130
|
+
constructor(restAddress: string, path: string, defaultSubmitTimeoutMs?: number);
|
|
44
131
|
private nextCorr;
|
|
45
132
|
private send;
|
|
46
133
|
connect(): Promise<void>;
|
|
47
134
|
private reconnect;
|
|
48
135
|
private handle;
|
|
49
136
|
private sendSubscribe;
|
|
137
|
+
/**
|
|
138
|
+
* Take one submission credit, waiting for the gateway to replenish when the
|
|
139
|
+
* window is exhausted (admission backpressure — no 503, no retry). When
|
|
140
|
+
* `timeoutMs` elapses first, rejects with {@link SubmissionTimeoutError} and
|
|
141
|
+
* removes the queued waiter so no credit slot leaks.
|
|
142
|
+
*/
|
|
143
|
+
private acquireCredit;
|
|
144
|
+
private releaseCreditWaiters;
|
|
50
145
|
/** Create a process instance over the stream. */
|
|
51
146
|
createInstance(input: {
|
|
52
147
|
processDefinitionId?: string;
|
|
@@ -55,6 +150,13 @@ declare class CommandStreamTransport {
|
|
|
55
150
|
awaitCompletion?: boolean;
|
|
56
151
|
fetchVariables?: string[];
|
|
57
152
|
requestTimeoutMs?: number;
|
|
153
|
+
/**
|
|
154
|
+
* Client-side bound (ms) on how long to wait for a submission credit before
|
|
155
|
+
* rejecting with {@link SubmissionTimeoutError}. Overrides the transport-wide
|
|
156
|
+
* default. Omit to wait indefinitely under backpressure. Never sent on the
|
|
157
|
+
* wire — the server is unaware of it.
|
|
158
|
+
*/
|
|
159
|
+
submitTimeoutMs?: number;
|
|
58
160
|
}): Promise<{
|
|
59
161
|
status: number;
|
|
60
162
|
body: unknown;
|
|
@@ -90,9 +192,9 @@ declare class NanoJobWorker {
|
|
|
90
192
|
private stopped;
|
|
91
193
|
readonly jobType: string;
|
|
92
194
|
readonly name: string;
|
|
93
|
-
constructor(transport:
|
|
195
|
+
constructor(transport: FalconTransport, cfg: NanoJobWorkerConfig);
|
|
94
196
|
/** Bind the transport after async Nano detection. */
|
|
95
|
-
bindTransport(transport:
|
|
197
|
+
bindTransport(transport: FalconTransport): void;
|
|
96
198
|
start(): Promise<void>;
|
|
97
199
|
private enrich;
|
|
98
200
|
private dispatch;
|
|
@@ -100,20 +202,23 @@ declare class NanoJobWorker {
|
|
|
100
202
|
close(): void;
|
|
101
203
|
}
|
|
102
204
|
|
|
103
|
-
/** auto: upgrade only on Nano.
|
|
104
|
-
type NanoTransport = "auto" | "
|
|
205
|
+
/** auto: upgrade only on Nano. falcon: force. rest: never upgrade. embedded: in-process μ-nano. */
|
|
206
|
+
type NanoTransport = "auto" | "falcon" | "rest" | "embedded";
|
|
105
207
|
type AnyOpts = Parameters<typeof createCamundaClient$1>[0] & {
|
|
106
208
|
config?: Record<string, unknown> & {
|
|
107
209
|
CAMUNDA_TRANSPORT?: NanoTransport;
|
|
108
210
|
CAMUNDA_REST_ADDRESS?: string;
|
|
211
|
+
CAMUNDA_NANO_SUBMIT_TIMEOUT_MS?: string | number;
|
|
109
212
|
};
|
|
213
|
+
/** Embedded (ADR 0005) in-process engine host; required when transport is "embedded". */
|
|
214
|
+
embeddedHost?: EmbeddedHost;
|
|
110
215
|
};
|
|
111
216
|
/**
|
|
112
217
|
* Drop-in replacement for the upstream createCamundaClient. Returns the upstream
|
|
113
218
|
* client wrapped in a Proxy that upgrades createProcessInstance + createJobWorker
|
|
114
|
-
* to the
|
|
115
|
-
* CAMUNDA_TRANSPORT config: "auto" | "
|
|
219
|
+
* to the Falcon protocol when connected to a Nano server (overridable via the
|
|
220
|
+
* CAMUNDA_TRANSPORT config: "auto" | "falcon" | "rest").
|
|
116
221
|
*/
|
|
117
222
|
declare function createCamundaClient(opts?: AnyOpts): ReturnType<typeof createCamundaClient$1>;
|
|
118
223
|
|
|
119
|
-
export {
|
|
224
|
+
export { type EmbeddedHost, type EmbeddedJob, EmbeddedTransport, FalconTransport, type NanoInfo, NanoJobWorker, type NanoTransport, SubmissionTimeoutError, createCamundaClient, createCamundaClient as default, detectNano };
|
package/dist/index.js
CHANGED
|
@@ -19,11 +19,11 @@ async function detectNano(restAddress, fetchImpl = fetch) {
|
|
|
19
19
|
if (res.ok) {
|
|
20
20
|
const body = await res.json().catch(() => ({}));
|
|
21
21
|
const nano = body?.nano;
|
|
22
|
-
if (nano && typeof nano.
|
|
22
|
+
if (nano && typeof nano.falconPath === "string") {
|
|
23
23
|
info = {
|
|
24
24
|
engine: String(nano.engine ?? "nanobpmn"),
|
|
25
25
|
version: nano.version ? String(nano.version) : void 0,
|
|
26
|
-
|
|
26
|
+
falconPath: nano.falconPath
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -33,7 +33,7 @@ async function detectNano(restAddress, fetchImpl = fetch) {
|
|
|
33
33
|
cache.set(base, info);
|
|
34
34
|
return info;
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function falconUrl(restAddress, path) {
|
|
37
37
|
let base = normalizeBase(restAddress);
|
|
38
38
|
if (base.startsWith("https://")) base = "wss://" + base.slice("https://".length);
|
|
39
39
|
else if (base.startsWith("http://")) base = "ws://" + base.slice("http://".length);
|
|
@@ -55,7 +55,17 @@ async function getWebSocket() {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// src/transport.ts
|
|
58
|
-
var
|
|
58
|
+
var SubmissionTimeoutError = class extends Error {
|
|
59
|
+
constructor(timeoutMs) {
|
|
60
|
+
super(
|
|
61
|
+
`create submission stalled: no gateway ack within ${timeoutMs}ms (server is applying admission backpressure)`
|
|
62
|
+
);
|
|
63
|
+
this.timeoutMs = timeoutMs;
|
|
64
|
+
this.name = "SubmissionTimeoutError";
|
|
65
|
+
}
|
|
66
|
+
timeoutMs;
|
|
67
|
+
};
|
|
68
|
+
var FalconTransport = class {
|
|
59
69
|
url;
|
|
60
70
|
ws = null;
|
|
61
71
|
open = false;
|
|
@@ -66,8 +76,17 @@ var CommandStreamTransport = class {
|
|
|
66
76
|
heartbeat = null;
|
|
67
77
|
connectPromise = null;
|
|
68
78
|
closed = false;
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
defaultSubmitTimeoutMs;
|
|
80
|
+
/**
|
|
81
|
+
* Server-granted submission-credit window (seeded by `welcome`, topped up by
|
|
82
|
+
* `submissionCredits`). Mirrors the engine's intake metering so creates queue
|
|
83
|
+
* client-side under admission backpressure instead of flooding the gateway.
|
|
84
|
+
*/
|
|
85
|
+
credits = 0;
|
|
86
|
+
creditWaiters = [];
|
|
87
|
+
constructor(restAddress, path, defaultSubmitTimeoutMs) {
|
|
88
|
+
this.url = falconUrl(restAddress, path);
|
|
89
|
+
this.defaultSubmitTimeoutMs = defaultSubmitTimeoutMs !== void 0 && defaultSubmitTimeoutMs > 0 ? defaultSubmitTimeoutMs : void 0;
|
|
71
90
|
}
|
|
72
91
|
nextCorr() {
|
|
73
92
|
this.corr += 1;
|
|
@@ -95,12 +114,16 @@ var CommandStreamTransport = class {
|
|
|
95
114
|
this.handle(f, resolve);
|
|
96
115
|
};
|
|
97
116
|
ws.onerror = () => {
|
|
98
|
-
if (!this.open) reject(new Error(`
|
|
117
|
+
if (!this.open) reject(new Error(`falcon connect failed: ${this.url}`));
|
|
99
118
|
};
|
|
100
119
|
ws.onclose = () => {
|
|
101
120
|
this.open = false;
|
|
121
|
+
this.credits = 0;
|
|
122
|
+
const waiters = this.creditWaiters;
|
|
123
|
+
this.creditWaiters = [];
|
|
124
|
+
for (const w of waiters) w.fail(new Error("falcon closed"));
|
|
102
125
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
103
|
-
for (const p of this.pending.values()) p.reject(new Error("
|
|
126
|
+
for (const p of this.pending.values()) p.reject(new Error("falcon closed"));
|
|
104
127
|
this.pending.clear();
|
|
105
128
|
if (!this.closed) setTimeout(() => void this.reconnect(), 1e3);
|
|
106
129
|
};
|
|
@@ -118,12 +141,14 @@ var CommandStreamTransport = class {
|
|
|
118
141
|
switch (f.type) {
|
|
119
142
|
case "welcome": {
|
|
120
143
|
this.open = true;
|
|
144
|
+
this.credits = Number(f.submissionCredits ?? 0);
|
|
121
145
|
const hb = Number(f.heartbeatMs ?? 0);
|
|
122
146
|
if (hb > 0) {
|
|
123
147
|
if (this.heartbeat) clearInterval(this.heartbeat);
|
|
124
148
|
this.heartbeat = setInterval(() => this.send({ type: "heartbeat" }), hb);
|
|
125
149
|
}
|
|
126
150
|
for (const sub of this.subs.values()) this.sendSubscribe(sub);
|
|
151
|
+
this.releaseCreditWaiters();
|
|
127
152
|
resolveConnect();
|
|
128
153
|
break;
|
|
129
154
|
}
|
|
@@ -155,6 +180,11 @@ var CommandStreamTransport = class {
|
|
|
155
180
|
}
|
|
156
181
|
break;
|
|
157
182
|
}
|
|
183
|
+
case "submissionCredits": {
|
|
184
|
+
this.credits += Number(f.n ?? 0);
|
|
185
|
+
this.releaseCreditWaiters();
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
158
188
|
}
|
|
159
189
|
}
|
|
160
190
|
sendSubscribe(sub) {
|
|
@@ -167,9 +197,51 @@ var CommandStreamTransport = class {
|
|
|
167
197
|
fetchVariable: sub.fetchVariables
|
|
168
198
|
});
|
|
169
199
|
}
|
|
200
|
+
// ----- submission-credit gating ------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* Take one submission credit, waiting for the gateway to replenish when the
|
|
203
|
+
* window is exhausted (admission backpressure — no 503, no retry). When
|
|
204
|
+
* `timeoutMs` elapses first, rejects with {@link SubmissionTimeoutError} and
|
|
205
|
+
* removes the queued waiter so no credit slot leaks.
|
|
206
|
+
*/
|
|
207
|
+
acquireCredit(timeoutMs) {
|
|
208
|
+
if (this.credits > 0) {
|
|
209
|
+
this.credits -= 1;
|
|
210
|
+
return Promise.resolve();
|
|
211
|
+
}
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
let timer;
|
|
214
|
+
const waiter = {
|
|
215
|
+
grant: () => {
|
|
216
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
217
|
+
this.credits -= 1;
|
|
218
|
+
resolve();
|
|
219
|
+
},
|
|
220
|
+
fail: (e) => {
|
|
221
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
222
|
+
reject(e);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
this.creditWaiters.push(waiter);
|
|
226
|
+
if (timeoutMs !== void 0 && timeoutMs > 0) {
|
|
227
|
+
timer = setTimeout(() => {
|
|
228
|
+
const i = this.creditWaiters.indexOf(waiter);
|
|
229
|
+
if (i >= 0) this.creditWaiters.splice(i, 1);
|
|
230
|
+
reject(new SubmissionTimeoutError(timeoutMs));
|
|
231
|
+
}, timeoutMs);
|
|
232
|
+
timer.unref?.();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
releaseCreditWaiters() {
|
|
237
|
+
while (this.credits > 0 && this.creditWaiters.length > 0) {
|
|
238
|
+
this.creditWaiters.shift().grant();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
170
241
|
/** Create a process instance over the stream. */
|
|
171
242
|
async createInstance(input) {
|
|
172
243
|
await this.connect();
|
|
244
|
+
await this.acquireCredit(input.submitTimeoutMs ?? this.defaultSubmitTimeoutMs);
|
|
173
245
|
const corr = this.nextCorr();
|
|
174
246
|
let completionResolve = null;
|
|
175
247
|
const completion = input.awaitCompletion ? new Promise((r) => completionResolve = r) : null;
|
|
@@ -221,6 +293,68 @@ var CommandStreamTransport = class {
|
|
|
221
293
|
}
|
|
222
294
|
};
|
|
223
295
|
|
|
296
|
+
// src/embedded.ts
|
|
297
|
+
var EmbeddedTransport = class {
|
|
298
|
+
constructor(host, pollMs = 25) {
|
|
299
|
+
this.host = host;
|
|
300
|
+
this.pollMs = pollMs;
|
|
301
|
+
}
|
|
302
|
+
host;
|
|
303
|
+
pollMs;
|
|
304
|
+
subs = /* @__PURE__ */ new Map();
|
|
305
|
+
timer = null;
|
|
306
|
+
closed = false;
|
|
307
|
+
async createInstance(input) {
|
|
308
|
+
const { processInstanceKey } = await this.host.createInstance(input);
|
|
309
|
+
this.host.tick();
|
|
310
|
+
if (!input.awaitCompletion) return { status: 200, body: { processInstanceKey } };
|
|
311
|
+
while (!this.host.instanceCompleted(processInstanceKey)) {
|
|
312
|
+
this.host.tick();
|
|
313
|
+
await new Promise((r) => setTimeout(r, this.pollMs));
|
|
314
|
+
}
|
|
315
|
+
const variables = this.host.instanceVariables(processInstanceKey);
|
|
316
|
+
return { status: 200, body: { processInstanceKey, variables }, completion: { processCompleted: true, variables, processInstanceKey } };
|
|
317
|
+
}
|
|
318
|
+
async subscribe(sub) {
|
|
319
|
+
this.subs.set(sub.jobType, { worker: sub.worker, credits: sub.credits, timeoutMs: sub.timeoutMs, onJob: sub.onJob });
|
|
320
|
+
if (!this.timer) this.timer = setInterval(() => this.pump(), this.pollMs);
|
|
321
|
+
}
|
|
322
|
+
unsubscribe(jobType) {
|
|
323
|
+
this.subs.delete(jobType);
|
|
324
|
+
}
|
|
325
|
+
completeJob(jobKey, variables) {
|
|
326
|
+
void this.host.completeJob(jobKey, variables);
|
|
327
|
+
}
|
|
328
|
+
failJob(jobKey, retries, errorMessage) {
|
|
329
|
+
void this.host.failJob(jobKey, retries ?? 0, errorMessage);
|
|
330
|
+
}
|
|
331
|
+
throwError(jobKey, _errorCode, errorMessage) {
|
|
332
|
+
void this.host.failJob(jobKey, 0, errorMessage);
|
|
333
|
+
}
|
|
334
|
+
grantCredits(jobType, n) {
|
|
335
|
+
const s = this.subs.get(jobType);
|
|
336
|
+
if (s) s.credits += n;
|
|
337
|
+
}
|
|
338
|
+
pump() {
|
|
339
|
+
if (this.closed) return;
|
|
340
|
+
this.host.tick();
|
|
341
|
+
for (const [type, s] of this.subs) {
|
|
342
|
+
if (s.credits <= 0) continue;
|
|
343
|
+
void this.host.activateJobs(type, s.credits, s.timeoutMs, s.worker).then((jobs) => {
|
|
344
|
+
for (const j of jobs) {
|
|
345
|
+
if (s.credits <= 0) break;
|
|
346
|
+
s.credits--;
|
|
347
|
+
s.onJob(j);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
close() {
|
|
353
|
+
this.closed = true;
|
|
354
|
+
if (this.timer) clearInterval(this.timer);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
224
358
|
// src/nanoWorker.ts
|
|
225
359
|
import { JobActionReceiptSymbol as JobActionReceipt } from "@camunda8/orchestration-cluster-api";
|
|
226
360
|
var NanoJobWorker = class {
|
|
@@ -313,6 +447,11 @@ function resolveMode(opts) {
|
|
|
313
447
|
const fromEnv = typeof process !== "undefined" ? process.env?.CAMUNDA_TRANSPORT : void 0;
|
|
314
448
|
return fromOpts ?? fromEnv ?? "auto";
|
|
315
449
|
}
|
|
450
|
+
function resolveSubmitTimeoutMs(opts) {
|
|
451
|
+
const raw = opts?.config?.CAMUNDA_NANO_SUBMIT_TIMEOUT_MS ?? (typeof process !== "undefined" ? process.env?.CAMUNDA_NANO_SUBMIT_TIMEOUT_MS : void 0);
|
|
452
|
+
const n = typeof raw === "string" ? Number(raw) : raw;
|
|
453
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? n : void 0;
|
|
454
|
+
}
|
|
316
455
|
function baseFrom(restAddress) {
|
|
317
456
|
return normalizeBase(restAddress).replace(/\/v2$/, "");
|
|
318
457
|
}
|
|
@@ -320,31 +459,40 @@ function createCamundaClient(opts) {
|
|
|
320
459
|
const client = createCamundaClientBase(opts);
|
|
321
460
|
const mode = resolveMode(opts);
|
|
322
461
|
if (mode === "rest") return client;
|
|
462
|
+
if (mode === "embedded") {
|
|
463
|
+
if (!opts?.embeddedHost) throw new Error("transport 'embedded' requires opts.embeddedHost");
|
|
464
|
+
const transport2 = new EmbeddedTransport(opts.embeddedHost);
|
|
465
|
+
return wrapClient(client, async () => transport2, () => transport2.close());
|
|
466
|
+
}
|
|
323
467
|
const restAddress = client.getConfig().restAddress;
|
|
324
468
|
const base = baseFrom(restAddress);
|
|
469
|
+
const submitTimeoutMs = resolveSubmitTimeoutMs(opts);
|
|
325
470
|
let transport = null;
|
|
326
471
|
let nano;
|
|
327
472
|
const ensure = async () => {
|
|
328
473
|
if (nano === void 0) {
|
|
329
|
-
nano = mode === "
|
|
474
|
+
nano = mode === "falcon" ? { engine: "nanobpmn", falconPath: "/falcon" } : await detectNano(base);
|
|
330
475
|
}
|
|
331
476
|
if (!nano) return null;
|
|
332
|
-
if (!transport) transport = new
|
|
477
|
+
if (!transport) transport = new FalconTransport(base, nano.falconPath, submitTimeoutMs);
|
|
333
478
|
return transport;
|
|
334
479
|
};
|
|
480
|
+
return wrapClient(client, ensure, () => transport?.close());
|
|
481
|
+
}
|
|
482
|
+
function wrapClient(client, ensure, onStop) {
|
|
335
483
|
return new Proxy(client, {
|
|
336
484
|
get(target, prop, receiver) {
|
|
337
485
|
if (prop === "createProcessInstance") {
|
|
338
486
|
return (input, options) => {
|
|
339
|
-
|
|
340
|
-
return useStream.then(async (t) => {
|
|
487
|
+
return ensure().then(async (t) => {
|
|
341
488
|
if (!t) return target.createProcessInstance(input, options);
|
|
342
489
|
const r = await t.createInstance({
|
|
343
490
|
processDefinitionId: input?.processDefinitionId,
|
|
344
491
|
processDefinitionKey: input?.processDefinitionKey,
|
|
345
492
|
variables: input?.variables,
|
|
346
493
|
awaitCompletion: input?.awaitCompletion ?? false,
|
|
347
|
-
fetchVariables: input?.fetchVariables
|
|
494
|
+
fetchVariables: input?.fetchVariables,
|
|
495
|
+
submitTimeoutMs: input?.submitTimeoutMs
|
|
348
496
|
});
|
|
349
497
|
if (r.status >= 400) throw new Error(`createInstance failed: ${r.status} ${JSON.stringify(r.body)}`);
|
|
350
498
|
const body = r.body ?? {};
|
|
@@ -366,7 +514,7 @@ function createCamundaClient(opts) {
|
|
|
366
514
|
}
|
|
367
515
|
if (prop === "stopAllWorkers") {
|
|
368
516
|
return () => {
|
|
369
|
-
|
|
517
|
+
onStop?.();
|
|
370
518
|
return target.stopAllWorkers();
|
|
371
519
|
};
|
|
372
520
|
}
|
|
@@ -376,8 +524,10 @@ function createCamundaClient(opts) {
|
|
|
376
524
|
}
|
|
377
525
|
var index_default = createCamundaClient;
|
|
378
526
|
export {
|
|
379
|
-
|
|
527
|
+
EmbeddedTransport,
|
|
528
|
+
FalconTransport,
|
|
380
529
|
NanoJobWorker,
|
|
530
|
+
SubmissionTimeoutError,
|
|
381
531
|
createCamundaClient,
|
|
382
532
|
index_default as default,
|
|
383
533
|
detectNano
|