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.
Files changed (50) hide show
  1. package/README.md +242 -39
  2. package/dist/JsBaoClient.d.ts +199 -16
  3. package/dist/JsBaoClient.d.ts.map +1 -1
  4. package/dist/JsBaoClient.js +240 -4
  5. package/dist/JsBaoClient.js.map +1 -1
  6. package/dist/api/blobBucketsApi.d.ts +29 -1
  7. package/dist/api/blobBucketsApi.d.ts.map +1 -1
  8. package/dist/api/blobBucketsApi.js +5 -3
  9. package/dist/api/blobBucketsApi.js.map +1 -1
  10. package/dist/api/collectionsApi.d.ts +9 -1
  11. package/dist/api/collectionsApi.d.ts.map +1 -1
  12. package/dist/api/collectionsApi.js.map +1 -1
  13. package/dist/api/databasesApi.d.ts +18 -1
  14. package/dist/api/databasesApi.d.ts.map +1 -1
  15. package/dist/api/databasesApi.js +8 -0
  16. package/dist/api/databasesApi.js.map +1 -1
  17. package/dist/api/documentsApi.d.ts +15 -5
  18. package/dist/api/documentsApi.d.ts.map +1 -1
  19. package/dist/api/documentsApi.js +19 -20
  20. package/dist/api/documentsApi.js.map +1 -1
  21. package/dist/api/geminiApi.d.ts.map +1 -1
  22. package/dist/api/geminiApi.js +11 -14
  23. package/dist/api/geminiApi.js.map +1 -1
  24. package/dist/api/groupsApi.d.ts +20 -4
  25. package/dist/api/groupsApi.d.ts.map +1 -1
  26. package/dist/api/groupsApi.js +9 -1
  27. package/dist/api/groupsApi.js.map +1 -1
  28. package/dist/api/notificationsApi.d.ts +134 -0
  29. package/dist/api/notificationsApi.d.ts.map +1 -0
  30. package/dist/api/notificationsApi.js +52 -0
  31. package/dist/api/notificationsApi.js.map +1 -0
  32. package/dist/api/resourceMetadataApi.d.ts +163 -0
  33. package/dist/api/resourceMetadataApi.d.ts.map +1 -0
  34. package/dist/api/resourceMetadataApi.js +56 -0
  35. package/dist/api/resourceMetadataApi.js.map +1 -0
  36. package/dist/browser.umd.js +514 -58
  37. package/dist/browser.umd.js.map +1 -1
  38. package/dist/errors.d.ts +34 -1
  39. package/dist/errors.d.ts.map +1 -1
  40. package/dist/errors.js +56 -0
  41. package/dist/errors.js.map +1 -1
  42. package/dist/internal/authController.d.ts +10 -0
  43. package/dist/internal/authController.d.ts.map +1 -1
  44. package/dist/internal/authController.js +14 -2
  45. package/dist/internal/authController.js.map +1 -1
  46. package/dist/internal/httpClient.d.ts +8 -0
  47. package/dist/internal/httpClient.d.ts.map +1 -1
  48. package/dist/internal/httpClient.js +34 -5
  49. package/dist/internal/httpClient.js.map +1 -1
  50. package/package.json +6 -2
@@ -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
- export { JsBaoError, isJsBaoError } from "./errors";
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
- const res = await fetch(url.toString(), { method, headers });
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
- throw new Error(`HTTP ${res.status}: ${text}`);
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]);