js-bao-wss-client 2.2.0-alpha.13 → 2.2.0-alpha.15

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 CHANGED
@@ -18,7 +18,6 @@ A TypeScript/JavaScript client library for js-bao-wss that provides HTTP APIs an
18
18
  - **Analytics**: Buffered event logging API with optional automatic lifecycle events
19
19
  - **Blob Storage**: Upload/list/get/downloadUrl/delete per document with offline cache
20
20
  - **Server Functions**: Invoke your app's server functions, start and wait on long-running runs, and join the channels they authorize
21
- - **LLM**: Chat API and model listing _(deprecated — run a managed prompt from a server function (`ctx.prompts.run`) instead)_
22
21
  - **Offline-first Open**: Non-blocking open with IndexedDB-backed cache
23
22
  - **Offline Blob Cache**: Cache API + IndexedDB backed uploads/reads with eviction and retry
24
23
  - **Network Controls**: Online/offline modes, reachability, connection control
@@ -595,7 +594,6 @@ The client exposes a buffered analytics queue that batches events, retries on re
595
594
  - `client.analytics.logEvent({ action, feature, context_json?, ... })`: enqueue a single event. `context_json` accepts an object (auto-serialized) or a JSON string.
596
595
  - `client.analytics.flush()`: attempt to send the queue immediately; also runs automatically on reconnect and right before unload/destroy.
597
596
  - `client.analytics.setPlanOverride(plan)` and `.setAppVersionOverride(version)`: stamp metadata onto every subsequent event until you clear or replace it.
598
- - `client.getLlmAnalyticsContext()`: returns `{ logEvent, isEnabled }` when any LLM auto phases are enabled so higher-level features can coordinate their own analytics.
599
597
 
600
598
  Queued events are persisted in IndexedDB when offline storage is active, so short offline windows or reloads do not drop data. Everything funnels through the same `analytics.batch` WebSocket channel used by the live event test.
601
599
 
@@ -608,9 +606,6 @@ All automatic emitters are on by default; pass `analyticsAutoEvents` when constr
608
606
  - `sync_error` (`"sync"`, toggle: `analyticsAutoEvents.syncErrors.enabled`): records the `documentId` and `reason` when flush/send attempts fail (with interval throttling).
609
607
  - `blob_upload_started` / `blob_upload_succeeded` / `blob_upload_failed` (`"blobs"`, toggles: `analyticsAutoEvents.blobUploads.{start|success|failure}`): include blob/document identifiers, attempt counts, byte size, and retry details (with truncated error text on failure).
610
608
  - `session_end` (`"session"`, toggle: `analyticsAutoEvents.sessionEnd`): emitted on `beforeunload` and `client.destroy()`, including `duration_ms` and exit reason.
611
- - `prompt_started` / `prompt_succeeded` / `prompt_failed` (`"llm"` or `"gemini"`, toggles: `analyticsAutoEvents.llm.{start|success|failure}` and `analyticsAutoEvents.gemini.{start|success|failure}`): unified prompt lifecycle events emitted by `client.llm.chat()` and `client.gemini.generate()`/`client.gemini.countTokens()`. The `feature` field distinguishes the provider.
612
-
613
- When `analyticsAutoEvents.llm` enables any of `start`, `success`, or `failure`, `client.getLlmAnalyticsContext()` becomes non-null so LLM helpers can emit structured events without guessing configuration. `analyticsAutoEvents.gemini` controls `client.getGeminiAnalyticsContext()`, which powers automatic logging inside the Gemini namespace.
614
609
 
615
610
  **Removed events** (no longer emitted, and their option flags have been removed from `analyticsAutoEvents`): `client_boot` (`boot`), `first_doc_open` (`firstDocOpen`), `first_doc_edit` (`firstDocEdit`), `offline_recovery` (`offlineRecovery`), `service_worker_control` / `service_worker_token_update` (`serviceWorker`), `llm_request_*`, `gemini_request_*`.
616
611
 
