js-bao-wss-client 2.0.4 → 2.0.5
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 +242 -39
- package/dist/JsBaoClient.d.ts +199 -16
- package/dist/JsBaoClient.d.ts.map +1 -1
- package/dist/JsBaoClient.js +240 -4
- package/dist/JsBaoClient.js.map +1 -1
- package/dist/api/blobBucketsApi.d.ts +29 -1
- package/dist/api/blobBucketsApi.d.ts.map +1 -1
- package/dist/api/blobBucketsApi.js +5 -3
- package/dist/api/blobBucketsApi.js.map +1 -1
- package/dist/api/collectionsApi.d.ts +9 -1
- package/dist/api/collectionsApi.d.ts.map +1 -1
- package/dist/api/collectionsApi.js.map +1 -1
- package/dist/api/databasesApi.d.ts +18 -1
- package/dist/api/databasesApi.d.ts.map +1 -1
- package/dist/api/databasesApi.js +8 -0
- package/dist/api/databasesApi.js.map +1 -1
- package/dist/api/documentsApi.d.ts +15 -5
- package/dist/api/documentsApi.d.ts.map +1 -1
- package/dist/api/documentsApi.js +19 -20
- package/dist/api/documentsApi.js.map +1 -1
- package/dist/api/geminiApi.d.ts.map +1 -1
- package/dist/api/geminiApi.js +11 -14
- package/dist/api/geminiApi.js.map +1 -1
- package/dist/api/groupsApi.d.ts +20 -4
- package/dist/api/groupsApi.d.ts.map +1 -1
- package/dist/api/groupsApi.js +9 -1
- package/dist/api/groupsApi.js.map +1 -1
- package/dist/api/notificationsApi.d.ts +134 -0
- package/dist/api/notificationsApi.d.ts.map +1 -0
- package/dist/api/notificationsApi.js +52 -0
- package/dist/api/notificationsApi.js.map +1 -0
- package/dist/api/resourceMetadataApi.d.ts +163 -0
- package/dist/api/resourceMetadataApi.d.ts.map +1 -0
- package/dist/api/resourceMetadataApi.js +56 -0
- package/dist/api/resourceMetadataApi.js.map +1 -0
- package/dist/browser.umd.js +514 -58
- package/dist/browser.umd.js.map +1 -1
- package/dist/errors.d.ts +34 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +56 -0
- package/dist/errors.js.map +1 -1
- package/dist/internal/authController.d.ts +10 -0
- package/dist/internal/authController.d.ts.map +1 -1
- package/dist/internal/authController.js +14 -2
- package/dist/internal/authController.js.map +1 -1
- package/dist/internal/httpClient.d.ts +8 -0
- package/dist/internal/httpClient.d.ts.map +1 -1
- package/dist/internal/httpClient.js +34 -5
- package/dist/internal/httpClient.js.map +1 -1
- package/package.json +6 -2
package/dist/JsBaoClient.js
CHANGED
|
@@ -15,7 +15,7 @@ import { HttpClient, } from "./internal/httpClient";
|
|
|
15
15
|
import { DocumentManager, } from "./internal/documentManager";
|
|
16
16
|
import { ensureArrayBuffer, toBase64, fromBase64, decompressGzip, } from "./utils/binary";
|
|
17
17
|
import { hashUint8Array } from "./utils/crypto";
|
|
18
|
-
import { JsBaoError } from "./errors";
|
|
18
|
+
import { JsBaoError, JsBaoApiError, JsBaoNetworkError } from "./errors";
|
|
19
19
|
import { DocumentsAPI, } from "./api/documentsApi";
|
|
20
20
|
import { createCacheFacade } from "./api/cacheFacade";
|
|
21
21
|
import { MeAPI } from "./api/meApi";
|
|
@@ -26,6 +26,7 @@ import { UsersAPI } from "./api/usersApi";
|
|
|
26
26
|
import { DatabasesAPI } from "./api/databasesApi";
|
|
27
27
|
import { GroupsAPI } from "./api/groupsApi";
|
|
28
28
|
import { CronTriggersAPI } from "./api/cronTriggersApi";
|
|
29
|
+
import { ResourceMetadataAPI } from "./api/resourceMetadataApi";
|
|
29
30
|
import { RuleSetsAPI } from "./api/ruleSetsApi";
|
|
30
31
|
import { BlobBucketsAPI } from "./api/blobBucketsApi";
|
|
31
32
|
import { GroupTypeConfigsAPI } from "./api/groupTypeConfigsApi";
|
|
@@ -33,7 +34,8 @@ import { CollectionTypeConfigsAPI } from "./api/collectionTypeConfigsApi";
|
|
|
33
34
|
import { DatabaseTypeConfigsAPI } from "./api/databaseTypeConfigsApi";
|
|
34
35
|
import { CollectionsAPI } from "./api/collectionsApi";
|
|
35
36
|
import { InvitationsAPI } from "./api/invitationsApi";
|
|
36
|
-
|
|
37
|
+
import { NotificationsAPI } from "./api/notificationsApi";
|
|
38
|
+
export { JsBaoError, isJsBaoError, JsBaoApiError, isJsBaoApiError, JsBaoNetworkError, isJsBaoNetworkError, } from "./errors";
|
|
37
39
|
export { AuthError, AUTH_CODES } from "./internal/authController";
|
|
38
40
|
export { ANALYTICS_UNAUTHENTICATED_USER } from "./internal/analyticsQueue";
|
|
39
41
|
const DEFAULT_RETURN_ACTIVE_MIN_MS = 5 * 60 * 1000;
|
|
@@ -481,6 +483,7 @@ export class JsBaoClient extends Observable {
|
|
|
481
483
|
this.auth = new AuthController({
|
|
482
484
|
appId: this.appId,
|
|
483
485
|
apiUrl: this.apiUrl,
|
|
486
|
+
globalAdminAppId: this.globalAdminAppId,
|
|
484
487
|
logger,
|
|
485
488
|
offlineStore: this.offlineStore,
|
|
486
489
|
request: (method, path, data) => this.makeRequest(method, path, data),
|
|
@@ -590,14 +593,20 @@ export class JsBaoClient extends Observable {
|
|
|
590
593
|
this.collectionTypeConfigs = new CollectionTypeConfigsAPI(this);
|
|
591
594
|
this.databaseTypeConfigs = new DatabaseTypeConfigsAPI(this);
|
|
592
595
|
this.cronTriggers = new CronTriggersAPI(this);
|
|
596
|
+
this.resourceMetadata = new ResourceMetadataAPI(this);
|
|
593
597
|
this.invitations = new InvitationsAPI(this);
|
|
598
|
+
this.notifications = new NotificationsAPI(this);
|
|
594
599
|
this.integrations = {
|
|
595
600
|
call: async (request) => this.callIntegration(request),
|
|
596
601
|
};
|
|
597
602
|
this.workflows = {
|
|
603
|
+
// The public overloads type `input` generically (Omit base input + I);
|
|
604
|
+
// the private impls read `input` as an opaque payload, so widen back to
|
|
605
|
+
// the concrete option types here. Runtime just forwards `input`.
|
|
598
606
|
start: (options) => this.startWorkflow(options),
|
|
599
607
|
runSync: (options) => this.runSyncWorkflow(options),
|
|
600
608
|
getStatus: (options) => this.getWorkflowStatus(options),
|
|
609
|
+
waitFor: (runId, options) => this.waitForWorkflow(runId, options),
|
|
601
610
|
terminate: (options) => this.terminateWorkflow(options),
|
|
602
611
|
listRuns: (options) => this.listWorkflowRuns(options),
|
|
603
612
|
listStepRuns: (options) => this.listWorkflowStepRuns(options),
|
|
@@ -2078,10 +2087,32 @@ export class JsBaoClient extends Observable {
|
|
|
2078
2087
|
Authorization: `Bearer ${this.token || ""}`,
|
|
2079
2088
|
"X-Global-Admin-App-Id": this.getGlobalAdminAppId(),
|
|
2080
2089
|
};
|
|
2081
|
-
|
|
2090
|
+
let res;
|
|
2091
|
+
try {
|
|
2092
|
+
res = await fetch(url.toString(), { method, headers });
|
|
2093
|
+
}
|
|
2094
|
+
catch (err) {
|
|
2095
|
+
throw new JsBaoNetworkError(`Network request to ${url.toString()} failed`, err);
|
|
2096
|
+
}
|
|
2082
2097
|
if (!res.ok) {
|
|
2083
2098
|
const text = await res.text().catch(() => "");
|
|
2084
|
-
|
|
2099
|
+
let body = text === "" ? null : text;
|
|
2100
|
+
let code;
|
|
2101
|
+
if (text) {
|
|
2102
|
+
try {
|
|
2103
|
+
const parsed = JSON.parse(text);
|
|
2104
|
+
body = parsed;
|
|
2105
|
+
if (parsed &&
|
|
2106
|
+
typeof parsed === "object" &&
|
|
2107
|
+
typeof parsed.code === "string") {
|
|
2108
|
+
code = parsed.code;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
catch {
|
|
2112
|
+
// non-JSON body — keep the raw text
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
throw new JsBaoApiError(res.status, text, body, code);
|
|
2085
2116
|
}
|
|
2086
2117
|
return res.arrayBuffer();
|
|
2087
2118
|
}
|
|
@@ -2285,6 +2316,15 @@ export class JsBaoClient extends Observable {
|
|
|
2285
2316
|
path += `?contextDocId=${encodeURIComponent(options.contextDocId)}`;
|
|
2286
2317
|
}
|
|
2287
2318
|
const response = await this.makeRequest("GET", path);
|
|
2319
|
+
return this.normalizeWorkflowStatusResponse(response);
|
|
2320
|
+
}
|
|
2321
|
+
/**
|
|
2322
|
+
* Normalize a raw `{ status: rawStatus, run }` status response into a
|
|
2323
|
+
* {@link WorkflowStatusResult}. Shared by `getStatus` (workflowKey/runKey
|
|
2324
|
+
* route) and `waitFor`'s reconcile fetch (runId route) so both surfaces map
|
|
2325
|
+
* the Cloudflare status spellings and apply-flow states identically.
|
|
2326
|
+
*/
|
|
2327
|
+
normalizeWorkflowStatusResponse(response) {
|
|
2288
2328
|
// The API returns { status: rawStatus, run } where rawStatus is the Cloudflare workflow status object
|
|
2289
2329
|
// and run is the WorkflowRun DB record (which tracks apply-flow states).
|
|
2290
2330
|
const rawStatus = response?.status;
|
|
@@ -2317,6 +2357,191 @@ export class JsBaoClient extends Observable {
|
|
|
2317
2357
|
run,
|
|
2318
2358
|
};
|
|
2319
2359
|
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Fetch normalized run status keyed by `runId` via the #1443 endpoint
|
|
2362
|
+
* (`GET /workflows/runs/:runId/status`). Used by `waitFor`'s reconcile
|
|
2363
|
+
* fetch. A 404 surfaces as a `NOT_FOUND` {@link JsBaoError} (unknown or
|
|
2364
|
+
* not-owned run).
|
|
2365
|
+
*/
|
|
2366
|
+
async getWorkflowStatusByRunId(runId) {
|
|
2367
|
+
const path = `/workflows/runs/${encodeURIComponent(runId)}/status`;
|
|
2368
|
+
const response = await this.makeRequest("GET", path);
|
|
2369
|
+
return this.normalizeWorkflowStatusResponse(response);
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* Implementation of {@link WorkflowsAPI.waitFor} (#1443). See the interface
|
|
2373
|
+
* doc for the contract. Cleanup mirrors `createUserIdWaiter` in
|
|
2374
|
+
* authController — a guarded `cleanup()` that clears the timer and removes
|
|
2375
|
+
* every listener, run on every exit path.
|
|
2376
|
+
*/
|
|
2377
|
+
async waitForWorkflow(runId, options) {
|
|
2378
|
+
if (!runId || typeof runId !== "string") {
|
|
2379
|
+
throw new JsBaoError("INVALID_ARGUMENT", "runId is required for workflows.waitFor");
|
|
2380
|
+
}
|
|
2381
|
+
const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
|
|
2382
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2383
|
+
return new Promise((resolve, reject) => {
|
|
2384
|
+
let cleaned = false;
|
|
2385
|
+
let timer = null;
|
|
2386
|
+
// Guards overlapping reconcile fetches (initial + reconnect) so a slow
|
|
2387
|
+
// fetch can't stack up behind a burst of reconnect events.
|
|
2388
|
+
let reconciling = false;
|
|
2389
|
+
const cleanup = () => {
|
|
2390
|
+
if (cleaned)
|
|
2391
|
+
return;
|
|
2392
|
+
cleaned = true;
|
|
2393
|
+
try {
|
|
2394
|
+
if (timer) {
|
|
2395
|
+
clearTimeout(timer);
|
|
2396
|
+
timer = null;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
catch { }
|
|
2400
|
+
try {
|
|
2401
|
+
this.off("workflowStatus", onWorkflowStatus);
|
|
2402
|
+
}
|
|
2403
|
+
catch { }
|
|
2404
|
+
try {
|
|
2405
|
+
this.off("status", onConnectionStatus);
|
|
2406
|
+
}
|
|
2407
|
+
catch { }
|
|
2408
|
+
};
|
|
2409
|
+
const settle = (result) => {
|
|
2410
|
+
cleanup();
|
|
2411
|
+
resolve(result);
|
|
2412
|
+
};
|
|
2413
|
+
const fail = (err) => {
|
|
2414
|
+
cleanup();
|
|
2415
|
+
reject(err);
|
|
2416
|
+
};
|
|
2417
|
+
// Map a reconcile response into a terminal result, or null if the run is
|
|
2418
|
+
// not yet terminal. A run is terminal when its status is a terminal
|
|
2419
|
+
// value, OR its `endedAt` is set regardless of status (#1388 robustness).
|
|
2420
|
+
const terminalFromReconcile = (res) => {
|
|
2421
|
+
const run = res.run;
|
|
2422
|
+
const isTerminalStatus = res.status === "completed" ||
|
|
2423
|
+
res.status === "failed" ||
|
|
2424
|
+
res.status === "terminated" ||
|
|
2425
|
+
res.status === "apply_pending" ||
|
|
2426
|
+
res.status === "apply_claimed";
|
|
2427
|
+
const endedAtSet = !!(run && run.endedAt);
|
|
2428
|
+
if (!isTerminalStatus && !endedAtSet)
|
|
2429
|
+
return null;
|
|
2430
|
+
return {
|
|
2431
|
+
status: this.toTerminalWaitStatus(res.status, run),
|
|
2432
|
+
output: res.output,
|
|
2433
|
+
error: res.error,
|
|
2434
|
+
};
|
|
2435
|
+
};
|
|
2436
|
+
const onWorkflowStatus = (event) => {
|
|
2437
|
+
if (!event || event.runId !== runId)
|
|
2438
|
+
return;
|
|
2439
|
+
// The workflowStatus frame is only emitted on a terminal transition
|
|
2440
|
+
// (completed/failed/terminated), and carries the terminal payload
|
|
2441
|
+
// directly — resolve from the event with ZERO extra HTTP.
|
|
2442
|
+
//
|
|
2443
|
+
// Apply-required workflows are the one wrinkle: the server broadcasts
|
|
2444
|
+
// status:"completed" with needsApply:true while the run row is actually
|
|
2445
|
+
// `apply_pending`. Map that frame to the apply-ready terminal status so
|
|
2446
|
+
// the event path agrees with the reconcile path (behavior 11) and
|
|
2447
|
+
// callers don't miss that a client-side apply step is still required.
|
|
2448
|
+
const status = event.needsApply
|
|
2449
|
+
? "apply_pending"
|
|
2450
|
+
: event.status;
|
|
2451
|
+
settle({
|
|
2452
|
+
status,
|
|
2453
|
+
output: event.output,
|
|
2454
|
+
error: event.error,
|
|
2455
|
+
});
|
|
2456
|
+
};
|
|
2457
|
+
// Recognise a "run not found" (HTTP 404) from either error shape the
|
|
2458
|
+
// status endpoint can throw: a structured JsBaoError NOT_FOUND, a plain
|
|
2459
|
+
// Error whose message starts with "HTTP 404" (the real transport), or an
|
|
2460
|
+
// error object carrying a numeric `status` of 404.
|
|
2461
|
+
const isNotFound = (err) => {
|
|
2462
|
+
if (err instanceof JsBaoError && err.code === "NOT_FOUND")
|
|
2463
|
+
return true;
|
|
2464
|
+
if (err && typeof err === "object" && err.status === 404) {
|
|
2465
|
+
return true;
|
|
2466
|
+
}
|
|
2467
|
+
const msg = String((err && err.message) || err || "");
|
|
2468
|
+
return msg.startsWith("HTTP 404");
|
|
2469
|
+
};
|
|
2470
|
+
const reconcile = () => {
|
|
2471
|
+
if (cleaned || reconciling)
|
|
2472
|
+
return;
|
|
2473
|
+
reconciling = true;
|
|
2474
|
+
this.getWorkflowStatusByRunId(runId)
|
|
2475
|
+
.then((res) => {
|
|
2476
|
+
if (cleaned)
|
|
2477
|
+
return;
|
|
2478
|
+
const terminal = terminalFromReconcile(res);
|
|
2479
|
+
if (terminal)
|
|
2480
|
+
settle(terminal);
|
|
2481
|
+
})
|
|
2482
|
+
.catch((err) => {
|
|
2483
|
+
if (cleaned)
|
|
2484
|
+
return;
|
|
2485
|
+
// A 404 means the run is unknown or not readable by this caller —
|
|
2486
|
+
// reject immediately rather than waiting for the timeout. The
|
|
2487
|
+
// status endpoint surfaces a 404 in two shapes: a JsBaoError with
|
|
2488
|
+
// code NOT_FOUND (from higher-level paths / the transport spy), or
|
|
2489
|
+
// a plain Error("HTTP 404: ...") thrown by the real
|
|
2490
|
+
// HttpClient.request transport. Recognise both so the real 404
|
|
2491
|
+
// rejects promptly instead of falling through as "transient".
|
|
2492
|
+
if (isNotFound(err)) {
|
|
2493
|
+
fail(err instanceof JsBaoError
|
|
2494
|
+
? err
|
|
2495
|
+
: new JsBaoError("NOT_FOUND", `Workflow run ${runId} not found`));
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
// Other (transient / network) errors: swallow and rely on the
|
|
2499
|
+
// workflowStatus event, the next reconnect reconcile, or timeout.
|
|
2500
|
+
})
|
|
2501
|
+
.finally(() => {
|
|
2502
|
+
reconciling = false;
|
|
2503
|
+
});
|
|
2504
|
+
};
|
|
2505
|
+
const onConnectionStatus = (evt) => {
|
|
2506
|
+
// Fork 1 = B: on WS reconnect, re-run the single reconcile fetch to
|
|
2507
|
+
// recover a terminal frame missed during the outage. No interval timer.
|
|
2508
|
+
if (evt?.status === "connected") {
|
|
2509
|
+
reconcile();
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
this.on("workflowStatus", onWorkflowStatus);
|
|
2513
|
+
this.on("status", onConnectionStatus);
|
|
2514
|
+
// 0 / Infinity disables the timeout (documented warning): the listener
|
|
2515
|
+
// lives until the run terminates.
|
|
2516
|
+
if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {
|
|
2517
|
+
timer = setTimeout(() => {
|
|
2518
|
+
fail(new JsBaoError("WORKFLOW_WAIT_TIMEOUT", `workflows.waitFor timed out after ${timeoutMs}ms waiting for run ${runId}`));
|
|
2519
|
+
}, timeoutMs);
|
|
2520
|
+
}
|
|
2521
|
+
// Always reconcile once at start (Fork 4 = A) to close the
|
|
2522
|
+
// started-before-subscribed race — the terminal frame may have fired
|
|
2523
|
+
// before this listener was attached and is never replayed.
|
|
2524
|
+
reconcile();
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Collapse a normalized {@link WorkflowStatusValue} into the narrower
|
|
2529
|
+
* {@link WaitForWorkflowStatus} for `waitFor`. Terminal statuses pass
|
|
2530
|
+
* through unchanged. A `"running"` status only reaches here when `endedAt`
|
|
2531
|
+
* is set (#1388): derive the real terminal state from the run row's error
|
|
2532
|
+
* (failed if an error message is present, otherwise completed).
|
|
2533
|
+
*/
|
|
2534
|
+
toTerminalWaitStatus(status, run) {
|
|
2535
|
+
if (status === "completed" ||
|
|
2536
|
+
status === "failed" ||
|
|
2537
|
+
status === "terminated" ||
|
|
2538
|
+
status === "apply_pending" ||
|
|
2539
|
+
status === "apply_claimed") {
|
|
2540
|
+
return status;
|
|
2541
|
+
}
|
|
2542
|
+
// status === "running" but endedAt is set (#1388 stale status).
|
|
2543
|
+
return run && run.errorMessage ? "failed" : "completed";
|
|
2544
|
+
}
|
|
2320
2545
|
async terminateWorkflow(options) {
|
|
2321
2546
|
const { workflowKey, runKey, contextDocId } = options;
|
|
2322
2547
|
if (!workflowKey || typeof workflowKey !== "string") {
|
|
@@ -4370,6 +4595,9 @@ export class JsBaoClient extends Observable {
|
|
|
4370
4595
|
else if (data.type === "invitation") {
|
|
4371
4596
|
this.handleInvitationMessage(data);
|
|
4372
4597
|
}
|
|
4598
|
+
else if (data.type === "notification") {
|
|
4599
|
+
this.handleNotificationMessage(data);
|
|
4600
|
+
}
|
|
4373
4601
|
else if (data.type === "workflowStatus") {
|
|
4374
4602
|
this.handleWorkflowStatusMessage(data);
|
|
4375
4603
|
}
|
|
@@ -4608,6 +4836,14 @@ export class JsBaoClient extends Observable {
|
|
|
4608
4836
|
logger.debug("[invitation] client emit failed", { error: err });
|
|
4609
4837
|
}
|
|
4610
4838
|
}
|
|
4839
|
+
handleNotificationMessage(data) {
|
|
4840
|
+
try {
|
|
4841
|
+
this.emit("notification", [data]);
|
|
4842
|
+
}
|
|
4843
|
+
catch (err) {
|
|
4844
|
+
logger.debug("[notification] client emit failed", { error: err });
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4611
4847
|
handleWorkflowStatusMessage(data) {
|
|
4612
4848
|
try {
|
|
4613
4849
|
this.emit("workflowStatus", [data]);
|