@bpmnkit/api 0.0.8
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 +150 -0
- package/dist/generated/admin-resources.d.ts +199 -0
- package/dist/generated/admin-resources.js +381 -0
- package/dist/generated/admin-types.d.ts +283 -0
- package/dist/generated/admin-types.js +4 -0
- package/dist/generated/resources.d.ts +1519 -0
- package/dist/generated/resources.js +2650 -0
- package/dist/generated/types.d.ts +11946 -0
- package/dist/generated/types.js +4 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/runtime/auth.d.ts +31 -0
- package/dist/runtime/auth.js +136 -0
- package/dist/runtime/cache.d.ts +13 -0
- package/dist/runtime/cache.js +48 -0
- package/dist/runtime/cache.test.d.ts +2 -0
- package/dist/runtime/cache.test.js +38 -0
- package/dist/runtime/client.d.ts +25 -0
- package/dist/runtime/client.js +33 -0
- package/dist/runtime/config.d.ts +15 -0
- package/dist/runtime/config.js +371 -0
- package/dist/runtime/errors.d.ts +53 -0
- package/dist/runtime/errors.js +101 -0
- package/dist/runtime/errors.test.d.ts +2 -0
- package/dist/runtime/errors.test.js +40 -0
- package/dist/runtime/events.d.ts +12 -0
- package/dist/runtime/events.js +48 -0
- package/dist/runtime/events.test.d.ts +2 -0
- package/dist/runtime/events.test.js +57 -0
- package/dist/runtime/http.d.ts +15 -0
- package/dist/runtime/http.js +210 -0
- package/dist/runtime/logger.d.ts +9 -0
- package/dist/runtime/logger.js +34 -0
- package/dist/runtime/relations.d.ts +43 -0
- package/dist/runtime/relations.js +54 -0
- package/dist/runtime/retry.d.ts +14 -0
- package/dist/runtime/retry.js +41 -0
- package/dist/runtime/retry.test.d.ts +2 -0
- package/dist/runtime/retry.test.js +46 -0
- package/dist/runtime/token-cache.d.ts +42 -0
- package/dist/runtime/token-cache.js +104 -0
- package/dist/runtime/types.d.ts +191 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/yaml.d.ts +14 -0
- package/dist/runtime/yaml.js +234 -0
- package/dist/runtime/yaml.test.d.ts +2 -0
- package/dist/runtime/yaml.test.js +93 -0
- package/package.json +31 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { CamundaError } from "./errors.js";
|
|
3
|
+
import { parseYaml } from "./yaml.js";
|
|
4
|
+
// ─── Environment variable names ───────────────────────────────────────────────
|
|
5
|
+
const ENV = {
|
|
6
|
+
BASE_URL: "CAMUNDA_BASE_URL",
|
|
7
|
+
CONFIG_FILE: "CAMUNDA_CONFIG_FILE",
|
|
8
|
+
TIMEOUT: "CAMUNDA_TIMEOUT",
|
|
9
|
+
AUTH_TYPE: "CAMUNDA_AUTH_TYPE",
|
|
10
|
+
AUTH_TOKEN: "CAMUNDA_AUTH_TOKEN",
|
|
11
|
+
AUTH_CLIENT_ID: "CAMUNDA_AUTH_CLIENT_ID",
|
|
12
|
+
AUTH_CLIENT_SECRET: "CAMUNDA_AUTH_CLIENT_SECRET",
|
|
13
|
+
AUTH_TOKEN_URL: "CAMUNDA_AUTH_TOKEN_URL",
|
|
14
|
+
AUTH_SCOPE: "CAMUNDA_AUTH_SCOPE",
|
|
15
|
+
AUTH_USERNAME: "CAMUNDA_AUTH_USERNAME",
|
|
16
|
+
AUTH_PASSWORD: "CAMUNDA_AUTH_PASSWORD",
|
|
17
|
+
TOKEN_CACHE_DISABLED: "CAMUNDA_TOKEN_CACHE_DISABLED",
|
|
18
|
+
TOKEN_CACHE_FILE: "CAMUNDA_TOKEN_CACHE_FILE",
|
|
19
|
+
RETRY_MAX_ATTEMPTS: "CAMUNDA_RETRY_MAX_ATTEMPTS",
|
|
20
|
+
RETRY_INITIAL_DELAY: "CAMUNDA_RETRY_INITIAL_DELAY",
|
|
21
|
+
RETRY_MAX_DELAY: "CAMUNDA_RETRY_MAX_DELAY",
|
|
22
|
+
RETRY_BACKOFF_FACTOR: "CAMUNDA_RETRY_BACKOFF_FACTOR",
|
|
23
|
+
RETRY_ON: "CAMUNDA_RETRY_ON",
|
|
24
|
+
CACHE_ENABLED: "CAMUNDA_CACHE_ENABLED",
|
|
25
|
+
CACHE_TTL: "CAMUNDA_CACHE_TTL",
|
|
26
|
+
CACHE_MAX_SIZE: "CAMUNDA_CACHE_MAX_SIZE",
|
|
27
|
+
LOG_LEVEL: "CAMUNDA_LOG_LEVEL",
|
|
28
|
+
};
|
|
29
|
+
// ─── Env loader ───────────────────────────────────────────────────────────────
|
|
30
|
+
function loadFromEnv() {
|
|
31
|
+
const e = process.env;
|
|
32
|
+
const result = {};
|
|
33
|
+
if (e[ENV.BASE_URL])
|
|
34
|
+
result.baseUrl = e[ENV.BASE_URL];
|
|
35
|
+
if (e[ENV.CONFIG_FILE])
|
|
36
|
+
result.configFile = e[ENV.CONFIG_FILE];
|
|
37
|
+
if (e[ENV.TIMEOUT])
|
|
38
|
+
result.timeout = Number.parseInt(e[ENV.TIMEOUT] ?? "", 10);
|
|
39
|
+
const auth = authFromEnv(e);
|
|
40
|
+
if (auth)
|
|
41
|
+
result.auth = auth;
|
|
42
|
+
const retry = retryFromEnv(e);
|
|
43
|
+
if (retry)
|
|
44
|
+
result.retry = retry;
|
|
45
|
+
const cache = cacheFromEnv(e);
|
|
46
|
+
if (cache)
|
|
47
|
+
result.cache = cache;
|
|
48
|
+
const logger = loggerFromEnv(e);
|
|
49
|
+
if (logger)
|
|
50
|
+
result.logger = logger;
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
function authFromEnv(e) {
|
|
54
|
+
const type = e[ENV.AUTH_TYPE];
|
|
55
|
+
if (!type)
|
|
56
|
+
return undefined;
|
|
57
|
+
switch (type) {
|
|
58
|
+
case "bearer": {
|
|
59
|
+
const token = e[ENV.AUTH_TOKEN];
|
|
60
|
+
if (!token)
|
|
61
|
+
return undefined;
|
|
62
|
+
return { type: "bearer", token };
|
|
63
|
+
}
|
|
64
|
+
case "oauth2": {
|
|
65
|
+
const clientId = e[ENV.AUTH_CLIENT_ID];
|
|
66
|
+
const clientSecret = e[ENV.AUTH_CLIENT_SECRET];
|
|
67
|
+
const tokenUrl = e[ENV.AUTH_TOKEN_URL];
|
|
68
|
+
if (!clientId || !clientSecret || !tokenUrl)
|
|
69
|
+
return undefined;
|
|
70
|
+
const tokenCache = tokenCacheFromEnv(e);
|
|
71
|
+
return {
|
|
72
|
+
type: "oauth2",
|
|
73
|
+
clientId,
|
|
74
|
+
clientSecret,
|
|
75
|
+
tokenUrl,
|
|
76
|
+
...(e[ENV.AUTH_SCOPE] ? { scope: e[ENV.AUTH_SCOPE] } : {}),
|
|
77
|
+
...(tokenCache ? { tokenCache } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
case "basic": {
|
|
81
|
+
const username = e[ENV.AUTH_USERNAME];
|
|
82
|
+
const password = e[ENV.AUTH_PASSWORD];
|
|
83
|
+
if (!username || !password)
|
|
84
|
+
return undefined;
|
|
85
|
+
return { type: "basic", username, password };
|
|
86
|
+
}
|
|
87
|
+
case "none":
|
|
88
|
+
return { type: "none" };
|
|
89
|
+
default:
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function tokenCacheFromEnv(e) {
|
|
94
|
+
const disabled = e[ENV.TOKEN_CACHE_DISABLED];
|
|
95
|
+
const filePath = e[ENV.TOKEN_CACHE_FILE];
|
|
96
|
+
if (!disabled && !filePath)
|
|
97
|
+
return undefined;
|
|
98
|
+
return {
|
|
99
|
+
...(disabled !== undefined ? { disabled: disabled === "true" || disabled === "1" } : {}),
|
|
100
|
+
...(filePath ? { filePath } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function retryFromEnv(e) {
|
|
104
|
+
const cfg = {};
|
|
105
|
+
if (e[ENV.RETRY_MAX_ATTEMPTS])
|
|
106
|
+
cfg.maxAttempts = Number.parseInt(e[ENV.RETRY_MAX_ATTEMPTS] ?? "", 10);
|
|
107
|
+
if (e[ENV.RETRY_INITIAL_DELAY])
|
|
108
|
+
cfg.initialDelay = Number.parseInt(e[ENV.RETRY_INITIAL_DELAY] ?? "", 10);
|
|
109
|
+
if (e[ENV.RETRY_MAX_DELAY])
|
|
110
|
+
cfg.maxDelay = Number.parseInt(e[ENV.RETRY_MAX_DELAY] ?? "", 10);
|
|
111
|
+
if (e[ENV.RETRY_BACKOFF_FACTOR])
|
|
112
|
+
cfg.backoffFactor = Number.parseFloat(e[ENV.RETRY_BACKOFF_FACTOR] ?? "");
|
|
113
|
+
if (e[ENV.RETRY_ON]) {
|
|
114
|
+
cfg.retryOn = (e[ENV.RETRY_ON] ?? "")
|
|
115
|
+
.split(",")
|
|
116
|
+
.map((s) => Number.parseInt(s.trim(), 10))
|
|
117
|
+
.filter((n) => !Number.isNaN(n));
|
|
118
|
+
}
|
|
119
|
+
return Object.keys(cfg).length > 0 ? cfg : undefined;
|
|
120
|
+
}
|
|
121
|
+
function cacheFromEnv(e) {
|
|
122
|
+
const cfg = {};
|
|
123
|
+
if (e[ENV.CACHE_ENABLED] !== undefined) {
|
|
124
|
+
cfg.enabled = e[ENV.CACHE_ENABLED] === "true" || e[ENV.CACHE_ENABLED] === "1";
|
|
125
|
+
}
|
|
126
|
+
if (e[ENV.CACHE_TTL])
|
|
127
|
+
cfg.ttl = Number.parseInt(e[ENV.CACHE_TTL] ?? "", 10);
|
|
128
|
+
if (e[ENV.CACHE_MAX_SIZE])
|
|
129
|
+
cfg.maxSize = Number.parseInt(e[ENV.CACHE_MAX_SIZE] ?? "", 10);
|
|
130
|
+
return Object.keys(cfg).length > 0 ? cfg : undefined;
|
|
131
|
+
}
|
|
132
|
+
function loggerFromEnv(e) {
|
|
133
|
+
const level = e[ENV.LOG_LEVEL];
|
|
134
|
+
if (!level)
|
|
135
|
+
return undefined;
|
|
136
|
+
return { level };
|
|
137
|
+
}
|
|
138
|
+
// ─── File loader ──────────────────────────────────────────────────────────────
|
|
139
|
+
function loadFromFile(filePath) {
|
|
140
|
+
let raw;
|
|
141
|
+
try {
|
|
142
|
+
raw = readFileSync(filePath, "utf8");
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
throw new CamundaError(`Cannot read config file "${filePath}": ${err}`);
|
|
146
|
+
}
|
|
147
|
+
let doc;
|
|
148
|
+
try {
|
|
149
|
+
doc = parseYaml(raw);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
throw new CamundaError(`Cannot parse config file "${filePath}": ${err}`);
|
|
153
|
+
}
|
|
154
|
+
return coerceFileConfig(doc, filePath);
|
|
155
|
+
}
|
|
156
|
+
function coerceFileConfig(doc, filePath) {
|
|
157
|
+
const result = {};
|
|
158
|
+
if (typeof doc.baseUrl === "string")
|
|
159
|
+
result.baseUrl = doc.baseUrl;
|
|
160
|
+
if (typeof doc.configFile === "string")
|
|
161
|
+
result.configFile = doc.configFile;
|
|
162
|
+
if (typeof doc.timeout === "number")
|
|
163
|
+
result.timeout = doc.timeout;
|
|
164
|
+
const auth = coerceAuth(doc.auth, filePath);
|
|
165
|
+
if (auth)
|
|
166
|
+
result.auth = auth;
|
|
167
|
+
const retry = coerceRetry(doc.retry);
|
|
168
|
+
if (retry)
|
|
169
|
+
result.retry = retry;
|
|
170
|
+
const cache = coerceCache(doc.cache);
|
|
171
|
+
if (cache)
|
|
172
|
+
result.cache = cache;
|
|
173
|
+
const logger = coerceLogger(doc.logger);
|
|
174
|
+
if (logger)
|
|
175
|
+
result.logger = logger;
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function coerceAuth(raw, filePath) {
|
|
179
|
+
if (typeof raw !== "object" || raw === null)
|
|
180
|
+
return undefined;
|
|
181
|
+
const a = raw;
|
|
182
|
+
const type = a.type;
|
|
183
|
+
switch (type) {
|
|
184
|
+
case "bearer":
|
|
185
|
+
if (typeof a.token !== "string")
|
|
186
|
+
return undefined;
|
|
187
|
+
return { type: "bearer", token: a.token };
|
|
188
|
+
case "oauth2": {
|
|
189
|
+
if (typeof a.clientId !== "string" ||
|
|
190
|
+
typeof a.clientSecret !== "string" ||
|
|
191
|
+
typeof a.tokenUrl !== "string") {
|
|
192
|
+
throw new CamundaError(`Config file "${filePath}": oauth2 auth requires clientId, clientSecret, and tokenUrl`);
|
|
193
|
+
}
|
|
194
|
+
const tokenCache = coerceTokenCache(a.tokenCache);
|
|
195
|
+
return {
|
|
196
|
+
type: "oauth2",
|
|
197
|
+
clientId: a.clientId,
|
|
198
|
+
clientSecret: a.clientSecret,
|
|
199
|
+
tokenUrl: a.tokenUrl,
|
|
200
|
+
...(typeof a.scope === "string" ? { scope: a.scope } : {}),
|
|
201
|
+
...(tokenCache ? { tokenCache } : {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
case "basic":
|
|
205
|
+
if (typeof a.username !== "string" || typeof a.password !== "string")
|
|
206
|
+
return undefined;
|
|
207
|
+
return { type: "basic", username: a.username, password: a.password };
|
|
208
|
+
case "none":
|
|
209
|
+
return { type: "none" };
|
|
210
|
+
default:
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function coerceTokenCache(raw) {
|
|
215
|
+
if (typeof raw !== "object" || raw === null)
|
|
216
|
+
return undefined;
|
|
217
|
+
const t = raw;
|
|
218
|
+
const cfg = {};
|
|
219
|
+
if (typeof t.disabled === "boolean")
|
|
220
|
+
cfg.disabled = t.disabled;
|
|
221
|
+
if (typeof t.filePath === "string")
|
|
222
|
+
cfg.filePath = t.filePath;
|
|
223
|
+
return Object.keys(cfg).length > 0 ? cfg : undefined;
|
|
224
|
+
}
|
|
225
|
+
function coerceRetry(raw) {
|
|
226
|
+
if (typeof raw !== "object" || raw === null)
|
|
227
|
+
return undefined;
|
|
228
|
+
const r = raw;
|
|
229
|
+
const cfg = {};
|
|
230
|
+
if (typeof r.maxAttempts === "number")
|
|
231
|
+
cfg.maxAttempts = r.maxAttempts;
|
|
232
|
+
if (typeof r.initialDelay === "number")
|
|
233
|
+
cfg.initialDelay = r.initialDelay;
|
|
234
|
+
if (typeof r.maxDelay === "number")
|
|
235
|
+
cfg.maxDelay = r.maxDelay;
|
|
236
|
+
if (typeof r.backoffFactor === "number")
|
|
237
|
+
cfg.backoffFactor = r.backoffFactor;
|
|
238
|
+
if (Array.isArray(r.retryOn)) {
|
|
239
|
+
cfg.retryOn = r.retryOn.filter((n) => typeof n === "number");
|
|
240
|
+
}
|
|
241
|
+
return Object.keys(cfg).length > 0 ? cfg : undefined;
|
|
242
|
+
}
|
|
243
|
+
function coerceCache(raw) {
|
|
244
|
+
if (typeof raw !== "object" || raw === null)
|
|
245
|
+
return undefined;
|
|
246
|
+
const c = raw;
|
|
247
|
+
const cfg = {};
|
|
248
|
+
if (typeof c.enabled === "boolean")
|
|
249
|
+
cfg.enabled = c.enabled;
|
|
250
|
+
if (typeof c.ttl === "number")
|
|
251
|
+
cfg.ttl = c.ttl;
|
|
252
|
+
if (typeof c.maxSize === "number")
|
|
253
|
+
cfg.maxSize = c.maxSize;
|
|
254
|
+
return Object.keys(cfg).length > 0 ? cfg : undefined;
|
|
255
|
+
}
|
|
256
|
+
function coerceLogger(raw) {
|
|
257
|
+
if (typeof raw !== "object" || raw === null)
|
|
258
|
+
return undefined;
|
|
259
|
+
const l = raw;
|
|
260
|
+
if (typeof l.level === "string")
|
|
261
|
+
return { level: l.level };
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
// ─── Merge ────────────────────────────────────────────────────────────────────
|
|
265
|
+
/**
|
|
266
|
+
* Merge auth configs from multiple sources (lowest → highest priority).
|
|
267
|
+
* When the `type` matches across sources, fields are merged so you can
|
|
268
|
+
* split non-sensitive config (type, tokenUrl) from secrets (clientSecret).
|
|
269
|
+
*/
|
|
270
|
+
function mergeAuth(...sources) {
|
|
271
|
+
// Determine winning auth type from highest-priority source that declares one
|
|
272
|
+
let winningType;
|
|
273
|
+
for (const s of sources) {
|
|
274
|
+
if (s?.type)
|
|
275
|
+
winningType = s.type;
|
|
276
|
+
}
|
|
277
|
+
if (!winningType)
|
|
278
|
+
return undefined;
|
|
279
|
+
// Collect all fields from sources that share the winning type
|
|
280
|
+
const merged = { type: winningType };
|
|
281
|
+
for (const s of sources) {
|
|
282
|
+
if (!s)
|
|
283
|
+
continue;
|
|
284
|
+
if (s.type !== winningType)
|
|
285
|
+
continue; // different type — skip entirely
|
|
286
|
+
Object.assign(merged, s);
|
|
287
|
+
}
|
|
288
|
+
return merged;
|
|
289
|
+
}
|
|
290
|
+
function mergeRetry(...sources) {
|
|
291
|
+
const merged = {};
|
|
292
|
+
for (const s of sources) {
|
|
293
|
+
if (!s)
|
|
294
|
+
continue;
|
|
295
|
+
if (s.maxAttempts !== undefined)
|
|
296
|
+
merged.maxAttempts = s.maxAttempts;
|
|
297
|
+
if (s.initialDelay !== undefined)
|
|
298
|
+
merged.initialDelay = s.initialDelay;
|
|
299
|
+
if (s.maxDelay !== undefined)
|
|
300
|
+
merged.maxDelay = s.maxDelay;
|
|
301
|
+
if (s.backoffFactor !== undefined)
|
|
302
|
+
merged.backoffFactor = s.backoffFactor;
|
|
303
|
+
if (s.retryOn !== undefined)
|
|
304
|
+
merged.retryOn = s.retryOn;
|
|
305
|
+
}
|
|
306
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
307
|
+
}
|
|
308
|
+
function mergeCache(...sources) {
|
|
309
|
+
const merged = {};
|
|
310
|
+
for (const s of sources) {
|
|
311
|
+
if (!s)
|
|
312
|
+
continue;
|
|
313
|
+
if (s.enabled !== undefined)
|
|
314
|
+
merged.enabled = s.enabled;
|
|
315
|
+
if (s.ttl !== undefined)
|
|
316
|
+
merged.ttl = s.ttl;
|
|
317
|
+
if (s.maxSize !== undefined)
|
|
318
|
+
merged.maxSize = s.maxSize;
|
|
319
|
+
}
|
|
320
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
321
|
+
}
|
|
322
|
+
function mergeLogger(...sources) {
|
|
323
|
+
let result;
|
|
324
|
+
for (const s of sources) {
|
|
325
|
+
if (!s)
|
|
326
|
+
continue;
|
|
327
|
+
result = { ...result, ...s };
|
|
328
|
+
}
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
332
|
+
/**
|
|
333
|
+
* Resolve the final `CamundaClientConfig` from three layers (lowest → highest):
|
|
334
|
+
* 1. Environment variables (CAMUNDA_*)
|
|
335
|
+
* 2. YAML config file (path from `input.configFile` or `CAMUNDA_CONFIG_FILE`)
|
|
336
|
+
* 3. Values passed directly to the constructor (`input`)
|
|
337
|
+
*
|
|
338
|
+
* Throws `CamundaError` if `baseUrl` or `auth` cannot be resolved.
|
|
339
|
+
*/
|
|
340
|
+
export function resolveConfig(input) {
|
|
341
|
+
const fromEnv = loadFromEnv();
|
|
342
|
+
// Config file path: constructor > env
|
|
343
|
+
const configFilePath = input.configFile ?? fromEnv.configFile;
|
|
344
|
+
const fromFile = configFilePath ? loadFromFile(configFilePath) : {};
|
|
345
|
+
// Merge: env (lowest) < file < explicit (highest)
|
|
346
|
+
const baseUrl = input.baseUrl ?? fromFile.baseUrl ?? fromEnv.baseUrl;
|
|
347
|
+
const auth = mergeAuth(fromEnv.auth, fromFile.auth, input.auth);
|
|
348
|
+
const timeout = input.timeout ?? fromFile.timeout ?? fromEnv.timeout;
|
|
349
|
+
const configFile = configFilePath;
|
|
350
|
+
const retry = mergeRetry(fromEnv.retry, fromFile.retry, input.retry);
|
|
351
|
+
const cache = mergeCache(fromEnv.cache, fromFile.cache, input.cache);
|
|
352
|
+
const logger = mergeLogger(fromEnv.logger, fromFile.logger, input.logger);
|
|
353
|
+
if (!baseUrl) {
|
|
354
|
+
throw new CamundaError("baseUrl is required. Set it in the constructor, a config file, or CAMUNDA_BASE_URL.");
|
|
355
|
+
}
|
|
356
|
+
if (!auth) {
|
|
357
|
+
throw new CamundaError("auth is required. Set it in the constructor, a config file, or CAMUNDA_AUTH_* env vars.");
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
baseUrl,
|
|
361
|
+
auth,
|
|
362
|
+
...(configFile ? { configFile } : {}),
|
|
363
|
+
...(timeout !== undefined ? { timeout } : {}),
|
|
364
|
+
...(retry ? { retry } : {}),
|
|
365
|
+
...(cache ? { cache } : {}),
|
|
366
|
+
...(logger ? { logger } : {}),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/** Exported for testing. */
|
|
370
|
+
export { loadFromEnv, loadFromFile };
|
|
371
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Base class for all errors thrown by the Camunda API client. */
|
|
2
|
+
export declare class CamundaError extends Error {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
}
|
|
5
|
+
/** An HTTP-level error (non-2xx response from the API). */
|
|
6
|
+
export declare class CamundaHttpError extends CamundaError {
|
|
7
|
+
readonly status: number;
|
|
8
|
+
readonly body: unknown;
|
|
9
|
+
readonly url: string;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
constructor(message: string, status: number, body: unknown, url: string, options?: ErrorOptions);
|
|
12
|
+
}
|
|
13
|
+
/** 400 Bad Request — invalid input. */
|
|
14
|
+
export declare class CamundaValidationError extends CamundaHttpError {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
}
|
|
17
|
+
/** 401 Unauthorized — missing or invalid credentials. */
|
|
18
|
+
export declare class CamundaAuthError extends CamundaHttpError {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
}
|
|
21
|
+
/** 403 Forbidden — authenticated but not allowed. */
|
|
22
|
+
export declare class CamundaForbiddenError extends CamundaHttpError {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
}
|
|
25
|
+
/** 404 Not Found. */
|
|
26
|
+
export declare class CamundaNotFoundError extends CamundaHttpError {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
}
|
|
29
|
+
/** 409 Conflict — resource is in the wrong state. */
|
|
30
|
+
export declare class CamundaConflictError extends CamundaHttpError {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
}
|
|
33
|
+
/** 429 Too Many Requests — rate limited. */
|
|
34
|
+
export declare class CamundaRateLimitError extends CamundaHttpError {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
/** Seconds until the client may retry, if provided by the server. */
|
|
37
|
+
readonly retryAfter?: number;
|
|
38
|
+
constructor(message: string, status: number, body: unknown, url: string, retryAfter?: number, options?: ErrorOptions);
|
|
39
|
+
}
|
|
40
|
+
/** 5xx Server Error. */
|
|
41
|
+
export declare class CamundaServerError extends CamundaHttpError {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
}
|
|
44
|
+
/** Network-level failure (no response received). */
|
|
45
|
+
export declare class CamundaNetworkError extends CamundaError {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
}
|
|
48
|
+
/** Request timed out before a response was received. */
|
|
49
|
+
export declare class CamundaTimeoutError extends CamundaNetworkError {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function buildHttpError(status: number, body: unknown, url: string): CamundaHttpError;
|
|
53
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Base class for all errors thrown by the Camunda API client. */
|
|
2
|
+
export class CamundaError extends Error {
|
|
3
|
+
name = "CamundaError";
|
|
4
|
+
}
|
|
5
|
+
/** An HTTP-level error (non-2xx response from the API). */
|
|
6
|
+
export class CamundaHttpError extends CamundaError {
|
|
7
|
+
status;
|
|
8
|
+
body;
|
|
9
|
+
url;
|
|
10
|
+
name = "CamundaHttpError";
|
|
11
|
+
constructor(message, status, body, url, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.body = body;
|
|
15
|
+
this.url = url;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** 400 Bad Request — invalid input. */
|
|
19
|
+
export class CamundaValidationError extends CamundaHttpError {
|
|
20
|
+
name = "CamundaValidationError";
|
|
21
|
+
}
|
|
22
|
+
/** 401 Unauthorized — missing or invalid credentials. */
|
|
23
|
+
export class CamundaAuthError extends CamundaHttpError {
|
|
24
|
+
name = "CamundaAuthError";
|
|
25
|
+
}
|
|
26
|
+
/** 403 Forbidden — authenticated but not allowed. */
|
|
27
|
+
export class CamundaForbiddenError extends CamundaHttpError {
|
|
28
|
+
name = "CamundaForbiddenError";
|
|
29
|
+
}
|
|
30
|
+
/** 404 Not Found. */
|
|
31
|
+
export class CamundaNotFoundError extends CamundaHttpError {
|
|
32
|
+
name = "CamundaNotFoundError";
|
|
33
|
+
}
|
|
34
|
+
/** 409 Conflict — resource is in the wrong state. */
|
|
35
|
+
export class CamundaConflictError extends CamundaHttpError {
|
|
36
|
+
name = "CamundaConflictError";
|
|
37
|
+
}
|
|
38
|
+
/** 429 Too Many Requests — rate limited. */
|
|
39
|
+
export class CamundaRateLimitError extends CamundaHttpError {
|
|
40
|
+
name = "CamundaRateLimitError";
|
|
41
|
+
/** Seconds until the client may retry, if provided by the server. */
|
|
42
|
+
retryAfter;
|
|
43
|
+
constructor(message, status, body, url, retryAfter, options) {
|
|
44
|
+
super(message, status, body, url, options);
|
|
45
|
+
this.retryAfter = retryAfter;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** 5xx Server Error. */
|
|
49
|
+
export class CamundaServerError extends CamundaHttpError {
|
|
50
|
+
name = "CamundaServerError";
|
|
51
|
+
}
|
|
52
|
+
/** Network-level failure (no response received). */
|
|
53
|
+
export class CamundaNetworkError extends CamundaError {
|
|
54
|
+
name = "CamundaNetworkError";
|
|
55
|
+
}
|
|
56
|
+
/** Request timed out before a response was received. */
|
|
57
|
+
export class CamundaTimeoutError extends CamundaNetworkError {
|
|
58
|
+
name = "CamundaTimeoutError";
|
|
59
|
+
}
|
|
60
|
+
export function buildHttpError(status, body, url) {
|
|
61
|
+
const message = extractMessage(body, status);
|
|
62
|
+
switch (status) {
|
|
63
|
+
case 400:
|
|
64
|
+
return new CamundaValidationError(message, status, body, url);
|
|
65
|
+
case 401:
|
|
66
|
+
return new CamundaAuthError(message, status, body, url);
|
|
67
|
+
case 403:
|
|
68
|
+
return new CamundaForbiddenError(message, status, body, url);
|
|
69
|
+
case 404:
|
|
70
|
+
return new CamundaNotFoundError(message, status, body, url);
|
|
71
|
+
case 409:
|
|
72
|
+
return new CamundaConflictError(message, status, body, url);
|
|
73
|
+
case 429: {
|
|
74
|
+
const retryAfter = typeof body === "object" &&
|
|
75
|
+
body !== null &&
|
|
76
|
+
"retryAfter" in body &&
|
|
77
|
+
typeof body.retryAfter === "number"
|
|
78
|
+
? body.retryAfter
|
|
79
|
+
: undefined;
|
|
80
|
+
return new CamundaRateLimitError(message, status, body, url, retryAfter);
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
if (status >= 500) {
|
|
84
|
+
return new CamundaServerError(message, status, body, url);
|
|
85
|
+
}
|
|
86
|
+
return new CamundaHttpError(message, status, body, url);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function extractMessage(body, status) {
|
|
90
|
+
if (typeof body === "object" && body !== null) {
|
|
91
|
+
const b = body;
|
|
92
|
+
if (typeof b.message === "string")
|
|
93
|
+
return b.message;
|
|
94
|
+
if (typeof b.detail === "string")
|
|
95
|
+
return b.detail;
|
|
96
|
+
if (typeof b.title === "string")
|
|
97
|
+
return b.title;
|
|
98
|
+
}
|
|
99
|
+
return `HTTP ${status}`;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { CamundaAuthError, CamundaConflictError, CamundaForbiddenError, CamundaNotFoundError, CamundaRateLimitError, CamundaServerError, CamundaValidationError, buildHttpError, } from "./errors.js";
|
|
3
|
+
describe("buildHttpError", () => {
|
|
4
|
+
it("returns CamundaValidationError for 400", () => {
|
|
5
|
+
const err = buildHttpError(400, { message: "bad input" }, "http://test");
|
|
6
|
+
expect(err).toBeInstanceOf(CamundaValidationError);
|
|
7
|
+
expect(err.status).toBe(400);
|
|
8
|
+
expect(err.message).toBe("bad input");
|
|
9
|
+
});
|
|
10
|
+
it("returns CamundaAuthError for 401", () => {
|
|
11
|
+
expect(buildHttpError(401, null, "http://test")).toBeInstanceOf(CamundaAuthError);
|
|
12
|
+
});
|
|
13
|
+
it("returns CamundaForbiddenError for 403", () => {
|
|
14
|
+
expect(buildHttpError(403, null, "http://test")).toBeInstanceOf(CamundaForbiddenError);
|
|
15
|
+
});
|
|
16
|
+
it("returns CamundaNotFoundError for 404", () => {
|
|
17
|
+
expect(buildHttpError(404, null, "http://test")).toBeInstanceOf(CamundaNotFoundError);
|
|
18
|
+
});
|
|
19
|
+
it("returns CamundaConflictError for 409", () => {
|
|
20
|
+
expect(buildHttpError(409, null, "http://test")).toBeInstanceOf(CamundaConflictError);
|
|
21
|
+
});
|
|
22
|
+
it("returns CamundaRateLimitError for 429 with retryAfter", () => {
|
|
23
|
+
const err = buildHttpError(429, { retryAfter: 30 }, "http://test");
|
|
24
|
+
expect(err).toBeInstanceOf(CamundaRateLimitError);
|
|
25
|
+
expect(err.retryAfter).toBe(30);
|
|
26
|
+
});
|
|
27
|
+
it("returns CamundaServerError for 500+", () => {
|
|
28
|
+
expect(buildHttpError(500, null, "http://test")).toBeInstanceOf(CamundaServerError);
|
|
29
|
+
expect(buildHttpError(503, null, "http://test")).toBeInstanceOf(CamundaServerError);
|
|
30
|
+
});
|
|
31
|
+
it("extracts message from response body detail field", () => {
|
|
32
|
+
const err = buildHttpError(400, { detail: "invalid value" }, "http://test");
|
|
33
|
+
expect(err.message).toBe("invalid value");
|
|
34
|
+
});
|
|
35
|
+
it("falls back to HTTP status as message", () => {
|
|
36
|
+
const err = buildHttpError(422, {}, "http://test");
|
|
37
|
+
expect(err.message).toBe("HTTP 422");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=errors.test.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal typed event emitter — no Node.js dependency, works in any runtime.
|
|
3
|
+
*/
|
|
4
|
+
export declare class TypedEventEmitter<TMap extends Record<string, unknown>> {
|
|
5
|
+
#private;
|
|
6
|
+
on<K extends keyof TMap>(event: K, listener: (data: TMap[K]) => void): this;
|
|
7
|
+
off<K extends keyof TMap>(event: K, listener: (data: TMap[K]) => void): this;
|
|
8
|
+
once<K extends keyof TMap>(event: K, listener: (data: TMap[K]) => void): this;
|
|
9
|
+
emit<K extends keyof TMap>(event: K, data: TMap[K]): void;
|
|
10
|
+
removeAllListeners<K extends keyof TMap>(event?: K): void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal typed event emitter — no Node.js dependency, works in any runtime.
|
|
3
|
+
*/
|
|
4
|
+
export class TypedEventEmitter {
|
|
5
|
+
#listeners = new Map();
|
|
6
|
+
on(event, listener) {
|
|
7
|
+
let set = this.#listeners.get(event);
|
|
8
|
+
if (!set) {
|
|
9
|
+
set = new Set();
|
|
10
|
+
this.#listeners.set(event, set);
|
|
11
|
+
}
|
|
12
|
+
set.add(listener);
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
off(event, listener) {
|
|
16
|
+
this.#listeners.get(event)?.delete(listener);
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
once(event, listener) {
|
|
20
|
+
const wrapped = (data) => {
|
|
21
|
+
this.off(event, wrapped);
|
|
22
|
+
listener(data);
|
|
23
|
+
};
|
|
24
|
+
return this.on(event, wrapped);
|
|
25
|
+
}
|
|
26
|
+
emit(event, data) {
|
|
27
|
+
const set = this.#listeners.get(event);
|
|
28
|
+
if (!set)
|
|
29
|
+
return;
|
|
30
|
+
for (const listener of set) {
|
|
31
|
+
try {
|
|
32
|
+
listener(data);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Listeners must not crash the client.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
removeAllListeners(event) {
|
|
40
|
+
if (event !== undefined) {
|
|
41
|
+
this.#listeners.delete(event);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.#listeners.clear();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=events.js.map
|