@@ -621,7 +616,7 @@ const client = await initializeClient({
621
616
  ...options,
622
617
  analyticsAutoEvents: {
623
618
  blobUploads: { start: false, success: true, failure: true },
624
- llm: { start: true, success: true, failure: false },
619
+ sessionEnd: false,
625
620
  },
626
621
  });
627
622
  ```
@@ -1280,15 +1275,19 @@ console.log(result.documentId); // The created document ID
1280
1275
  console.log(result.alias.aliasKey); // "current-draft"
1281
1276
  console.log(result.alias.documentId); // Same as documentId
1282
1277
 
1283
- // The alias create takes `documentFormat` too, so a large document can be
1284
- // created with its alias atomically. The response carries `documentFormat: 2`
1285
- // for a large document and omits the key for an ordinary one.
1278
+ // The alias create takes the whole create vocabulary — `tags`, `metadata` and
1279
+ // `documentFormat` — with the same meaning they have on `create()`: applied when
1280
+ // the call creates the document. The response echoes what it applied, and omits
1281
+ // `documentFormat` for an ordinary document.
1286
1282
  const ledger = await client.documents.createWithAlias({
1287
1283
  title: "Ledger",
1288
1284
  alias: { scope: "user", aliasKey: "ledger" },
1285
+ tags: ["ledger", "q3"],
1286
+ metadata: { owner: "finance" },
1289
1287
  documentFormat: 2,
1290
1288
  });
1291
1289
  console.log(ledger.documentFormat); // 2
1290
+ console.log(ledger.tags); // ["ledger", "q3"]
1292
1291
 
1293
1292
  // Attempting to create with an existing alias throws HTTP 409
1294
1293
  try {
@@ -1310,6 +1309,27 @@ try {
1310
1309
 
1311
1310
  Use `createWithAlias()` when you need guaranteed uniqueness based on an alias (e.g., "only one home page per app"). Use regular `create()` + `aliases.set()` when offline support is needed or when the document should be created regardless of alias conflicts.
1312
1311
 
1312
+ #### Get-or-Create with Alias
1313
+
1314
+ When you want the document to exist rather than to be new — "this user's ledger" — `getOrCreateWithAlias()` is the idempotent form: the first call creates it, every later one returns the same document. It takes the same options, applied only when the call creates the document:
1315
+
1316
+ ```typescript
1317
+ const ledger = await client.documents.getOrCreateWithAlias({
1318
+ title: "Ledger",
1319
+ alias: { scope: "user", aliasKey: "ledger" },
1320
+ tags: ["ledger"],
1321
+ documentFormat: 2,
1322
+ });
1323
+
1324
+ console.log(ledger.created); // true on the call that made it, false after
1325
+ console.log(ledger.documentFormat); // 2 either way
1326
+ console.log(ledger.tags); // ["ledger"] on the create; absent afterwards
1327
+ ```
1328
+
1329
+ A stated `documentFormat` is also a statement about a document the alias already names. If the stored format **differs**, the call is refused with `DOCUMENT_FORMAT_MISMATCH` (409) naming the document and both formats, and nothing is created — being handed an ordinary document when you asked for a large one is the mistake this route exists to prevent. If it agrees, the format is echoed. Omit it and an existing binding is answered exactly as it always was.
1330
+
1331
+ > All three create routes refuse a top-level body key they do not read, with 400 `VALIDATION_FAILED` and a `details` entry naming it. The typed methods above send only keys the routes read; this reaches you only if you build a request body by hand.
1332
+
1313
1333
  ### Manage Permissions
1314
1334
 
1315
1335
  ```typescript
@@ -1777,142 +1797,6 @@ const user = await client.users.getBasic("u01H...");
1777
1797
  console.log(user.name, user.email, user.appRole);
1778
1798
  ```
1779
1799
 
1780
- ## Large Language Models (LLM)
1781
-
1782
- > **Deprecated.** The direct LLM client API (`client.llm.*`) is deprecated and will be removed in a future major release. Run a managed prompt from a server function (`ctx.prompts.run`) instead.
1783
-
1784
- ```typescript
1785
- // List available models
1786
- const { models, defaultModel } = await client.llm.models();
1787
- console.log("Default model:", defaultModel);
1788
-
1789
- // Basic chat
1790
- const reply = await client.llm.chat({
1791
- // model is optional; uses server default when omitted
1792
- messages: [
1793
- { role: "system", content: "You are a helpful assistant." },
1794
- { role: "user", content: "Summarize: Collaborative editing with Yjs." },
1795
- ],
1796
- temperature: 0.2,
1797
- max_tokens: 512,
1798
- reasoning: {
1799
- effort: "medium",
1800
- exclude: false,
1801
- },
1802
- });
1803
- console.log(reply.content);
1804
-
1805
- // Chat with image attachment (base64-encoded PNG)
1806
- const imageBase64 = await loadImageAsBase64("/path/to/screenshot.png");
1807
- const imageReply = await client.llm.chat({
1808
- messages: [
1809
- {
1810
- role: "system",
1811
- content: "You analyze screenshots and respond helpfully.",
1812
- },
1813
- { role: "user", content: "Describe what you see in this image." },
1814
- ],
1815
- attachments: [
1816
- {
1817
- type: "image",
1818
- mime: "image/png",
1819
- base64: imageBase64,
1820
- },
1821
- ],
1822
- });
1823
- console.log(imageReply.content);
1824
- ```
1825
-
1826
- ## Gemini
1827
-
1828
- > **Deprecated.** The direct Gemini client API (`client.gemini.*`) is deprecated and will be removed in a future major release. Run a managed prompt from a server function (`ctx.prompts.run`) instead.
1829
-
1830
- ```typescript
1831
- // Text generation with optional structured output
1832
- const result = await client.gemini.generate({
1833
- messages: [
1834
- {
1835
- role: "system",
1836
- parts: [{ type: "text", text: "Reply in JSON with keys title and summary." }],
1837
- },
1838
- {
1839
- role: "user",
1840
- parts: [{ type: "text", text: "Summarize collaborative editing with Yjs." }],
1841
- },
1842
- ],
1843
- structuredOutput: {
1844
- responseMimeType: "application/json",
1845
- responseJsonSchema: {
1846
- type: "object",
1847
- properties: {
1848
- title: { type: "string" },
1849
- summary: { type: "string" },
1850
- },
1851
- required: ["title", "summary"],
1852
- },
1853
- },
1854
- });
1855
- console.log(result.message.parts[0].text);
1856
-
1857
- // Multimodal prompt with inline image data
1858
- const screenshot = await loadImageAsBase64("/path/to/screenshot.png");
1859
- await client.gemini.generate({
1860
- messages: [
1861
- {
1862
- role: "user",
1863
- parts: [
1864
- { type: "text", text: "Describe this screenshot." },
1865
- { type: "image", mimeType: "image/png", base64Data: screenshot },
1866
- ],
1867
- },
1868
- ],
1869
- });
1870
-
1871
- // Raw passthrough using Google's payload schema
1872
- const rawPayload = {
1873
- contents: [
1874
- {
1875
- role: "user",
1876
- parts: [
1877
- {
1878
- text: "Summarize this JSON.",
1879
- },
1880
- {
1881
- inline_data: {
1882
- mimeType: "application/json",
1883
- data: someBase64Json,
1884
- },
1885
- },
1886
- ],
1887
- },
1888
- ],
1889
- };
1890
- const rawResponse = await client.gemini.generateRaw({
1891
- model: "models/gemini-2.5-flash",
1892
- body: rawPayload,
1893
- });
1894
- console.log(rawResponse.candidates?.[0]?.content);
1895
-
1896
- // Token usage estimation
1897
- const tokens = await client.gemini.countTokens({
1898
- model: "models/gemini-2.5-flash",
1899
- messages: [{ role: "user", parts: [{ type: "text", text: "How many tokens?" }] }],
1900
- });
1901
- console.log(tokens.totalTokens);
1902
- ```
1903
-
1904
- ### Gemini Configuration & Notes
1905
-
1906
- - `client.gemini` proxies to server-side routes:
1907
- - `GET /gemini/models` → `client.gemini.models()`
1908
- - `POST /gemini/generate` → `client.gemini.generate(...)`
1909
- - `POST /gemini/count-tokens` → `client.gemini.countTokens(...)`
1910
- - `POST /gemini/generate-raw` → `client.gemini.generateRaw({ model, body })`
1911
- These endpoints run server-side, so browser clients never see Google credentials.
1912
- - Inline multimodal content uses base64 `parts` (text, image, file), up to about 25 MB per request.
1913
- - Structured output leverages `generationConfig.responseMimeType` plus optional `responseSchema` / `responseJsonSchema` so Gemini can return deterministic JSON. The full server response is always available via the `raw` field when you need annotations or safety metadata.
1914
- - Error handling surfaces `JsBaoError` with `code: "GEMINI_ERROR"`; the `details` property contains the raw upstream payload so you can log or render troubleshooting info.
1915
-
1916
1800
  ## Server Functions
1917
1801
 
1918
1802
  A server function is TypeScript your app's team writes in its config tree and
@@ -15,12 +15,11 @@ import { type RequestOptions } from "./internal/httpClient";
15
15
  import { type DocumentPermission as DocumentAccessLevel, type LocalDocumentEntry, type LocalMetadataEntry, type DocumentDebugSnapshot } from "./internal/documentManager";
16
16
  import { type OfflineReplayNotice } from "js-bao";
17
17
  import type { DocumentOfflineWindowError } from "js-bao";
18
+ import { JsBaoError } from "./errors";
18
19
  import { DocumentsAPI, DocumentContext, type DocumentInfo, type ResolveAliasParams, type CreateDocumentOptions } from "./api/documentsApi";
19
20
  import { type CacheFacade } from "./api/cacheFacade";
20
21
  import { MeAPI } from "./api/meApi";
21
22
  import { SessionAPI } from "./api/sessionApi";
22
- import { LlmAPI } from "./api/llmApi";
23
- import { GeminiAPI } from "./api/geminiApi";
24
23
  import { UsersAPI } from "./api/usersApi";
25
24
  import { DatabasesAPI } from "./api/databasesApi";
26
25
  import { GroupsAPI } from "./api/groupsApi";
@@ -55,15 +54,12 @@ export type { AppInvitationInfo, InvitationQuota, CreateInvitationParams, Invita
55
54
  export type { DoDb, DOClientEngine, ModelIdentifier, ModelFieldInfo, SaveOptions } from "js-bao/client";
56
55
  export type { DiscoveredSchema, DiscoveredModel, DiscoveredField, DiscoveredConstraint, DiscoveredRelationship } from "js-bao";
57
56
  export type { CacheFacade } from "./api/cacheFacade";
58
- export type { ReasoningOptions, LlmChatOptions } from "./api/llmApi";
59
57
  export type { AnalyticsEventInput } from "./internal/analyticsQueue";
60
58
  export { ANALYTICS_UNAUTHENTICATED_USER } from "./internal/analyticsQueue";
61
59
  export type { DocumentBlobContext, BlobUploadSourceOptions, BlobUploadResult, BlobListResult, BlobUploadStatus, BlobReadOptions, BlobPrefetchOptions, BlobProxyUrlOptions, BlobDisposition, } from "./internal/blobManager";
62
60
  export type { DocumentsAPI, DocumentContext, DocumentAliasesAPI, DocumentIngestsAPI, DocumentSnapshotsAPI, } from "./api/documentsApi";
63
61
  export type { DatabasesAPI } from "./api/databasesApi";
64
62
  export type { CollectionsAPI } from "./api/collectionsApi";
65
- export type { GeminiAPI } from "./api/geminiApi";
66
- export type { LlmAPI } from "./api/llmApi";
67
63
  export type { MeAPI } from "./api/meApi";
68
64
  export type { SessionAPI } from "./api/sessionApi";
69
65
  export type { UsersAPI } from "./api/usersApi";
@@ -85,7 +81,6 @@ export type { CreateGroupTypeConfigParams, UpdateGroupTypeConfigParams, } from "
85
81
  export type { CreateRuleSetParams, DebugRuleSetParams, ListRuleSetsOptions, TestRuleSetParams, UpdateRuleSetParams, } from "./api/ruleSetsApi";
86
82
  export type { GetMeOptions, UpdateMeParams } from "./api/meApi";
87
83
  export type { GetUserOptions } from "./api/usersApi";
88
- export type { GeminiCandidate, GeminiFilePart, GeminiGenerationConfig, GeminiImagePart, GeminiSafetySetting, GeminiTextPart, } from "./api/geminiApi";
89
84
  export type { AggregateGroupByEntry, CreateOperationParamsOf, DatabaseOperationInfoOf, ObjectOrParamsRef, PipelineAggregateStep, PipelineCountStep, PipelineQueryStep, } from "./api/operationDefinition";
90
85
  export type { NotificationTokenAttempt } from "./api/notificationsApi";
91
86
  export type { InvitationsAPI, InvitationListOptions, DeferredGrantListOptions, } from "./api/invitationsApi";
@@ -105,7 +100,7 @@ export type { ResourceMetadataReadResult, ResourceMetadataWriteResult, ResourceM
105
100
  * `analyticsAutoEvents` in {@link JsBaoClientOptions}. Every category is
106
101
  * enabled by default; set a flag (or a category's sub-flags) to `false` to
107
102
  * opt out. Categories cover daily auth, return-to-active resumes, sync
108
- * errors, blob upload lifecycle, session end, and LLM / Gemini calls.
103
+ * errors, blob upload lifecycle, and session end.
109
104
  */
110
105
  export interface AnalyticsAutoEventsOptions {
111
106
  dailyAuth?: boolean;
@@ -121,18 +116,7 @@ export interface AnalyticsAutoEventsOptions {
121
116
  failure?: boolean;
122
117
  };
123
118
  sessionEnd?: boolean;
124
- llm?: boolean | {
125
- start?: boolean;
126
- success?: boolean;
127
- failure?: boolean;
128
- };
129
- gemini?: boolean | {
130
- start?: boolean;
131
- success?: boolean;
132
- failure?: boolean;
133
- };
134
119
  }
135
- export type { GeminiRole, GeminiContentPart, GeminiMessage, GeminiPromptOptions, GeminiGenerateOptions, GeminiGenerateResult, GeminiModelSummary, GeminiStructuredOutput, GeminiGenerateRawOptions, } from "./api/geminiApi";
136
120
  /**
137
121
  * Request passed to `client.integrations.call()`. Identifies the integration
138
122
  * by key and describes the HTTP request to send through the integration
@@ -407,6 +391,25 @@ export interface ExecutePromptResult<R = unknown> {
407
391
  success: boolean;
408
392
  output: string;
409
393
  error?: string;
394
+ /**
395
+ * The upstream provider's own HTTP status, on a failure where the call
396
+ * reached the provider and it answered. It does not depend on which provider
397
+ * the prompt's configuration names, and it is what a retry decision is made
398
+ * on without parsing `error`: retry on 408, 429, 502, 503 and 504; any other
399
+ * 4xx will fail the same way next time.
400
+ *
401
+ * Absent on success, and absent on a failure where no provider answer was
402
+ * observed — an unset provider key, an oversized payload, a completion that
403
+ * came back empty — so a number here is always the provider's own.
404
+ */
405
+ upstreamStatus?: number;
406
+ /**
407
+ * `"PROMPT_UPSTREAM_TIMEOUT"` when the provider itself ran out of time,
408
+ * which is the one failure of the model call that carries a code. Absent on
409
+ * success and on every other provider failure, so `error` with no
410
+ * `errorCode` still means "the call failed for some other reason".
411
+ */
412
+ errorCode?: "PROMPT_UPSTREAM_TIMEOUT";
410
413
  metrics: {
411
414
  durationMs: number;
412
415
  inputTokens?: number;
@@ -663,6 +666,36 @@ export interface DocumentWriteRefusedEvent {
663
666
  */
664
667
  error: DocumentOfflineWindowError;
665
668
  }
669
+ /**
670
+ * Payload of the `document:format-mismatch` event
671
+ * (`client.on("document:format-mismatch", ...)`).
672
+ *
673
+ * Fired when the platform refuses a document because the format this client
674
+ * believed it had is not the format the platform resolved. The document is
675
+ * CLOSED when this arrives — its local rows and its unacknowledged writes are
676
+ * kept, nothing is evicted — because two of the three open shapes have no
677
+ * waiter to reject: a cached open sends its handshake from a microtask, and an
678
+ * already-open document re-syncs on reconnect. An open that WAS waiting on the
679
+ * network rejects with the same error as well.
680
+ *
681
+ * Diagnose from {@link declared} and {@link actual} before doing anything: the
682
+ * server also logs `Document format disagreement` naming its own pinned format.
683
+ * Evicting the document clears this client's belief, which repairs a stale row
684
+ * and HIDES a mis-pinned document.
685
+ */
686
+ export interface DocumentFormatMismatchEvent {
687
+ documentId: string;
688
+ /** What this client believed, from its own local metadata row. */
689
+ declared: 1 | 2;
690
+ /** What the platform resolved for the document. */
691
+ actual: 1 | 2;
692
+ /**
693
+ * The very error a waiting `documents.open` rejects with: `code` is
694
+ * `DOCUMENT_FORMAT_MISMATCH` and `details` carries `documentId`, `declared`
695
+ * and `actual`.
696
+ */
697
+ error: JsBaoError;
698
+ }
666
699
  /**
667
700
  * Options for `client.syncMetadata()`. Controls whether the sync covers all
668
701
  * of the user's document metadata or a single document (`scope` /
@@ -1794,6 +1827,7 @@ export interface JsBaoEvents {
1794
1827
  awareness: AwarenessEvent;
1795
1828
  "document:snapshot-load": DocumentSnapshotLoadEvent;
1796
1829
  "document:write-refused": DocumentWriteRefusedEvent;
1830
+ "document:format-mismatch": DocumentFormatMismatchEvent;
1797
1831
  "blobs:upload-progress": BlobUploadProgressEvent;
1798
1832
  "blobs:upload-completed": BlobUploadCompletedEvent;
1799
1833
  "blobs:upload-failed": BlobUploadFailedEvent;
@@ -2319,6 +2353,28 @@ export declare class JsBaoClient extends Observable<any> {
2319
2353
  * and released once the handshake has been answered.
2320
2354
  */
2321
2355
  private readonly format2AwaitingHandshake;
2356
+ /**
2357
+ * The format each open attempt in flight pinned — #3764, decision D8.
2358
+ *
2359
+ * One format per open, declared on `syncStep1` and BOUND by the ORM. The
2360
+ * background document read `DocumentManager.openDocument` starts rewrites the
2361
+ * metadata row in between, so reading the row at the bind could bind the
2362
+ * other format after the handshake had agreed on this one — the exact failure
2363
+ * this feature exists to prevent. Cleared when the open settles, after which
2364
+ * the row is the source again.
2365
+ */
2366
+ private readonly openAttemptFormats;
2367
+ /**
2368
+ * Documents the platform has REFUSED on this connection — #3764.
2369
+ *
2370
+ * A refused document sends no further handshake, is not retried by the sync
2371
+ * watchdog, and does not rebuild the socket on its account; a fresh open of it
2372
+ * is rejected with the refusal rather than waiting out an availability budget
2373
+ * for a handshake this client will not send. The mark is cleared when the
2374
+ * document is closed, evicted, or its metadata row's format changes — after
2375
+ * any of which this client's belief is no longer the one that was refused.
2376
+ */
2377
+ private readonly format2FormatRefusals;
2322
2378
  /** Outbound sequences per large document: queued, sent, acknowledged. */
2323
2379
  private readonly format2Ack;
2324
2380
  private jsBaoAddDocumentModelMapping;
@@ -2447,18 +2503,6 @@ export declare class JsBaoClient extends Observable<any> {
2447
2503
  /** Sub-API for session management (sign in, sign out, token refresh).
2448
2504
  * @group Sub-APIs */
2449
2505
  session: SessionAPI;
2450
- /** Sub-API for LLM (large language model) operations.
2451
- * @group Sub-APIs
2452
- * @deprecated The direct LLM client API is deprecated and will be removed in a
2453
- * future major release. Run a managed prompt from a
2454
- * server function instead: `ctx.prompts.run` (see the Server Functions guide). */
2455
- llm: LlmAPI;
2456
- /** Sub-API for Gemini model operations.
2457
- * @group Sub-APIs
2458
- * @deprecated The direct Gemini client API is deprecated and will be removed in
2459
- * a future major release. Run a managed prompt from a
2460
- * server function instead: `ctx.prompts.run` (see the Server Functions guide). */
2461
- gemini: GeminiAPI;
2462
2506
  /** Sub-API for managing app users.
2463
2507
  * @group Sub-APIs */
2464
2508
  users: UsersAPI;
@@ -2976,18 +3020,6 @@ export declare class JsBaoClient extends Observable<any> {
2976
3020
  private handleBlobUploadComplete;
2977
3021
  private handleBlobUploadFailed;
2978
3022
  private logSessionEndEvent;
2979
- /** Get the analytics context for LLM operations.
2980
- * @group Configuration */
2981
- getLlmAnalyticsContext(): {
2982
- logEvent: (event: Omit<AnalyticsEventInput, "user_ulid">) => void;
2983
- isEnabled: (phase?: "start" | "success" | "failure") => boolean;
2984
- } | null;
2985
- /** Get the analytics context for Gemini operations.
2986
- * @group Configuration */
2987
- getGeminiAnalyticsContext(): {
2988
- logEvent: (event: Omit<AnalyticsEventInput, "user_ulid">) => void;
2989
- isEnabled: (phase?: "start" | "success" | "failure") => boolean;
2990
- } | null;
2991
3023
  private sanitizeOverride;
2992
3024
  private readEnvNumber;
2993
3025
  /** Check if a document is synced with the server.
@@ -3398,6 +3430,12 @@ export declare class JsBaoClient extends Observable<any> {
3398
3430
  * the server's mark is contiguous, so nothing above a covered sequence can be
3399
3431
  * acknowledged anyway, and claiming it would only invite the prune this
3400
3432
  * exists to prevent.
3433
+ *
3434
+ * A `legacy` note (#3760) is the same debt reached a different way — writes
3435
+ * taken over from a pre-amendment pool and kept off the overlay until they
3436
+ * are judged — so it withholds exactly as an ordinary one does (finding
3437
+ * 3760-REVIEW-07). Only the bulk-load kind is left out: `_discontinuities`
3438
+ * owns that debt and its own hold.
3401
3439
  */
3402
3440
  private format2ApplyDurableWithhold;
3403
3441
  /** The withheld writes have been replayed: their sequences may travel. */
@@ -3848,6 +3886,95 @@ export declare class JsBaoClient extends Observable<any> {
3848
3886
  * nothing of its local state may go out (see `format2RequireReload`).
3849
3887
  */
3850
3888
  private format2NeedsReload;
3889
+ /**
3890
+ * Pin what this open attempt BELIEVES the document's format is, before any
3891
+ * handshake can leave (#3764, D8).
3892
+ *
3893
+ * Deliberately the local row's own answer rather than `_resolveDocumentFormat`,
3894
+ * and the difference is the whole point twice over:
3895
+ *
3896
+ * - that resolver DEFAULTS. It answers 1 for a document nobody knows about,
3897
+ * which is the right thing to BIND (the compatible layout) and a false
3898
+ * thing to DECLARE — a first open of somebody else's document would claim
3899
+ * format 1 and be refused. Only a row that NAMES a format is a belief, and
3900
+ * `documentFormatOf` answers `null` for anything else (criterion 3).
3901
+ * - it can reach the SERVER, and this runs before the connection is up, so
3902
+ * resolving here would both add a request to the open's critical path and
3903
+ * answer differently depending on how far the connect had got.
3904
+ *
3905
+ * Nothing is pinned when nothing is believed, and the bind then resolves as it
3906
+ * always has. When something IS pinned, the declaration and the bind are that
3907
+ * one value — which is what makes a row rewritten in between unable to move
3908
+ * the bind under a handshake that has already agreed.
3909
+ */
3910
+ private pinOpenAttemptFormat;
3911
+ /**
3912
+ * The format the ORM binds this document on (#3764, D8).
3913
+ *
3914
+ * The attempt's pinned answer when an open is in flight — never a second
3915
+ * resolution, which is what let a row rewritten mid-open move the bind under a
3916
+ * handshake that had already agreed. With no attempt (a rebind outside an
3917
+ * open) the ordinary resolution stands.
3918
+ */
3919
+ private boundDocumentFormat;
3920
+ /**
3921
+ * The format this client DECLARES for a document on `syncStep1` (#3764).
3922
+ *
3923
+ * The attempt's pinned answer while an open is in flight, else what the local
3924
+ * metadata row says. `undefined` — a first open of somebody else's document,
3925
+ * or one whose row an eviction removed — declares nothing, and such a client
3926
+ * is served exactly as it was before this field existed (criterion 3).
3927
+ */
3928
+ private declaredDocumentFormat;
3929
+ /**
3930
+ * Whether the platform has refused this document on this connection (#3764).
3931
+ *
3932
+ * The recorded belief is re-read before the mark is: behavior 17's rule is
3933
+ * that the mark clears when the row's format changes, and a row is written
3934
+ * from more places than the format resolver's own `remember` — a listing
3935
+ * upgrades it, and so does the canonical read an open performs. Asking here
3936
+ * means no write site has to remember, and the answer cannot drift from what
3937
+ * the next handshake would declare.
3938
+ */
3939
+ private formatRefusalFor;
3940
+ /**
3941
+ * Reject an open whose document the platform refused while it ran (#3764).
3942
+ *
3943
+ * A cached open resolves without a waiter, so a refusal landing while it is
3944
+ * still binding the ORM has nothing to fail. Checked after the bind and
3945
+ * before the handle is returned, the open rejects with the typed error
3946
+ * instead of handing back a document whose sync and persistence the
3947
+ * refusal's teardown has detached. A mark that stood before the open began
3948
+ * never gets here: the open refuses it on entry.
3949
+ */
3950
+ private rejectIfRefusedDuringOpen;
3951
+ /** Drop a format refusal, naming why it no longer applies (#3764). */
3952
+ private clearFormatRefusal;
3953
+ /**
3954
+ * A document's locally recorded format changed, so the belief that was
3955
+ * refused is no longer the one this client holds (#3764, behavior 17).
3956
+ *
3957
+ * Only a change AWAY from the refused declaration clears the mark: a row that
3958
+ * still says the refused thing would only be refused again.
3959
+ */
3960
+ private noteDocumentFormatChanged;
3961
+ /**
3962
+ * Refuse ONE document because the platform and this client disagree about its
3963
+ * format — #3764, decision D7.
3964
+ *
3965
+ * A document-scoped teardown rather than a waiter failer, and that is the
3966
+ * whole finding: of the three shapes an open takes, only a network wait has a
3967
+ * waiter. A cached open sends its `syncStep1` from a microtask with nothing
3968
+ * waiting, and an already-open document re-syncs on reconnect — so an
3969
+ * event-only answer would leave both bound and syncing against a document the
3970
+ * platform is refusing to serve them.
3971
+ *
3972
+ * What it does NOT do: close the connection (D1 — one document is refused,
3973
+ * and the socket's other documents are none of its business) and clear any
3974
+ * local data (the rows and `_pending_ops` are kept, so a corrected open
3975
+ * publishes what this client wrote).
3976
+ */
3977
+ private refuseDocumentFormat;
3851
3978
  /**
3852
3979
  * Hold every open large document's queued writes until its own handshake on
3853
3980
  * this connection has been answered (#3435).