@rebasepro/client 0.23.0 → 0.24.1-canary.g56af4a4
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 +8 -8
- package/dist/admin-contract.types.d.ts +21 -0
- package/dist/admin.d.ts +11 -5
- package/dist/api-keys.d.ts +29 -11
- package/dist/auth.d.ts +65 -11
- package/dist/backups.d.ts +14 -7
- package/dist/collection.d.ts +40 -19
- package/dist/cron.d.ts +13 -8
- package/dist/exports-contract.types.d.ts +39 -0
- package/dist/functions.d.ts +2 -12
- package/dist/index.d.ts +44 -19
- package/dist/index.es.js +1509 -3924
- package/dist/index.es.js.map +1 -1
- package/dist/offline-8Ks3yp9n.js +3307 -0
- package/dist/offline-8Ks3yp9n.js.map +1 -0
- package/dist/offline-codec.d.ts +12 -0
- package/dist/offline-connectivity.d.ts +6 -0
- package/dist/offline-errors.d.ts +9 -0
- package/dist/offline-lazy.d.ts +58 -0
- package/dist/offline-store-D9ytNNaa.js +1825 -0
- package/dist/offline-store-D9ytNNaa.js.map +1 -0
- package/dist/offline-store.d.ts +45 -0
- package/dist/offline.d.ts +315 -19
- package/dist/query-contract.types.d.ts +76 -24
- package/dist/realtime-channel.d.ts +30 -0
- package/dist/reviver.d.ts +18 -0
- package/dist/sdk_query_builder.d.ts +2 -2
- package/dist/sql-script.d.ts +10 -0
- package/dist/storage-resumable-VO-cMpse.js +2 -0
- package/dist/storage-resumable.d.ts +1 -0
- package/dist/transport.d.ts +95 -7
- package/dist/websocket.d.ts +183 -21
- package/package.json +4 -4
- package/dist/query_builder.d.ts +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline-store-D9ytNNaa.js","names":[],"sources":["../src/reviver.ts","../src/transport.ts","../src/sdk_query_builder.ts","../src/collection.ts","../src/offline-errors.ts","../src/offline-store.ts"],"sourcesContent":["import { EntityReference, EntityRelation, GeoPoint, Vector } from \"@rebasepro/types\";\n\nexport function rebaseReviver(_key: string, value: unknown): unknown {\n if (value && typeof value === \"object\" && \"__type\" in value) {\n const record = value as Record<string, unknown>;\n switch (record.__type) {\n case \"date\":\n case \"Date\": {\n if (typeof record.value !== \"string\") {\n return value;\n }\n const date = new Date(record.value);\n return isNaN(date.getTime()) ? null : date;\n }\n case \"reference\":\n case \"EntityReference\":\n return new EntityReference({\n id: String(record.id),\n path: record.path as string,\n driver: record.driver as string | undefined,\n databaseId: record.databaseId as string | undefined\n });\n case \"relation\":\n case \"EntityRelation\":\n return new EntityRelation(\n record.id as string | number,\n record.path as string,\n record.data as Record<string, unknown> | undefined\n );\n case \"GeoPoint\":\n return new GeoPoint(record.latitude as number, record.longitude as number);\n case \"Vector\":\n return new Vector(record.value as number[]);\n default:\n return value;\n }\n }\n return value;\n}\n\n/**\n * Could `text` hold an object with a `__type` key — the only values\n * {@link rebaseReviver} changes?\n *\n * A key is spelled either literally or with `\\u` escapes, so a body with\n * neither cannot contain one. The check is two substring scans, which run at\n * memory speed; a false positive only costs the walk below.\n */\nfunction mayHoldTypedValues(text: string): boolean {\n return text.includes(\"\\\"__type\\\"\") || text.includes(\"\\\\u\");\n}\n\n/**\n * Set a property the way `JSON.parse`'s reviver does: as an own data property.\n * A plain assignment to a key named `__proto__` would call the setter and swap\n * the object's prototype instead.\n */\nfunction setRevived(target: Record<string, unknown> | unknown[], key: string | number, value: unknown): void {\n if (key === \"__proto__\") {\n Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });\n } else {\n (target as Record<string | number, unknown>)[key] = value;\n }\n}\n\n/**\n * {@link rebaseReviver}, applied bottom-up to an already parsed value, in\n * place: every child is revived before its parent, so a relation's `data` holds\n * its own revived values by the time the relation is built from it — the order\n * `JSON.parse` calls a reviver in. Primitives are never visited; the reviver\n * hands every one of them back unchanged.\n */\nfunction reviveTree(value: unknown): unknown {\n if (value === null || typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const item: unknown = value[i];\n if (item !== null && typeof item === \"object\") {\n const revived = reviveTree(item);\n if (revived !== item) value[i] = revived;\n }\n }\n return value;\n }\n const record = value as Record<string, unknown>;\n for (const key of Object.keys(record)) {\n const item = record[key];\n if (item !== null && typeof item === \"object\") {\n const revived = reviveTree(item);\n if (revived !== item) setRevived(record, key, revived);\n }\n }\n return rebaseReviver(\"\", record);\n}\n\n/**\n * `JSON.parse(text, rebaseReviver)`, without the reviver's cost where it has\n * nothing to do.\n *\n * Handing `JSON.parse` a reviver takes V8 off its fast path and calls back for\n * every key and every value, primitives included: about 7× a plain parse on a\n * 1,000-row page, 10 ms of main thread per 600 KB. The reviver only ever\n * changes objects carrying `__type`, and a REST page of flat rows has none. So\n * this parses plainly and walks the result only when the text can hold one —\n * and the walk visits objects, never primitives.\n *\n * Same result, instance for instance: the same values revived, bottom-up, as\n * own data properties, and nothing deleted (the reviver never answers\n * `undefined`).\n *\n * @throws SyntaxError exactly where `JSON.parse` does.\n */\nexport function parseRebaseJson<T = unknown>(text: string): T {\n const parsed: unknown = JSON.parse(text);\n if (!mayHoldTypedValues(text)) return parsed as T;\n return reviveTree(parsed) as T;\n}\n","import { AggregateParams, FindParams as TypesFindParams, FindResponse as TypesFindResponse, IMPERSONATE_HEADER, type IncludeSpec, RebaseApiError, SCHEMA_VERSION_HEADER } from \"@rebasepro/types\";\nimport { RESERVED_QUERY_KEYS, resolveFindWindow, serializeFilter, serializeInclude, serializeLogicalCondition, serializeOrderBy } from \"@rebasepro/common\";\nimport { parseRebaseJson } from \"./reviver\";\n\n// The canonical client error now lives in `@rebasepro/types` so every package\n// (client, auth, …) throws one type. Re-exported here to preserve the historical\n// `import { RebaseApiError } from \".../transport\"` path used across the SDK.\nexport { RebaseApiError } from \"@rebasepro/types\";\nexport type { RebaseErrorInit } from \"@rebasepro/types\";\nimport { RebaseClientError } from \"@rebasepro/types\";\n\nexport interface RebaseClientConfig {\n /**\n * Origin of the Rebase server — scheme, host and port **only**.\n *\n * {@link apiPath} is appended to this, so do not include it here:\n * `\"http://localhost:3001\"` is correct, while `\"http://localhost:3001/api\"`\n * silently builds `/api/api/…` and every request 404s. Omit entirely for\n * same-origin requests from the browser.\n */\n baseUrl?: string;\n /**\n * Bearer token sent as `Authorization` on every request.\n *\n * In the browser this is the signed-in user's access token, so row-level\n * security applies. Server-side callers — scripts, cron jobs, ETL — pass the\n * service key instead, which resolves to `{ uid: \"service\", roles: [\"admin\"] }`\n * and **bypasses RLS**: there is no user to constrain those queries, so scope\n * them explicitly.\n */\n token?: string;\n /**\n * Path the API is mounted under, appended to {@link baseUrl}.\n * Defaults to `\"/api\"`; override only if the server mounts it elsewhere.\n */\n apiPath?: string;\n /**\n * Origin to use instead of {@link baseUrl} for URLs that are handed to the\n * browser to fetch on its own — storage file downloads and previews.\n *\n * API *requests* always go to `baseUrl`; this only changes URLs the SDK\n * *returns* (e.g. `storage.getSignedUrl`). It exists for proxied setups:\n * when `baseUrl` routes through an authenticated middleman (the Rebase\n * console's Studio proxy), a plain `<img src>` or a copied link cannot\n * satisfy the middleman's auth — but the file route itself is reachable\n * directly at the origin server and secured by its own scoped `?token=`.\n * Set this to that server's public origin (no path; {@link apiPath} is\n * appended) and returned file URLs point straight at it.\n */\n storageUrlOrigin?: string;\n fetch?: typeof globalThis.fetch;\n onUnauthorized?: () => Promise<boolean>;\n websocketUrl?: string; // Optional real-time WebSocket connection\n /**\n * Open the realtime WebSocket. **Defaults to `true`.**\n *\n * The socket connects as soon as the client is constructed and keeps the\n * Node event loop alive, so a one-shot script (CLI, cron job, ETL) will not\n * exit on its own. Set this to `false` for any process that reads or writes\n * and then terminates — `.listen()` and `.listenById()` then throw instead\n * of silently doing nothing.\n *\n * Long-lived processes that do want realtime can instead call\n * `client.close()` when shutting down.\n */\n realtime?: boolean;\n /**\n * \"Yes, I meant to be anonymous.\"\n *\n * Off-browser, a client with no credential can only ever call as an\n * anonymous user, and row-level security answers it with whatever is\n * public — usually nothing. That is almost always a mistake in a script or\n * cron job, so the SDK warns once on the first request (see\n * {@link ANONYMOUS_SERVER_CLIENT_WARNING}). Anonymous is a legitimate\n * choice for public reads, though; set this to `true` to say so and\n * silence the warning.\n *\n * Has no effect in the browser, where anonymous-before-sign-in is normal\n * and nothing is ever warned about.\n */\n anonymous?: boolean;\n /**\n * The schema this client was generated against, sent as `x-rebase-schema`.\n *\n * `rebase generate-sdk` writes the value into `schema.meta.ts` as\n * `SCHEMA_VERSION`; pass it here and every request carries it. Advisory —\n * nothing is ever refused for it — but it is what lets a server say \"this\n * client was built against a schema three deploys old\" instead of\n * answering a renamed field with a bare 400.\n *\n * The compatibility matrix has listed this header as something the SDK\n * sends since it was written. Nothing sent it: the constant existed, the\n * server echoed it back on two routes, and no client ever put it on a\n * request. This is the missing half.\n */\n schemaVersion?: string;\n /**\n * Headers added to every request, beneath any per-request `headers`.\n *\n * For identifying the caller, not for authenticating it — the CLI uses it\n * to send `User-Agent: rebase-cli/<version>`, which is what a control plane\n * needs before it can refuse a CLI that is too old by name. `Authorization`\n * is not settable here; it always comes from the token.\n */\n headers?: Record<string, string>;\n /**\n * Run every request as this user, named by uid: sent as\n * `x-rebase-impersonate` on each HTTP request, and with the realtime\n * socket's sign-in.\n *\n * For checking what one user can see and do against the real row-level\n * security, as Studio's API explorer and JS editor do. Only an\n * administrator's own session may — the server refuses it for anyone\n * else, and always for an API key or the service key. The data API,\n * custom functions and realtime run as the named user; every other route\n * refuses the request rather than answer as the administrator.\n */\n impersonate?: string;\n}\n\n/**\n * Facts about the surrounding client that the transport cannot read off its own\n * config, but needs in order to decide whether a request is *meaningfully*\n * credential-less.\n */\nexport interface TransportEnvironment {\n /**\n * The credential reaches the server without an `Authorization` header —\n * i.e. `auth.authFlowMode: \"cookie\"`, where the refresh token lives in an\n * httpOnly cookie. Such a client looks tokenless to the transport but is\n * not anonymous, so it must never trip the guard.\n */\n credentialOutOfBand?: boolean;\n}\n\n/**\n * True when there is no browser to have signed a user in — a Node script, a\n * cron job, an edge worker.\n *\n * Anonymous is an ordinary, correct state in a browser: before sign-in, on a\n * marketing page, for public reads. Warning there would be noise that teaches\n * people to ignore warnings, so the guard is off entirely. This uses the same\n * `typeof window` test as {@link resolveBaseUrl}, and additionally treats a\n * defined `document` as a browser so an SSR shim or test harness that installs\n * only one of the two is still excluded.\n */\nfunction isServerLikeEnvironment(): boolean {\n return typeof window === \"undefined\" && typeof document === \"undefined\";\n}\n\n/**\n * Emitted once per client. Kept as a constant so the wording is testable and\n * greppable — this is the string a user will paste into a search.\n */\nexport const ANONYMOUS_SERVER_CLIENT_WARNING =\n \"[rebase] This client was created outside a browser with no credential — no `token`, no auth token getter, \"\n + \"and no cookie auth flow — so every request runs as an anonymous caller. Row-level security will return only \"\n + \"publicly readable rows, which is usually nothing and occasionally the wrong thing. \"\n + \"Inside a cron or function handler, use the `rebase` you were handed instead of building a new one: its data \"\n + \"plane is already admin-scoped. In a standalone script or job, pass the service key as `token`. \"\n + \"If you really do want anonymous access, pass `anonymous: true` to silence this.\";\n\n/**\n * Re-exported from `@rebasepro/types` so an SDK consumer can name the type of a\n * call it is already making without a second dependency.\n *\n * Forwards the row type: without the parameter this alias flattened\n * `FindParams<M>` back to its `Record<string, unknown>` default, and `where` /\n * `orderBy` went back to accepting any column name — the alias, not the\n * definition, was where the typing was lost.\n */\nexport type FindParams<M extends Record<string, unknown> = Record<string, unknown>, Inc = IncludeSpec> = TypesFindParams<M, Inc>;\nexport type FindResponse<T> = TypesFindResponse<T extends Record<string, unknown> ? T : Record<string, unknown>>;\n\n/**\n * Refuse a filter whose *value* is missing.\n *\n * `where: { status: [\"==\", undefined] }` used to serialize to the literal\n * string, so `status=eq.undefined` went out on the wire and the server dutifully\n * looked for rows whose status is the four-letter word \"undefined\". The caller\n * saw an empty page, not an error — the classic shape of a variable that was\n * never set.\n *\n * Dropping the condition instead would be worse than sending it: the query\n * would come back *unfiltered*, which for an ownership or tenant filter means\n * returning rows the caller never asked to see. So this is a hard error, and\n * both correct spellings are named in the message: omit the key to skip the\n * filter, or use `[\"is-null\", null]` to match SQL NULL (which still\n * serializes — `null` is a value, `undefined` is the absence of one).\n */\nexport function assertNoUndefinedFilterValues(where: Record<string, unknown>): void {\n const reject = (field: string, op: unknown): never => {\n throw new RebaseClientError(\n `Filter on \"${field}\" has an undefined value ([\"${String(op)}\", undefined]). `\n + `Omit \"${field}\" from \\`where\\` to skip the filter, or use [\"is-null\", null] to match SQL NULL.`,\n { code: \"INVALID_FILTER\", details: { field, operator: String(op) } }\n );\n };\n\n for (const [field, condition] of Object.entries(where)) {\n // An entirely absent condition is the documented way to skip a filter.\n if (condition === undefined) continue;\n if (!Array.isArray(condition)) continue;\n\n // Either one `[op, value]` tuple or an array of them.\n const tuples = Array.isArray(condition[0]) ? condition as unknown[][] : [condition as unknown[]];\n for (const tuple of tuples) {\n if (!Array.isArray(tuple) || tuple.length !== 2) continue;\n const [op, value] = tuple;\n if (value === undefined) reject(field, op);\n // `[\"in\", [...]]` — a hole in the list is the same mistake.\n if (Array.isArray(value) && value.some(v => v === undefined)) reject(field, op);\n }\n }\n}\n\n/**\n * A find's parameters as `[name, value]` pairs, in the order the URL carries\n * them, values not yet URL-encoded.\n *\n * One list for both spellings of a listing: {@link buildQueryString} encodes it\n * into the `GET`'s URL, and {@link buildQueryBody} gathers it into the body of\n * `POST …/query`, which the server turns back into exactly these parameters.\n * Two serializers would be two chances to send a query one way and not the\n * other.\n */\nfunction findParamPairs(params?: FindParams, deleted?: SoftDeletedRows): [string, string][] {\n const pairs: [string, string][] = [];\n // First, so a trash read is recognisable at a glance in a log line.\n if (deleted) pairs.push([\"deleted\", deleted]);\n if (!params) return pairs;\n // Refuses `page` with `offset` (`PAGE_WITH_OFFSET`) before any request,\n // with the code the server would answer.\n resolveFindWindow(params);\n\n if (params.limit != null) pairs.push([\"limit\", String(params.limit)]);\n if (params.offset != null) pairs.push([\"offset\", String(params.offset)]);\n if (params.page != null) pairs.push([\"page\", String(params.page)]);\n // The opaque keyset cursor, passed straight back. Nothing on this side\n // reads it: the encoding is the server's, and a client that parsed it would\n // be depending on something that exists to be changed.\n if (params.after) pairs.push([\"after\", params.after]);\n // Only `none`: `exact` is the server's default, so sending it says\n // nothing — and a server older than the parameter reads any `?count=` as\n // a filter on a column named `count` (see `find` in collection.ts).\n if (params.count === \"none\") pairs.push([\"count\", \"none\"]);\n\n if (params.orderBy) {\n const wire = serializeOrderBy(params.orderBy);\n if (wire) pairs.push([\"orderBy\", wire]);\n }\n\n if (params.searchString) {\n pairs.push([\"searchString\", params.searchString]);\n if (params.searchExplain) pairs.push([\"searchExplain\", \"true\"]);\n }\n\n // The server keys vector search off `vector_search` naming the property and\n // `vector` carrying the embedding as a JSON array; both must be present or\n // it ignores the pair entirely.\n if (params.vectorSearch) {\n const vs = params.vectorSearch;\n pairs.push([\"vector_search\", vs.property]);\n pairs.push([\"vector\", JSON.stringify(vs.vector)]);\n if (vs.distance) pairs.push([\"vector_distance\", vs.distance]);\n if (vs.threshold !== undefined) pairs.push([\"vector_threshold\", String(vs.threshold)]);\n }\n\n // Through the shared codec, which picks the wire spelling: comma-separated\n // dotted paths when no relation carries options, JSON when one does. The\n // server accepts both and tells them apart the same way. Joining an array\n // here — which is what this did — could not express the tree at all.\n const include = serializeInclude(params.include);\n if (include) pairs.push([\"include\", include]);\n\n if (params.fields && params.fields.length > 0) {\n pairs.push([\"fields\", params.fields.join(\",\")]);\n }\n\n // Only when true. `?distinct=false` is the default and sending it says\n // nothing, while the server refuses anything that is neither.\n if (params.distinct) pairs.push([\"distinct\", \"true\"]);\n\n if (params.logical) {\n const root = params.logical;\n const serialized = (root.conditions ?? []).map(serializeLogicalCondition).join(\",\");\n pairs.push([root.type, `(${serialized})`]);\n }\n\n if (params.where) appendWhere(pairs, params.where);\n\n return pairs;\n}\n\nconst encodePairs = (pairs: [string, string][]): string =>\n pairs.length > 0\n ? \"?\" + pairs.map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join(\"&\")\n : \"\";\n\n/**\n * `?deleted=` — which soft-deleted rows a read covers: `\"only\"` the trashed\n * ones, `\"include\"` live and trashed together. Absent, the server hides them.\n *\n * Not a {@link FindParams} field: `FindParams` is also what `listen()` and the\n * offline cache take, and neither can answer for a trashed row — a\n * subscription has no `withDeleted`, and the local database never holds one.\n * Only the trash accessor (`client.data.<slug>.trash`) passes it.\n */\nexport type SoftDeletedRows = \"include\" | \"only\";\n\nexport function buildQueryString(params?: FindParams, deleted?: SoftDeletedRows): string {\n return encodePairs(findParamPairs(params, deleted));\n}\n\n/**\n * The body of `POST /data/<collection>/query`: the parameters\n * {@link buildQueryString} puts in the URL, spelled as the URL spells them —\n * each value a string, and a parameter the URL repeats (a filter with two\n * conditions on one field) a list of them. The server reads it back into the\n * same parameters and runs the same listing.\n */\nexport function buildQueryBody(params?: FindParams, deleted?: SoftDeletedRows): Record<string, string | string[]> {\n const body: Record<string, string | string[]> = {};\n for (const [name, value] of findParamPairs(params, deleted)) {\n const held = body[name];\n body[name] = held === undefined ? value : [...(Array.isArray(held) ? held : [held]), value];\n }\n return body;\n}\n\n/**\n * Append a `where` to a query string, one `?<field>=<op>.<value>` parameter per\n * condition.\n *\n * Except on a column whose name the server reads as a parameter of its own —\n * `page`, `select`, `hard`, `where` ({@link RESERVED_QUERY_KEYS}). A filter on\n * such a column sent as its own parameter is read as that parameter, and the\n * filter is never applied. Those conditions go inside `?where=` instead, in the\n * same dot-string spelling, which the server decodes through the same codec and\n * merges in. A collection with no such column sends exactly what it did before.\n */\nfunction appendWhere(pairs: [string, string][], where: Record<string, unknown>): void {\n assertNoUndefinedFilterValues(where);\n const serialized = serializeFilter(where);\n const reserved: Record<string, string | string[]> = {};\n for (const [field, value] of Object.entries(serialized)) {\n if (RESERVED_QUERY_KEYS.has(field)) {\n reserved[field] = value;\n continue;\n }\n for (const v of Array.isArray(value) ? value : [value]) pairs.push([field, v]);\n }\n if (Object.keys(reserved).length > 0) {\n pairs.push([\"where\", JSON.stringify(reserved)]);\n }\n}\n\n/**\n * The query string for `GET /<collection>/aggregate`.\n *\n * `?select=sum(total),count()` is SQL's spelling, because whoever writes an\n * aggregate is thinking in SQL and any other spelling has to be learned first —\n * and because it is what the route already parses. The filters are serialised\n * by exactly the same code a `find()` uses, so \"revenue by status, this month\"\n * narrows the same rows whichever call is asking.\n *\n * `include` and a cursor are deliberately not here: an aggregate has no\n * relations to load and no row to continue after. `limit`, `offset` and\n * `orderBy` are, and page and sort the *groups* — `orderBy` by a group column or\n * an aggregate's alias, in the spelling a `find()` sends.\n */\nexport function buildAggregateQueryString<M extends Record<string, unknown>>(params: AggregateParams<M>): string {\n const pairs: [string, string][] = [];\n\n const select = params.select\n .map(entry => `${entry.fn}(${(entry as { field?: string }).field ?? \"\"})`)\n .join(\",\");\n pairs.push([\"select\", select]);\n\n if (params.groupBy && params.groupBy.length > 0) {\n pairs.push([\"groupBy\", params.groupBy.join(\",\")]);\n }\n if (params.limit != null) pairs.push([\"limit\", String(params.limit)]);\n if (params.offset != null) pairs.push([\"offset\", String(params.offset)]);\n if (params.orderBy) {\n const wire = serializeOrderBy(params.orderBy);\n if (wire) pairs.push([\"orderBy\", wire]);\n }\n if (params.searchString) {\n pairs.push([\"searchString\", params.searchString]);\n }\n if (params.logical) {\n const root = params.logical;\n const serialized = (root.conditions ?? []).map(serializeLogicalCondition).join(\",\");\n pairs.push([root.type, `(${serialized})`]);\n }\n if (params.where) appendWhere(pairs, params.where);\n\n return encodePairs(pairs);\n}\n\n/**\n * The 401 codes that mean \"the credential on this request is no good\", which a\n * refreshed token can fix.\n *\n * The rest of the 401s are answers about something the caller *sent*: a wrong\n * current password (`INVALID_CREDENTIALS`), a mistyped MFA code (`INVALID_CODE`,\n * `CHALLENGE_EXHAUSTED`), a sign-in that needs a second factor\n * (`MFA_REQUIRED`). Refreshing and resending one of those rotated the session\n * for nothing, sent the same wrong answer twice — and an MFA challenge claims\n * an attempt before it judges the code, so every typo cost two of its five.\n */\nconst TOKEN_REFUSAL_CODES = new Set([\"UNAUTHORIZED\", \"INVALID_TOKEN\", \"TOKEN_EXPIRED\", \"SESSION_REVOKED\"]);\n\n/**\n * Whether a 401's body says the token was the problem.\n *\n * A 401 with no code at all counts: it is what an older server, a proxy or\n * anything not speaking the envelope sends, and there the token is the\n * likeliest cause — refreshing is what this client always did with it.\n */\nfunction isTokenRefusal(body: Record<string, unknown>): boolean {\n const error = body?.error;\n const code = error && typeof error === \"object\" ? (error as Record<string, unknown>).code : undefined;\n return typeof code !== \"string\" || code === \"\" || TOKEN_REFUSAL_CODES.has(code);\n}\n\n/**\n * Response metadata a caller can ask for, filled in by `request`.\n *\n * An out-parameter rather than a second return value, because every one of the\n * fifty-odd call sites wants the body and nothing else, and changing the return\n * shape would mean rewriting all of them to reach past a wrapper. It is also\n * why this is a third *optional* parameter: a `Transport` stub in a test that\n * ignores it is still a valid `Transport`.\n *\n * Only `ETag` for now, and only because a row's version is not part of the row.\n * It cannot be: adding it as a column would put it in the generated `Row` type,\n * in every `find()` result, in the offline cache and in what a caller sends\n * back on the next write — a field the server would then have to strip. The\n * header is where HTTP puts it, so the header is where this reads it.\n */\nexport interface ResponseMeta {\n /** The `ETag` header, when the response carried one. */\n etag?: string;\n /** The HTTP status, for a caller that has to tell 200 from 204. */\n status?: number;\n /**\n * The response's headers, for a protocol that answers in them — a\n * resumable upload's `Location` and `Upload-Offset`.\n */\n headers?: { get(name: string): string | null };\n}\n\n/** See {@link Transport.setTokenRefresher}. */\nexport type TokenRefresher = () => Promise<string | undefined> | undefined;\n\nexport interface Transport {\n request: <T = unknown>(path: string, init?: RequestInit, meta?: ResponseMeta) => Promise<T>;\n /**\n * Like `request`, but the body of a success is left unread: the response\n * comes back as it arrived, for a caller that wants text, a Blob or a\n * stream. Auth, the refresh-and-resend and the error envelope are the same.\n * Optional so a hand-written transport need not have it.\n */\n requestRaw?: (path: string, init?: RequestInit, meta?: ResponseMeta) => Promise<Response>;\n setToken: (newToken: string | null) => void;\n setAuthTokenGetter: (getter: () => Promise<string | null>) => void;\n setOnUnauthorized: (handler: () => Promise<boolean>) => void;\n /**\n * Install the hook asked, before each request that carries this\n * transport's own token, whether that token is known to be spent. It\n * answers `undefined` to send the token as held, or a promise for the one\n * to send instead — the auth module's refresh, so a request made while a\n * session is being restored, or after its token expired unnoticed, goes\n * out once with a good token rather than with none or a dead one.\n *\n * Not consulted when an auth token getter is installed: the getter owns\n * the token then. Optional so a hand-written transport need not have it.\n */\n setTokenRefresher?: (refresher: TokenRefresher | null) => void;\n readonly baseUrl: string;\n readonly apiPath: string;\n /** See {@link RebaseClientConfig.storageUrlOrigin}. Undefined = use `baseUrl`. */\n readonly storageUrlOrigin?: string;\n readonly fetchFn: typeof globalThis.fetch;\n getHeaders: (init?: RequestInit) => Record<string, string>;\n resolveToken: () => Promise<string | null>;\n}\n\n/**\n * The base every request and every caller-built URL resolves against.\n *\n * `baseUrl` is optional because the common production shape is a Rebase\n * backend serving its own SPA, where the API is simply the page's origin.\n * Leaving it unset is therefore the *correct* configuration there — and the\n * one that keeps working when a second hostname (a custom domain) points at\n * the same app.\n *\n * When unset in a browser this resolves to the page origin rather than \"\".\n * Requests behave identically either way, but the empty string is a trap for\n * anything that builds a URL from `client.baseUrl`: `new URL(\"\" + path)`\n * throws, so apps \"fixed\" it by baking an absolute host into their bundle —\n * which is exactly what breaks the day a custom domain is added, and which no\n * amount of CORS configuration repairs, because a SameSite=Lax auth cookie is\n * not sent cross-site either.\n */\n/**\n * The code an error carries when the response did not name one: a proxy's 413,\n * a load balancer's 502, a crash page's 500 — anything without the server's\n * envelope. Without it `e.code` was `undefined` and the documented\n * `switch (e.code)` fell through to `default` on exactly the failures that are\n * not Rebase's own. Only codes the backend itself answers with, so a switch\n * written against the error reference catches both.\n *\n * @internal\n */\nexport function defaultCodeForStatus(status: number): string | undefined {\n switch (status) {\n case 400: return \"BAD_REQUEST\";\n case 401: return \"UNAUTHORIZED\";\n case 403: return \"FORBIDDEN\";\n case 404: return \"NOT_FOUND\";\n case 409: return \"CONFLICT\";\n case 412: return \"PRECONDITION_FAILED\";\n case 413: return \"PAYLOAD_TOO_LARGE\";\n case 415: return \"UNSUPPORTED_MEDIA_TYPE\";\n case 429: return \"RATE_LIMITED\";\n case 502: case 503: case 504: return \"SERVICE_UNAVAILABLE\";\n default: return status >= 500 && status < 600 ? \"INTERNAL_ERROR\" : undefined;\n }\n}\n\nfunction resolveBaseUrl(configured?: string): string {\n if (configured) return configured.replace(/\\/$/, \"\");\n if (typeof window !== \"undefined\" && window.location?.origin) return window.location.origin;\n return \"\";\n}\n\nexport function createTransport(config: RebaseClientConfig, environment?: TransportEnvironment): Transport {\n const rawFetch = config.fetch || globalThis.fetch;\n\n /**\n * `fetch`, but its rejection is a `RebaseApiError` like everything else\n * this client throws.\n *\n * A transport failure — DNS, a refused connection, CORS, an aborted\n * request — rejects with whatever the runtime's `fetch` felt like: a\n * `TypeError` reading \"Failed to fetch\" in a browser, a `TypeError` with a\n * `cause` in undici, a `DOMException` on abort. So the one class the SDK\n * documents as \"a `catch` block only ever needs to check for this\" did not\n * cover the most common failure of all, and `e.status` / `e.code` were\n * undefined on the error every app hits first: the server being down.\n *\n * `status: 0` rather than a made-up 5xx. There was no response, and 0 is\n * how `XMLHttpRequest` has always spelled that; a fabricated 503 would be\n * indistinguishable from one the server actually sent. The original error\n * is on `cause`, so nothing is hidden.\n *\n * Wrapped once, at construction, because the request path reads `fetchFn`\n * twice — the first attempt and the post-refresh retry — and a wrapper\n * applied at one call site is a wrapper missing from the other.\n */\n const fetchFn: typeof globalThis.fetch = async (input, init) => {\n try {\n return await rawFetch(input, init);\n } catch (e) {\n if (e instanceof RebaseApiError) throw e;\n const url = typeof input === \"string\" ? input : String((input as Request).url ?? input);\n // No `baseUrl`, outside a browser: the URL is relative, and there\n // is no page origin for `fetch` to resolve it against. That is a\n // configuration error, not the server being unreachable — said as\n // a network failure, it sent people looking for a server that was\n // never the problem.\n if (!/^[a-z][a-z0-9+.-]*:/i.test(url) && typeof window === \"undefined\") {\n throw new RebaseClientError(\n `This client has no \\`baseUrl\\`, and outside a browser the relative URL ${url} cannot be fetched. ` +\n \"Pass `baseUrl` (e.g. \\\"http://localhost:3001\\\") to createRebaseClient().\",\n { code: \"BASE_URL_REQUIRED\", cause: e }\n );\n }\n throw new RebaseApiError(\n `Could not reach the server at ${url}: ${e instanceof Error ? e.message : String(e)}`,\n { status: 0, code: \"NETWORK_ERROR\", cause: e }\n );\n }\n };\n\n const apiPath = config.apiPath || \"/api\";\n\n // `apiPath` is appended to `baseUrl`, so a `baseUrl` that already ends in it\n // builds `/api/api/…` and every request 404s. That was documented on\n // `baseUrl` and left to be discovered at runtime — including by this\n // package's own tests, which configured it that way a dozen times. A 404 on\n // every call looks like a server that is down, not like a doubled path.\n // `storageUrlOrigin` is checked alongside it because `storage.ts` composes\n // it the same way — `${storageUrlOrigin ?? baseUrl}${apiPath}` — and its own\n // docblock carries the same \"no path\" caveat.\n for (const field of [\"baseUrl\", \"storageUrlOrigin\"] as const) {\n const value = config[field];\n if (!value || !apiPath) continue;\n const trimmed = value.replace(/\\/+$/, \"\");\n if (!trimmed.endsWith(apiPath)) continue;\n console.warn(\n `[Rebase] ${field} ${JSON.stringify(value)} already ends with the API path ` +\n `${JSON.stringify(apiPath)}, which is appended to it — requests will go to ` +\n `${trimmed}${apiPath}/… and 404. Pass the origin only ` +\n `(${JSON.stringify(trimmed.slice(0, trimmed.length - apiPath.length) || \"/\")}), or set ` +\n \"`apiPath` if the server really does mount the API one level deeper.\"\n );\n }\n let token = config.token;\n let tokenGetter: (() => Promise<string | null>) | undefined;\n let tokenRefresher: TokenRefresher | undefined;\n let onUnauthorizedHandler = config.onUnauthorized;\n /** Once per client, never per request — log spam is its own bug. */\n let anonymousWarningIssued = false;\n\n /**\n * Warn a server-side caller that it built a client that can only ever be\n * anonymous. Deliberately checked at the *first request* rather than at\n * construction: `setToken()` / `setAuthTokenGetter()` and a server-side\n * `auth.signIn…()` (which calls `transport.setToken`) all land after the\n * constructor, and warning at construction would fire on every one of them.\n */\n function warnIfAnonymousServerClient(activeToken: string | undefined): void {\n if (anonymousWarningIssued) return;\n if (activeToken) return; // a credential is being sent\n if (tokenGetter) return; // a credential is being fetched per request\n if (config.anonymous) return; // \"yes, I meant this\"\n if (environment?.credentialOutOfBand) return; // cookie auth flow — credential is not a header\n if (!isServerLikeEnvironment()) return; // browsers are legitimately anonymous\n anonymousWarningIssued = true;\n console.warn(ANONYMOUS_SERVER_CLIENT_WARNING);\n }\n\n // Built once, and applied *under* `Authorization`: a default header set\n // that could overwrite the token would turn a caller's convenience into an\n // authentication bug.\n const defaultHeaders: Record<string, string> = {\n ...(config.headers ?? {}),\n ...(config.schemaVersion ? { [SCHEMA_VERSION_HEADER]: config.schemaVersion } : {}),\n ...(config.impersonate !== undefined ? { [IMPERSONATE_HEADER]: config.impersonate } : {})\n };\n\n function getHeaders(activeToken: string | undefined, init?: RequestInit): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...(activeToken ? { Authorization: `Bearer ${activeToken}` } : {}),\n ...((init?.headers as Record<string, string>) || {})\n };\n // A FormData body needs the multipart Content-Type that fetch writes\n // itself, boundary included; any other value leaves the server unable\n // to find the parts. Here rather than at a call site, because the\n // request path builds headers twice — the first attempt and the\n // post-refresh retry — and the retry was the one without it: an upload\n // whose token had expired came back \"No file provided\".\n if (init?.body instanceof FormData) delete headers[\"Content-Type\"];\n return headers;\n }\n\n /**\n * The refusal for a success status carrying a body this client cannot read.\n *\n * The first 120 characters go in the message because they identify the\n * sender at a glance: `<!doctype html>` says \"you are talking to a web\n * server, not to this API\" faster than any wording here could.\n *\n * One function for both the first attempt and the post-refresh retry — the\n * retry is a second copy of this whole response-reading path, and copies\n * are how one of them ends up fixed and the other not.\n */\n function unreadableResponse(status: number, text: string, contentType: string | null): RebaseApiError {\n const type = contentType?.split(\";\")[0].trim().toLowerCase() || \"\";\n // A body that says what it is — CSV, plain text, a PDF — was sent on\n // purpose, by a custom function or route; blaming the API URL for it\n // points the developer at a misconfiguration that does not exist.\n // Only HTML (or no type at all) is the signature of the wrong host.\n const deliberate = type !== \"\" && type !== \"text/html\" && !type.includes(\"json\");\n const cause = deliberate\n ? `The body is ${type}. If this is a custom function that answers text or a file, ask for it ` +\n \"with `functions.invoke(name, payload, { responseType: \\\"text\\\" | \\\"blob\\\" | \\\"response\\\" })`.\"\n : \"This usually means the request reached something other than the Rebase API — a single-page-app \" +\n \"fallback serving index.html, or a proxy error page — so check the API URL configuration \" +\n \"(e.g. VITE_API_URL).\";\n return new RebaseApiError(\n `The server answered ${status} with a body that is not JSON, so there is nothing to return. ` +\n `${cause} The body began: ${JSON.stringify(text.slice(0, 120))}`,\n { status, code: \"INVALID_JSON_RESPONSE\", ...(type ? { details: { contentType: type } } : {}) }\n );\n }\n\n /**\n * The server always emits the canonical\n * `{ error: { message, code, details?, requestId? } }` envelope (formatted\n * by the central `errorHandler`), so a field is read strictly from\n * `body.error.*` and never from the top level.\n */\n function getErrorField(obj: Record<string, unknown>, field: string): unknown {\n const err = obj?.error;\n if (err && typeof err === \"object\" && err !== null) {\n return (err as Record<string, unknown>)[field];\n }\n return undefined;\n }\n\n /**\n * `Retry-After` as whole seconds, or `undefined`.\n *\n * The header is either a number of seconds or an HTTP date; both spellings\n * are legal and servers use both, so both are read. A date in the past —\n * which happens on a clock skew — is clamped to 0 rather than negative.\n */\n function parseRetryAfter(header: string | null): number | undefined {\n if (!header) return undefined;\n const seconds = Number(header.trim());\n if (Number.isFinite(seconds)) return Math.max(0, Math.floor(seconds));\n const at = Date.parse(header);\n if (Number.isNaN(at)) return undefined;\n return Math.max(0, Math.round((at - Date.now()) / 1000));\n }\n\n /**\n * Everything a failed response says about itself, in one place.\n *\n * The request path reads a response twice — the first attempt and the\n * post-refresh retry — and the two used to build their error separately.\n * Which is how they came to disagree: both dropped `requestId` and\n * `Retry-After`, and a fix applied to one would have missed the other.\n */\n function errorFrom(\n res: { status: number; statusText: string; headers?: { get(name: string): string | null } },\n body: Record<string, unknown>,\n fallbackMessage: string\n ): RebaseApiError {\n const requestId = getErrorField(body, \"requestId\")\n ?? res.headers?.get(\"X-Request-ID\")\n ?? undefined;\n return new RebaseApiError(\n String(getErrorField(body, \"message\") || fallbackMessage || `Request failed with status ${res.status}`),\n {\n status: res.status,\n code: (getErrorField(body, \"code\") as string | undefined) || defaultCodeForStatus(res.status),\n details: getErrorField(body, \"details\"),\n ...(typeof requestId === \"string\" && requestId ? { requestId } : {}),\n ...(() => {\n const retryAfter = parseRetryAfter(res.headers?.get(\"Retry-After\") ?? null);\n return retryAfter === undefined ? {} : { retryAfterSeconds: retryAfter };\n })()\n }\n );\n }\n\n /**\n * The token a request should carry: the getter's when one is installed,\n * otherwise the one held — or, when the token refresher says the held one\n * is spent, the one it hands back once its refresh lands.\n */\n async function tokenForRequest(): Promise<string | undefined> {\n if (tokenGetter) {\n try {\n const fetched = await tokenGetter();\n if (fetched !== null && fetched !== undefined) return fetched;\n } catch (e) {\n // Ignore error, fallback to static token if any\n }\n return token;\n }\n const pending = tokenRefresher?.();\n if (pending) {\n try {\n const fresh = await pending;\n if (fresh) return fresh;\n } catch (e) {\n // The refresher answers its own failures with a token; a\n // throw is a bug in it, and the held token is still the\n // best thing to send.\n }\n }\n return token;\n }\n\n /** The body as JSON, `{}` for none, `undefined` when it is there and is not JSON. */\n function parseBody(text: string): Record<string, unknown> | undefined {\n if (!text) return {};\n try {\n return parseRebaseJson<Record<string, unknown>>(text);\n } catch (e) {\n return undefined;\n }\n }\n\n /**\n * Send a request and hand back the response that answers it: the token\n * attached, a token refusal answered by one refresh and resend, and an\n * error status thrown as a {@link RebaseApiError}. The body of a success is\n * left unread, for the caller to read in whatever form it expects.\n *\n * One path for the first attempt and the post-refresh retry: the two used\n * to be separate copies of this whole sequence, and copies are how one of\n * them ends up fixed and the other not.\n */\n async function requestRaw(path: string, init?: RequestInit, meta?: ResponseMeta): Promise<Response> {\n const url = resolveBaseUrl(config.baseUrl) + apiPath + path;\n\n const activeToken = await tokenForRequest();\n\n warnIfAnonymousServerClient(activeToken);\n\n let res = await fetchFn(url, { ...init,\nheaders: getHeaders(activeToken, init) });\n // A body read to decide on a refresh is spent; it is kept here so the\n // error built from the same response can still quote it.\n let readText: string | undefined;\n\n if (res.status === 401 && onUnauthorizedHandler) {\n readText = await res.text().catch(() => \"\");\n if (isTokenRefusal(parseBody(readText) ?? {})) {\n const retried = await onUnauthorizedHandler();\n if (retried) {\n let retryToken = token;\n if (tokenGetter) {\n try {\n const fetched = await tokenGetter();\n if (fetched !== null && fetched !== undefined) {\n retryToken = fetched;\n }\n } catch (e) { /* ignore */ }\n }\n res = await fetchFn(url, { ...init,\nheaders: getHeaders(retryToken, init) });\n readText = undefined;\n }\n }\n }\n\n // The response the caller gets is the one whose metadata describes\n // what they are holding — after a retry, recording the 401's would hand\n // back the ETag of an error page.\n if (meta) {\n meta.status = res.status;\n meta.etag = res.headers?.get?.(\"ETag\") ?? undefined;\n meta.headers = res.headers;\n }\n\n if (!res.ok) {\n const text = readText ?? await res.text().catch(() => \"\");\n let fallbackMessage = res.statusText;\n if (res.status === 404 && !fallbackMessage) {\n const method = init?.method || \"GET\";\n fallbackMessage = `Endpoint not found (${method} ${path}). This usually means the collection is not registered on the backend, or the frontend API URL configuration (e.g. VITE_API_URL) is missing or pointing to the wrong host.`;\n }\n // On an error status an unreadable body does not matter — the\n // status is the answer and the message falls back to `statusText`.\n throw errorFrom(res, parseBody(text) ?? {}, fallbackMessage);\n }\n\n return res;\n }\n\n async function request<T = unknown>(path: string, init?: RequestInit, meta?: ResponseMeta): Promise<T> {\n const res = await requestRaw(path, init, meta);\n\n if (res.status === 204) return undefined as T; // SAFETY: HTTP 204 No Content has no body\n\n const text = await res.text().catch(() => \"\");\n /**\n * `undefined` when the body was there and could not be read as JSON.\n *\n * On a *success* status that is the whole answer, and `{}` was being\n * returned as though the server had sent it: `find()` answered `{}`\n * instead of an array, `getOne()` an empty object, with nothing thrown.\n *\n * The case that produces it is not exotic. Point `VITE_API_URL` at the\n * frontend's own host and `/api/data/posts` lands on the SPA fallback,\n * which answers `200` with `index.html` — so the misconfiguration the\n * 404 message spends four lines explaining reaches the caller, in its\n * most common form, as an empty success.\n */\n const body = parseBody(text);\n if (body === undefined) {\n throw unreadableResponse(res.status, text, res.headers?.get?.(\"Content-Type\") ?? null);\n }\n\n return body as T;\n }\n\n return {\n request,\n requestRaw,\n setToken(newToken: string | null) { token = newToken || undefined; },\n setAuthTokenGetter(getter: () => Promise<string | null>) { tokenGetter = getter; },\n setOnUnauthorized(handler: () => Promise<boolean>) { onUnauthorizedHandler = handler; },\n setTokenRefresher(refresher: TokenRefresher | null) { tokenRefresher = refresher ?? undefined; },\n get baseUrl() { return resolveBaseUrl(config.baseUrl); },\n get apiPath() { return apiPath; },\n get storageUrlOrigin() { return config.storageUrlOrigin?.replace(/\\/$/, \"\") || undefined; },\n get fetchFn() { return fetchFn; },\n getHeaders: (init?: RequestInit) => getHeaders(token, init),\n resolveToken: async () => (await tokenForRequest()) || null\n };\n}\n","import {\n FindAllParams,\n FindParams,\n FindResult,\n IterateParams,\n LogicalCondition,\n OrderByTuple,\n PageWalkOptions,\n RelationAggregateSort,\n SDKCollectionClient,\n SDKQueryBuilderInterface,\n WhereFilterOp,\n WhereValueFor,\n sortKeyToString,\n type AggregateParams,\n type AggregateResult,\n type ComputedSortField,\n type FieldPath,\n type IncludeSpec,\n type NonColumnFieldPath,\n type NullsPlacement\n} from \"@rebasepro/types\";\nimport { mergeIncludeSpecs, normalizeOrderBy } from \"@rebasepro/common\";\n\n/**\n * SDK Query Builder — returns flat rows (`FindResult<M>`) instead of\n * Entity-wrapped results (`FindResponse<M>`).\n *\n * @example\n * const { data } = await rebase.data.posts\n * .where(\"status\", \"==\", \"published\")\n * .orderBy(\"created_at\", \"desc\")\n * .limit(10)\n * .find();\n *\n * console.log(data[0].title); // flat access\n */\nexport class SDKQueryBuilder<M extends Record<string, unknown> = Record<string, unknown>> implements SDKQueryBuilderInterface<M> {\n // The accumulator stays keyed by plain `string`: it is written through\n // `where()` / `orderBy()` below, whose own parameters are typed against `M`,\n // and a `Partial<Record<FieldPath<M>, …>>` is read-only under a generic `M`\n // (TS2862) so it cannot be built up in place. The typing users see is on the\n // methods; this field is the buffer behind them, cast once on the way out.\n private params: FindParams = { where: {} };\n\n constructor(private collection: SDKCollectionClient<M>) {}\n\n /**\n * Add a filter condition to your query.\n * @example\n * client.data.users.where('age', '>=', 18).find()\n */\n where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;\n /** A relation path (`author.name`) or a JSON path (`metadata->>tier`). */\n where(column: NonColumnFieldPath, operator: WhereFilterOp, value: unknown): this;\n where(logicalCondition: LogicalCondition): this;\n where(columnOrCondition: string | LogicalCondition, operator?: WhereFilterOp, value?: unknown): this {\n if (typeof columnOrCondition === \"object\" && columnOrCondition !== null && \"type\" in columnOrCondition) {\n // A second group **narrows** rather than replaces. Every other\n // `.where()` on this builder adds a condition, and `find()`'s\n // `where`/`logical`/`search` are AND-ed with each other — so a\n // second `.where(or(…))` silently discarding the first was the one\n // call on the chain that widened the result set instead. An `or`\n // that is dropped returns rows the caller filtered out.\n const next = columnOrCondition as LogicalCondition;\n this.params.logical = this.params.logical\n ? { type: \"and\", conditions: [this.params.logical, next] }\n : next;\n return this;\n }\n\n if (!this.params.where) {\n this.params.where = {};\n }\n\n const column = columnOrCondition as string;\n const condition: [WhereFilterOp, unknown] = [operator!, value];\n const existing = this.params.where[column];\n\n if (existing === undefined) {\n this.params.where[column] = condition;\n } else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {\n (this.params.where[column] as [WhereFilterOp, unknown][]).push(condition);\n } else {\n let firstCondition: [WhereFilterOp, unknown];\n if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === \"string\") {\n firstCondition = existing as [WhereFilterOp, unknown];\n } else {\n firstCondition = [\"==\", existing];\n }\n this.params.where[column] = [firstCondition, condition];\n }\n\n return this;\n }\n\n /**\n * Order the results by a specific column.\n *\n * Call it again to add a tie-breaker rather than replace the sort: keys\n * apply in the order they were added, so\n * `.orderBy(\"roles\").orderBy(\"created_at\", \"desc\")` sorts by role and\n * shows the newest first within each one.\n */\n orderBy(\n column: FieldPath<M> | ComputedSortField | RelationAggregateSort,\n direction: \"asc\" | \"desc\" = \"asc\",\n /**\n * Where this key's NULLs go. Omitted means the direction's own\n * convention — last ascending, first descending — which is what put\n * every undated row at the top of a `.orderBy(\"published_at\", \"desc\")`\n * list, ahead of everything real.\n */\n nulls?: NullsPlacement\n ): this {\n const existing = normalizeOrderBy(this.params.orderBy) ?? [];\n // `sortKeyToString` encodes a relation aggregate — `count(applications)`\n // — which is the single-string form every layer below here speaks.\n const key = sortKeyToString(column);\n this.params.orderBy = [...existing, (nulls\n ? [key, direction, nulls]\n : [key, direction]) as OrderByTuple];\n return this;\n }\n\n /**\n * Limit the number of results returned.\n */\n limit(count: number): this {\n this.params.limit = count;\n return this;\n }\n\n /**\n * Skip the first N results.\n */\n offset(count: number): this {\n this.params.offset = count;\n return this;\n }\n\n /**\n * Set a free-text search string if supported by the backend.\n *\n * By default this is a substring match across the collection's top-level\n * string properties. A Postgres collection that declares a `search` block\n * gets ranked full-text matching over the fields it named instead, and each\n * row comes back with a `_score` you can sort on:\n *\n * ```ts\n * client.data.talents.search(\"auditor iso 14001\").orderBy(\"_score\", \"desc\").find()\n * ```\n *\n * Pass `{ explain: true }` to have each row report which of the declared\n * fields matched, with a highlighted snippet, on `_matches`:\n *\n * ```ts\n * const { data } = await client.data.talents.search(\"iso 14001\", { explain: true }).find();\n * data[0]._matches\n * // [{ field: \"questionnaire.certifications\", snippet: \"<mark>ISO</mark> <mark>14001</mark> Lead Auditor\" }]\n * ```\n */\n search(searchString: string, options?: { explain?: boolean }): this {\n this.params.searchString = searchString;\n if (options?.explain !== undefined) this.params.searchExplain = options.explain;\n return this;\n }\n\n /**\n * Order rows by nearest-neighbour distance to `vector`.\n *\n * The server has supported this from the REST layer since vectors landed;\n * this is the SDK reaching it. Results come back closest-first with a\n * `_distance` on each row, and any `where` / `orderBy` on the same query is\n * a filter applied before the ordering — distance decides the order.\n *\n * You supply the query vector. Rebase stores and searches embeddings; it\n * does not produce them, so this is where whatever model you already use\n * for the stored vectors gets called.\n *\n * @param property - Name of the `vector` property to compare against.\n * @param vector - The query embedding. Its length must match the property's\n * declared `dimensions`, or the server answers 400.\n * @example\n * client.data.docs.vectorSearch(\"embedding\", queryVector, { threshold: 0.35 }).limit(10).find()\n */\n vectorSearch(\n property: string,\n vector: number[],\n options?: { distance?: \"cosine\" | \"l2\" | \"inner_product\"; threshold?: number }\n ): this {\n this.params.vectorSearch = {\n property,\n vector,\n ...(options?.distance !== undefined && { distance: options.distance }),\n ...(options?.threshold !== undefined && { threshold: options.threshold })\n };\n return this;\n }\n\n /**\n * Load related rows into the response, in place of their foreign keys.\n *\n * Three spellings, all the same request:\n *\n * ```ts\n * client.data.posts.include(\"tags\", \"author\") // names\n * client.data.posts.include(\"comments.author\") // a dotted path\n * client.data.posts.include({ // parametrised\n * comments: {\n * limit: 5,\n * where: { published: [\"==\", true] },\n * orderBy: [\"created_at\", \"desc\"],\n * include: { author: true }\n * }\n * })\n * ```\n *\n * Up to three hops deep, and `\"*\"` still loads every relation one hop\n * deep. A name that is not a relation of the collection is a 400\n * `UNKNOWN_RELATION` — it used to be ignored, which answers 200 with the\n * field missing and reads exactly like a row that has no related row.\n *\n * Repeated calls **merge**: `.include(\"author\").include(\"tags\")` asks for\n * both. Assigning here — which is what it used to do — meant the second\n * call silently discarded the first.\n */\n include(...relations: (string | IncludeSpec)[]): this {\n this.params.include = mergeIncludeSpecs(this.params.include, relations);\n return this;\n }\n\n /**\n * Return only these columns.\n *\n * A projection at the database, not a trim of the response: a query that\n * needs two fields of a wide row reads two columns. The primary key always\n * comes back regardless — a row that cannot be addressed cannot be\n * updated, deleted or paged past — and `excludeFromApi` columns stay\n * hidden whether or not they are named.\n */\n fields(...columns: (FieldPath<M> | string)[]): this {\n this.params.fields = [...(this.params.fields ?? []), ...columns as string[]];\n return this;\n }\n\n /**\n * Collapse rows that are identical over the columns being returned.\n *\n * Only meaningful alongside {@link fields}: the primary key is always in\n * the projection, so without narrowing it every row is already distinct.\n *\n * ```ts\n * client.data.posts.fields(\"status\").distinct().find() // the statuses in use\n * ```\n */\n distinct(enabled = true): this {\n this.params.distinct = enabled;\n return this;\n }\n\n /**\n * Continue after a previous page's `meta.nextCursor` — keyset paging.\n *\n * Unlike `offset`, a row inserted or deleted before the cursor cannot\n * shift the window, so a walk neither repeats nor skips rows. Keep\n * `orderBy` identical across pages: a cursor only continues the listing it\n * came from, and one used against a different sort is refused rather than\n * seeked in an order nobody asked for.\n */\n after(cursor: string): this {\n this.params.after = cursor;\n return this;\n }\n\n /**\n * Execute the find query and return the results as flat rows.\n */\n async find(): Promise<FindResult<M>> {\n return this.collection.find(this.params as FindParams<M>);\n }\n\n /**\n * Aggregate the rows this query matches instead of returning them.\n *\n * The builder's `where`/`logical`/`search` narrow which rows are\n * aggregated; its `orderBy`, `include` and window do not apply — an\n * aggregate has no rows to sort, no relations to load and no page to\n * continue.\n *\n * ```ts\n * await client.data.orders\n * .where(\"created_at\", \">=\", startOfMonth)\n * .aggregate({ select: [{ fn: \"sum\", field: \"total\" }], groupBy: [\"status\"] });\n * // [{ status: \"paid\", sum_total: 41822.5 }, …]\n * ```\n */\n async aggregate(\n params: Omit<AggregateParams<M>, \"where\" | \"logical\" | \"searchString\">\n ): Promise<AggregateResult> {\n return this.collection.aggregate({\n ...params,\n where: this.params.where as AggregateParams<M>[\"where\"],\n logical: this.params.logical as AggregateParams<M>[\"logical\"],\n searchString: this.params.searchString\n });\n }\n\n /**\n * Page through everything this query matches, one row at a time.\n *\n * `.limit()` on the builder becomes the page size, so the ceiling on a\n * single `find()` is not a ceiling on what the query can read.\n */\n iterate(options?: PageWalkOptions<M>): AsyncIterableIterator<M> {\n return this.collection.iterate({\n ...(this.params as FindParams<M>),\n ...(this.params.limit !== undefined && { pageSize: this.params.limit }),\n ...options\n } as IterateParams<M>);\n }\n\n /** Collect everything this query matches into one array. */\n findAll(options?: PageWalkOptions<M> & { maxRows?: number }): Promise<M[]> {\n return this.collection.findAll({\n ...(this.params as FindParams<M>),\n ...(this.params.limit !== undefined && { pageSize: this.params.limit }),\n ...options\n } as FindAllParams<M>);\n }\n\n /**\n * Count the records matching this query.\n */\n async count(): Promise<number> {\n return this.collection.count(this.params as FindParams<M>);\n }\n\n /**\n * Listen to realtime updates matching this query.\n *\n * The guard that used to stand here — `if (!this.collection.listen) throw`\n * — has moved onto the client itself, which is where the same failure had\n * to be diagnosed anyway: `client.data.posts.listen(…)`, one call short of\n * this builder, answered `undefined is not a function`. The message is now\n * the same either way.\n */\n listen(onUpdate: (data: FindResult<M>) => void, onError?: (error: Error) => void): () => void {\n return this.collection.listen(this.params as FindParams<M>, onUpdate, onError);\n }\n}\n","import {\n assertNoUndefinedFilterValues,\n buildAggregateQueryString,\n buildQueryBody,\n buildQueryString,\n FindParams,\n RebaseApiError,\n Transport,\n type ResponseMeta,\n type SoftDeletedRows\n} from \"./transport\";\nimport { RebaseWebSocketClient } from \"./websocket\";\nimport {\n FindAllParams,\n FindResult,\n IterateParams,\n LogicalCondition,\n SDKCollectionClient,\n SDKQueryBuilderInterface,\n WhereFilterOp,\n WhereValueFor,\n WriteOptions,\n type AggregateMeta,\n type AggregateParams,\n type AggregateResult,\n type AggregateRow,\n type ByIdReadOptions,\n type CollectionUpdateMeta,\n type IncludeSpec,\n type NullsPlacement,\n type UpdateValues,\n type UpsertOptions,\n isUnsupported,\n unsupportedMethod,\n type ComputedSortField,\n type FieldPath,\n type RelationAggregateSort,\n type RestoreOptions,\n type TrashClient,\n RebaseClientError\n} from \"@rebasepro/types\";\nimport { collectAllPages, etagOf, normalizeOrderBy, paginateFind, resolveFindWindow, withETag } from \"@rebasepro/common\";\n\nimport { SDKQueryBuilder } from \"./sdk_query_builder\";\n\n/**\n * What `listen`/`listenById` say on a client that has no socket.\n *\n * One sentence, in one place: it used to live in `SDKQueryBuilder.listen` only,\n * so the same failure reached callers of `client.data.posts.listen` as\n * `undefined is not a function`.\n */\nconst NO_SOCKET =\n \"Listen is only available when RebaseClient is configured with a websocketUrl, \"\n + \"and not when it was created with realtime: false.\";\n\n/**\n * Why a live query cannot be opened for these parameters, or `undefined` when\n * it can.\n *\n * A `listen()` is the same query as the `find()` beside it, so what `find()`\n * refuses it refuses too. The socket would not: a frame is JSON, where an\n * `undefined` filter value becomes `null`, so `[\"!=\", uid]` with `uid` unset\n * subscribed to every row whose column is NOT NULL — an ownership filter\n * widened instead of refused. And `after` names a place in one run of the\n * query, which a subscription re-runs on every write; the server keeps no\n * cursor for one, so it was dropped, and page one streamed to a caller who had\n * asked for page two.\n */\nfunction listenRefusal(params: FindParams | undefined): RebaseClientError | undefined {\n if (params?.where) {\n try {\n assertNoUndefinedFilterValues(params.where);\n } catch (error) {\n if (error instanceof RebaseClientError) return error;\n throw error;\n }\n }\n if (params?.after) {\n return new RebaseClientError(\n \"listen() cannot continue from a cursor: a subscription re-runs its query on every write, and \"\n + \"`after` names a place in one run of it. Use `offset` or `page` for a live window, or \"\n + \"`find({ after })` to read the page once.\",\n { code: \"CURSOR_NOT_LIVE\" }\n );\n }\n return undefined;\n}\n\n/**\n * Counts currently in flight, **per client**, keyed by the exact request they\n * issue. Entries live only for the duration of the request — see `count()` for\n * why they exist at all.\n *\n * The map used to be module-level, shared by every client in the process. A\n * request key is a path and a query string: it says nothing about *who* is\n * asking. So two clients holding different credentials — an admin panel beside\n * a signed-in user's client, a per-request server client in a Node process, a\n * test asserting one client's calls — counting the same collection would share\n * one request, and whichever arrived second was answered with the first one's\n * total. Row-level security makes those genuinely different numbers.\n *\n * Keyed on the transport instead, which is the object a credential lives on.\n * A `WeakMap`, so a closed client's entry goes with it.\n */\nconst inflightCounts = new WeakMap<Transport, Map<string, Promise<number>>>();\n\n/** The in-flight map for one transport, created on first use. */\nfunction inflightCountsFor(transport: Transport): Map<string, Promise<number>> {\n let map = inflightCounts.get(transport);\n if (!map) {\n map = new Map<string, Promise<number>>();\n inflightCounts.set(transport, map);\n }\n return map;\n}\n\n/**\n * Transports whose server reads `?count=none` as a filter on a column named\n * `count`.\n *\n * Every server released before the parameter existed does, and answers a 400\n * `UNKNOWN_FILTER_FIELD` naming `count`. Skipping the count only ever saves\n * the server work, so a read refused for it is sent again without it, and\n * later reads through the same transport (one per client, so one server) stop\n * sending it. Membership is never cleared: a server does not lose the\n * parameter, and one that gains it in an upgrade gets it again on the next\n * page load.\n */\nconst serversWithoutCountParam = new WeakSet<Transport>();\n\n/**\n * The longest URL a `find()` sends as a `GET`. Past it the parameters go in\n * the body of `POST /data/<collection>/query` instead.\n *\n * Not the server's own ceiling but the one in front of it: nginx refuses a\n * request line over 8 KB by default (414), and a proxy or CDN is in front of\n * most deployments. 6 KB leaves the rest of the request line, and the headers\n * some proxies count against the same buffer, room.\n */\nconst MAX_FIND_URL_LENGTH = 6 * 1024;\n\n/**\n * Transports whose server has no `POST …/query` — every server before 0.24.\n *\n * Such a server answers the POST 404 (or a proxy 405), and the read is sent\n * again as the `GET` it has always been, which may well be refused for its\n * length: that refusal is the old server's answer, and the caller sees it.\n * Membership is never cleared, as for {@link serversWithoutCountParam}.\n */\nconst serversWithoutQueryRoute = new WeakSet<Transport>();\n\n/** {@link serversWithoutQueryRoute}, for `POST …/count`. */\nconst serversWithoutCountRoute = new WeakSet<Transport>();\n\n/**\n * Whether a read goes as its `POST` twin rather than `GET`: always for a\n * vector search, whose embedding is a JSON array in the URL (768 dimensions\n * are 17 KB of it, past Node's 16 KB and nginx's 8 KB), and for anything else\n * whose URL would not fit through a proxy.\n */\nfunction sendsQueryBody(\n transport: Transport,\n path: string,\n queryString: string,\n params: FindParams | undefined,\n serversWithoutRoute: WeakSet<Transport>\n): boolean {\n if (serversWithoutRoute.has(transport)) return false;\n if (params?.vectorSearch) return true;\n return `${transport.baseUrl ?? \"\"}${transport.apiPath ?? \"\"}${path}${queryString}`.length > MAX_FIND_URL_LENGTH;\n}\n\n/**\n * A read whose parameters go in the URL of `GET <path>`, or — where\n * {@link sendsQueryBody} says so — in the JSON body of its `POST` twin:\n * `POST …/query` for a listing, `POST …/count` for a count.\n *\n * Against a server without the twin (404, or a proxy's 405) the `GET` is sent\n * instead, and the transport remembers — unless the `GET` 404s as well, which\n * is the collection's answer, not the route's.\n */\n/**\n * A success whose body is not the shape the method reads: no body at all (a\n * `204`, or a `200` with nothing in it), or JSON without the page's `data`\n * list. Usually a proxy or a route that is not the Rebase API answering. It\n * used to resolve as an empty page, or the row `{}`, or throw a bare\n * `TypeError` — none of which `switch (e.code)` could tell from a real answer.\n */\nfunction unexpectedResponse(slug: string, method: string, status: number, expected: string): RebaseApiError {\n return new RebaseApiError(\n `${method} on \"${slug}\" got ${status === 204 ? \"a 204 with no body\" : `a ${status} without ${expected}`} from the server. ` +\n \"This usually means something other than the Rebase API answered — a proxy, or a route outside the API — so check the API URL configuration.\",\n { status, code: \"UNEXPECTED_RESPONSE\" }\n );\n}\n\n/**\n * A bulk call given something other than an array. A `RebaseClientError` with\n * the code the server uses for the same mistake in a request body, so one\n * `case \"INVALID_BULK_BODY\"` covers both.\n */\nexport function invalidBulkBody(message: string): RebaseClientError {\n return new RebaseClientError(message, { code: \"INVALID_BULK_BODY\" });\n}\n\nasync function readWithParams<T>(\n transport: Transport,\n path: string,\n postPath: string,\n params: FindParams | undefined,\n serversWithoutRoute: WeakSet<Transport>,\n deleted?: SoftDeletedRows\n): Promise<T> {\n const queryString = buildQueryString(params, deleted);\n const get = () => transport.request<T>(path + queryString, { method: \"GET\" });\n if (!sendsQueryBody(transport, path, queryString, params, serversWithoutRoute)) return get();\n try {\n return await transport.request<T>(postPath, {\n method: \"POST\",\n body: JSON.stringify(buildQueryBody(params, deleted))\n });\n } catch (error) {\n if (!(error instanceof RebaseApiError) || (error.status !== 404 && error.status !== 405)) throw error;\n // A server without the route, or a collection it does not\n // have: the GET tells the two apart. A 404 from it as well\n // is the collection's, and says nothing about the route.\n try {\n const answer = await get();\n serversWithoutRoute.add(transport);\n return answer;\n } catch (getError) {\n if (!(getError instanceof RebaseApiError && getError.status === 404)) {\n serversWithoutRoute.add(transport);\n }\n throw getError;\n }\n }\n}\n\nfunction isCountReadAsFilter(error: unknown): boolean {\n if (!(error instanceof RebaseApiError) || error.status !== 400 || error.code !== \"UNKNOWN_FILTER_FIELD\") {\n return false;\n }\n const details = error.details;\n return typeof details === \"object\" && details !== null && \"field\" in details && details.field === \"count\";\n}\n\n// `etagOf` lives in `@rebasepro/common` with `withETag`, because the admin's\n// entity layer reads the same tag and cannot depend on this package.\nexport { etagOf };\n\n/**\n * The headers a write's options ask for.\n *\n * One function rather than a spread at each call site: the three headers are\n * easy to add to one method and forget on the next, which is exactly how single\n * `PATCH` and `DELETE` came to send an `Idempotency-Key` the server never read\n * and `delete()` came to have no options at all.\n */\nfunction writeHeaders(options?: WriteOptions): { headers: Record<string, string> } | undefined {\n const headers: Record<string, string> = {};\n if (options?.idempotencyKey) headers[\"Idempotency-Key\"] = options.idempotencyKey;\n if (options?.ifMatch) headers[\"If-Match\"] = options.ifMatch;\n // A preference, not an instruction: the server answers `Preference-Applied`\n // when it honoured it, and the methods below read the body they get rather\n // than assuming a 204.\n if (options?.returning === false) headers[\"Prefer\"] = \"return=minimal\";\n return Object.keys(headers).length > 0 ? { headers } : undefined;\n}\n\n/**\n * A live query result: a normal {@link FindResult} plus what an interface\n * needs to decide whether to show a \"saving…\" or \"offline\" affordance over it.\n *\n * The three flags are always `false` on a client without offline support —\n * every result there came straight from the server.\n */\nexport interface LiveResult<M extends Record<string, unknown>> extends FindResult<M> {\n /** The data came from the local database, not from a completed request. */\n fromCache: boolean;\n /** At least one row here carries a write the server has not accepted yet. */\n hasPendingWrites: boolean;\n /**\n * The local database may not hold every row the server would have\n * returned, so treat this as a best effort rather than a complete answer.\n */\n partial: boolean;\n /** The most recent revalidation failure, when the last one failed. */\n error?: Error;\n}\n\n/** Snapshot metadata for a single observed row. */\nexport interface RowSnapshotMeta {\n fromCache: boolean;\n hasPendingWrites: boolean;\n}\n\nexport interface ObserveOptions {\n /**\n * Keep the subscription live off the realtime socket, so changes made by\n * other clients arrive without a refetch. On by default whenever realtime\n * is enabled on the client; pass `false` for a one-shot read that still\n * reports offline/pending metadata.\n */\n realtime?: boolean;\n}\n\n/**\n * Options for `observeById`: {@link ObserveOptions}, plus the relations to load\n * into the row. `include` reaches both of its reads — the one-shot\n * `findById` and the `listenById` subscription — so every emission carries\n * the same relations.\n */\nexport interface ObserveByIdOptions<Inc = IncludeSpec> extends ObserveOptions, ByIdReadOptions<Inc> {}\n\n/**\n * {@link TrashClient} and {@link RestoreOptions} are the transport-agnostic\n * contract in `@rebasepro/types`. On this client every call goes to the\n * server. With `offline` enabled the trash is not cached and nothing in it is\n * queued: the local database never holds a trashed row, so it cannot answer\n * for one, and a restore or a purge made offline would act on a row this\n * device cannot see. They reject with `OFFLINE` instead (see\n * `isOfflineError`). A restored row joins the local database; a purged one\n * leaves it.\n */\nexport type { RestoreOptions, TrashClient };\n\n/** What `softDelete: true` names the field a delete stamps. */\nconst DEFAULT_SOFT_DELETE_FIELD = \"deletedAt\";\n\n/**\n * The concrete, HTTP-backed implementation of the public\n * {@link SDKCollectionClient} contract — flat rows (no Entity wrapper), plus\n * fluent query-builder methods (`.where()`, `.orderBy()`, …).\n *\n * This is what `createRebaseClient().data.<collection>` returns. It is not a\n * separate API from {@link SDKCollectionClient}; it only widens it with\n * `count()` and the reactive `observe()` pair. Program against\n * {@link SDKCollectionClient} when you want a transport-agnostic type.\n */\nexport interface CollectionClient<\n M extends Record<string, unknown> = Record<string, unknown>,\n I = Partial<M>,\n U = Partial<M>,\n Inc = IncludeSpec\n> extends SDKCollectionClient<M, I, U, Inc> {\n count(params?: FindParams<M, Inc>): Promise<number>;\n\n /**\n * The collection's trash: list, count, restore and purge the rows a soft\n * delete stamped. See {@link TrashClient}.\n */\n trash: TrashClient<M, Inc>;\n\n /**\n * Subscribe to a query's results.\n *\n * This is the reactive read primitive, and the one to reach for in a UI:\n * unlike `find()` it keeps emitting. On a client with `offline` enabled it\n * is local-first — the first emission comes from the local database, with\n * no request in the way — and re-emits on every local write, every queued\n * write reaching the server, and every rollback. With realtime enabled it\n * also re-emits on changes made by other clients.\n *\n * Emissions are de-duplicated: a refresh that changes nothing does not\n * call back.\n *\n * @returns An unsubscribe function.\n */\n observe(\n params: FindParams<M, Inc> | undefined,\n onResult: (result: LiveResult<M>) => void,\n onError?: (error: Error) => void,\n options?: ObserveOptions\n ): () => void;\n\n /** {@link CollectionClient.observe} for a single row. */\n observeById(\n id: string | number,\n onResult: (row: M | undefined, meta: RowSnapshotMeta) => void,\n onError?: (error: Error) => void,\n options?: ObserveByIdOptions<Inc>\n ): () => void;\n}\n\n/**\n * The part of {@link ObserveByIdOptions} that is a read option: `include`, and\n * nothing else, so `realtime` never travels as a query parameter.\n */\nexport function byIdRead<Inc>(options: ObserveByIdOptions<Inc> | undefined): ByIdReadOptions<Inc> | undefined {\n return options?.include !== undefined ? { include: options.include } : undefined;\n}\n\n/**\n * The bulk route names the row a failure came from — \"Row 0 of 1 (id 7)\n * failed: …\" — which is what a caller needs from a 10,000-row import and noise\n * from `upsert()`, which the caller wrote as one row and which only travels as\n * a one-row batch. Drops that prefix and nothing else: the error keeps its\n * class, status, code, details and cause. `createMany` does not call this.\n */\nconst ONE_ROW_BULK_PREFIX = /^Row 0 of 1 \\(.*?\\) failed: /;\n\nfunction withoutBulkRowPrefix(error: unknown): unknown {\n if (error instanceof Error && ONE_ROW_BULK_PREFIX.test(error.message)) {\n error.message = error.message.replace(ONE_ROW_BULK_PREFIX, \"\");\n }\n return error;\n}\n\nexport function createCollectionClient<M extends Record<string, unknown> = Record<string, unknown>>(transport: Transport, slug: string, ws?: RebaseWebSocketClient): CollectionClient<M> {\n const basePath = `/data/${slug}`;\n\n /**\n * A listing — the live rows, or with `deleted` the trash's. One reader for\n * both, so the trash takes every parameter, and every fallback for an\n * older server, that `find()` does.\n */\n async function findRows(params: FindParams<M> | undefined, deleted?: SoftDeletedRows): Promise<FindResult<M>> {\n type Page = { data: Record<string, unknown>[]; meta: FindResult<M>[\"meta\"] };\n const method = deleted ? \"trash.find()\" : \"find()\";\n const read = async (p: FindParams<M> | undefined): Promise<Page> => {\n const page = await readWithParams<Page | undefined>(transport, basePath, `${basePath}/query`, p, serversWithoutQueryRoute, deleted);\n // The transport answers `undefined` for a 204 and nothing\n // else, and a read route answers every other success with 200.\n if (!page || !Array.isArray(page.data)) {\n throw unexpectedResponse(slug, method, page ? 200 : 204, \"a page with a `data` list\");\n }\n return page;\n };\n const counted = { ...params, count: undefined };\n let raw: Awaited<ReturnType<typeof read>>;\n if (params?.count !== \"none\") {\n raw = await read(params);\n } else if (serversWithoutCountParam.has(transport)) {\n raw = await read(counted);\n } else {\n try {\n raw = await read(params);\n } catch (error) {\n if (!isCountReadAsFilter(error)) throw error;\n serversWithoutCountParam.add(transport);\n raw = await read(counted);\n }\n }\n return {\n data: raw.data as M[],\n meta: raw.meta\n };\n }\n\n /** A count — of the live rows, or with `deleted` of the trash's. */\n async function countRows(params: FindParams<M> | undefined, deleted?: SoftDeletedRows): Promise<number> {\n const countParams: FindParams<M> = {\n ...params,\n limit: undefined,\n offset: undefined,\n // A count reads no relation data, so `include` can only add\n // joins — and a join that does not match drops rows, which\n // would make the total disagree with the `find()` it describes.\n // Same reasoning as limit/offset: parameters that cannot affect\n // the answer are not forwarded.\n include: undefined,\n // Whether a *page* is counted beside it; this is the count.\n count: undefined\n };\n const qs = buildQueryString(countParams, deleted);\n\n // One count per query in flight, not one per caller.\n //\n // A count is a property of the query, and every concurrent caller\n // asking the same question wants the same answer — so they can\n // share the one request. This is not a micro-optimisation: a live\n // subscription re-counts on every push (see `listen` below), and\n // `listenCollection` deliberately collapses identical queries onto\n // a single socket subscription while keeping one callback per\n // subscriber. Each push therefore woke N subscribers, and each of\n // them issued its own identical count. A table showing one relation\n // column fired one count per visible cell, on every update.\n //\n // The entry is dropped as soon as it settles, so this merges\n // concurrent calls only and never serves a cached total. The key\n // carries `?deleted=`, so a trash count never answers a live one.\n const key = basePath + \"/count\" + qs;\n const inflight = inflightCountsFor(transport);\n const existing = inflight.get(key);\n if (existing) return existing;\n\n // The verb `find()` would use for the same filters: a vector\n // search or a URL too long for a proxy goes as `POST …/count`, so\n // a count beside a `find()` that worked is not the request a\n // 431/414 refuses.\n const request = readWithParams<{ count: number }>(\n transport, `${basePath}/count`, `${basePath}/count`, countParams, serversWithoutCountRoute, deleted\n ).then((raw) => raw.count ?? 0);\n inflight.set(key, request);\n try {\n return await request;\n } finally {\n inflight.delete(key);\n }\n }\n\n const restoreField = (options?: RestoreOptions<M>): string => options?.field ?? DEFAULT_SOFT_DELETE_FIELD;\n\n const client: CollectionClient<M> = {\n find(params?: FindParams<M>): Promise<FindResult<M>> {\n return findRows(params);\n },\n\n trash: {\n find: (params?: FindParams<M>) => findRows(params, \"only\"),\n count: (params?: FindParams<M>) => countRows(params, \"only\"),\n // A restore is the update the soft-delete docs describe — the\n // field back to `null` — so it goes through `update()` and keeps\n // its headers, its verb and its answer.\n restore: (id: string | number, options?: RestoreOptions<M>) =>\n client.update(id, { [restoreField(options)]: null } as Partial<M>, options),\n restoreMany: (ids: (string | number)[], options?: RestoreOptions<M>) => {\n if (!Array.isArray(ids)) {\n return Promise.reject(invalidBulkBody(\"trash.restoreMany expects an array of ids.\"));\n }\n const data = { [restoreField(options)]: null } as Partial<M>;\n return client.updateMany(ids.map((id) => ({ id, data })), options);\n },\n async purge(id: string | number, options?: WriteOptions) {\n await transport.request<void>(`${basePath}/${encodeURIComponent(String(id))}?hard=true`, {\n method: \"DELETE\",\n ...writeHeaders(options)\n });\n },\n async purgeMany(ids: (string | number)[], options?: WriteOptions) {\n if (!Array.isArray(ids)) {\n throw invalidBulkBody(\"trash.purgeMany expects an array of ids.\");\n }\n if (ids.length === 0) return;\n await transport.request<void>(`${basePath}/bulk/delete?hard=true`, {\n method: \"POST\",\n body: JSON.stringify({ ids }),\n ...writeHeaders(options)\n });\n }\n },\n\n // The pagination engine lives in `@rebasepro/common`, shared with the\n // in-process accessor: `iterate()` has to mean the same thing whichever\n // transport the caller happens to be holding.\n iterate(params?: IterateParams<M>) {\n return paginateFind<M>((p) => client.find(p), params, slug);\n },\n\n findAll(params?: FindAllParams<M>) {\n return collectAllPages<M>((p) => client.find(p), params, slug);\n },\n\n async findById(id: string | number, options?: ByIdReadOptions) {\n try {\n const meta: ResponseMeta = {};\n const raw = await transport.request<Record<string, unknown>>(\n `${basePath}/${encodeURIComponent(String(id))}${buildQueryString({ include: options?.include })}`,\n { method: \"GET\" },\n meta\n );\n if (!raw) return undefined;\n // An empty body reads as `{}`, and no row has no columns.\n if (Object.keys(raw).length === 0) throw unexpectedResponse(slug, \"findById()\", meta.status ?? 200, \"a row\");\n return withETag(raw as M, meta.etag);\n } catch (err) {\n if (err instanceof RebaseApiError && err.status === 404) {\n return undefined;\n }\n throw err;\n }\n },\n\n async get(id: string | number) {\n const row = await client.findById(id);\n if (row === undefined) {\n // 404 rather than a bespoke code: it is the same outcome the\n // transport would have surfaced, and row-level security makes\n // \"no such row\" and \"not yours\" the same answer on purpose.\n throw new RebaseApiError(\n `No record with id ${JSON.stringify(String(id))} in \"${slug}\".`,\n { status: 404, code: \"NOT_FOUND\" }\n );\n }\n return row;\n },\n\n async create(data: Partial<M>, id?: string | number, options?: WriteOptions) {\n const body: Record<string, unknown> = { ...data };\n if (id !== undefined) {\n body.id = id;\n }\n const raw = await transport.request<Record<string, unknown>>(basePath, {\n method: \"POST\",\n body: JSON.stringify(body),\n ...writeHeaders(options)\n });\n return raw as M;\n },\n\n /**\n * Insert, or replace the row already occupying the conflict target.\n *\n * One statement server-side (`INSERT ... ON CONFLICT DO UPDATE`), so\n * unlike a `findById` followed by `create`-or-`update` it cannot lose\n * the race between the two.\n *\n * Sent as a one-row bulk write with `upsert: true`, which is what the\n * in-process accessor and the offline queue already send. It used to be\n * `POST /data/<slug>` with `?on_conflict=` only when a target was\n * named — and the route reads that POST without the parameter as the\n * plain insert a `create` is, so the documented default (\"on the\n * primary key\") never happened online: re-upserting an existing row\n * answered 409, while the same call succeeded offline and in a\n * function.\n */\n async upsert(data: Partial<M>, options?: UpsertOptions) {\n const raw = await transport.request<{ data?: Record<string, unknown>[] } | undefined>(`${basePath}/bulk`, {\n method: \"POST\",\n body: JSON.stringify({\n rows: [data],\n upsert: true,\n ...(options?.onConflict?.length ? { onConflict: options.onConflict } : {})\n }),\n ...writeHeaders(options)\n }).catch((error: unknown) => {\n throw withoutBulkRowPrefix(error);\n });\n // With `returning: false` the bulk route answers the id, not the\n // row — and `create` resolves to nothing there, so this does too.\n const row = options?.returning === false ? undefined : raw?.data?.[0];\n return row as M;\n },\n\n async createMany(\n data: Partial<M>[],\n options?: { upsert?: boolean; onConflict?: readonly string[] } & WriteOptions\n ) {\n if (!Array.isArray(data)) {\n throw invalidBulkBody(\"createMany expects an array of records.\");\n }\n if (data.length === 0) return [];\n\n const raw = await transport.request<{ data: Record<string, unknown>[] }>(`${basePath}/bulk`, {\n method: \"POST\",\n body: JSON.stringify({\n rows: data,\n ...(options?.upsert ? { upsert: true } : {}),\n ...(options?.onConflict?.length ? { onConflict: options.onConflict } : {})\n }),\n ...writeHeaders(options)\n });\n // With `returning: false` the server answers the ids rather than\n // the rows, and those are not `M`. Handing back `[]` is the honest\n // answer to \"you said you did not want them\": the alternative is a\n // typed array of objects that have one field.\n if (options?.returning === false) return [];\n return (raw.data || []) as M[];\n },\n\n /**\n * `PATCH`, the verb for a merge — `update(id, data: Partial<M>)` sends\n * only the keys the caller named and the server merges the rest.\n *\n * `PATCH` is the verb the OpenAPI spec names, so a spec-validating\n * gateway in front of the API sees the operation this sends. The\n * server also answers `PUT` on the same route, as a deprecated alias\n * marked `Deprecation: true` and left out of the spec; this client\n * never sends it.\n */\n async update(id: string | number, data: Partial<M> | UpdateValues<Partial<M>>, options?: WriteOptions) {\n const meta: ResponseMeta = {};\n const raw = await transport.request<Record<string, unknown>>(`${basePath}/${encodeURIComponent(String(id))}`, {\n method: \"PATCH\",\n body: JSON.stringify(data),\n ...writeHeaders(options)\n }, meta);\n // The version this write produced, so `etagOf(updated)` chains\n // the next conditional write without reading the row back.\n return withETag(raw as M, meta.etag);\n },\n\n async updateMany(\n updates: { id: string | number; data: Partial<M> | UpdateValues<Partial<M>> }[],\n options?: WriteOptions\n ) {\n if (!Array.isArray(updates)) {\n throw invalidBulkBody(\"updateMany expects an array of { id, data } entries.\");\n }\n if (updates.length === 0) return [];\n\n const raw = await transport.request<{ data: Record<string, unknown>[] }>(`${basePath}/bulk`, {\n method: \"PATCH\",\n body: JSON.stringify({ updates }),\n ...writeHeaders(options)\n });\n if (options?.returning === false) return [];\n return (raw.data || []) as M[];\n },\n\n async delete(id: string | number, options?: WriteOptions) {\n await transport.request<void>(`${basePath}/${encodeURIComponent(String(id))}`, {\n method: \"DELETE\",\n // The only write on this surface that took no options at all,\n // so the one mutation a retry cannot make safe on its own — a\n // replayed delete answers 404, which an offline queue reads as\n // permanent failure — was also the one that could not carry a\n // key or a precondition.\n ...writeHeaders(options)\n });\n },\n\n /**\n * `POST .../bulk/delete`, not `DELETE .../bulk`.\n *\n * The honest verb would take the ids in a DELETE body, and that is the\n * one request shape the HTTP ecosystem handles unreliably: bodies on\n * DELETE are permitted but widely dropped by proxies and CDNs, and\n * several OpenAPI generators ignore `requestBody` on a DELETE\n * operation, so a generated client would send the request without its\n * ids. A backend deployed behind arbitrary ingress cannot take that\n * bet. Same reason `:batchDelete` exists in Google's API guidelines.\n */\n async deleteMany(ids: (string | number)[], options?: WriteOptions) {\n if (!Array.isArray(ids)) {\n throw invalidBulkBody(\"deleteMany expects an array of ids.\");\n }\n if (ids.length === 0) return;\n\n await transport.request<void>(`${basePath}/bulk/delete`, {\n method: \"POST\",\n body: JSON.stringify({ ids }),\n ...writeHeaders(options)\n });\n },\n\n count(params?: FindParams<M>): Promise<number> {\n return countRows(params);\n },\n\n // Reactive reads. Without the offline layer there is no local database\n // to read from, so this is a fetch plus — when realtime is available —\n // the live subscription that keeps it current.\n observe(\n params: FindParams<M> | undefined,\n onResult: (result: LiveResult<M>) => void,\n onError?: (error: Error) => void,\n options?: ObserveOptions\n ) {\n let closed = false;\n // Two sources race into one callback: the one-shot fetch below and\n // the subscription beside it. Whichever resolves last used to win,\n // so a socket update that landed first was overwritten by the\n // fetch's older snapshot and stayed wrong until the next change.\n // `listenCollection` replays cached rows synchronously to a second\n // subscriber, so a second component observing the same query hit\n // that ordering every time.\n let liveDelivered = false;\n let signature: string | undefined;\n\n const deliver = (result: FindResult<M>, fromLive: boolean) => {\n if (closed) return;\n // Once the socket has spoken, the fetch issued alongside it is\n // older news — delivering it would move the app backwards.\n if (fromLive) liveDelivered = true;\n else if (liveDelivered) return;\n // The de-duplication `observe()` documents. Keyed on the rows\n // and the total, the same two things the offline layer keys on,\n // so both implementations mean the same thing by \"changed\".\n const next = `${result.meta?.total ?? \"\"}|${JSON.stringify(result.data)}`;\n if (signature !== undefined && next === signature) return;\n signature = next;\n onResult({ ...result, fromCache: false, hasPendingWrites: false, partial: false });\n };\n\n // `observe()` degrades to the single fetch below rather than\n // throwing, so it asks the capability question explicitly — the\n // method itself is always there now.\n const wantsLive = options?.realtime !== false && !isUnsupported(client.listen);\n\n // A query the live half would refuse is refused whole, and once.\n // Left to each half, an undefined filter value was reported twice\n // — by `find()` and again by `listen()` — and a cursor was served\n // once and then never kept current, which is not what this\n // promises.\n const refusal = wantsLive ? listenRefusal(params) : undefined;\n if (refusal) {\n queueMicrotask(() => {\n if (!closed) onError?.(refusal);\n });\n return () => {\n closed = true;\n };\n }\n\n // Subscribe first. A socket that already holds this query's rows —\n // another observer has the same subscription — replays them\n // synchronously, and then the fetch could only ever be dropped by\n // `deliver` above, after the server had run the query and its\n // count for it. Only when the socket has nothing yet does the fetch\n // go out, as the fast path to a first answer.\n const live = wantsLive\n ? client.listen(params, (result) => deliver(result, true), onError)\n : undefined;\n if (!liveDelivered) {\n client.find(params).then((result) => deliver(result, false)).catch((error) => {\n if (!closed) onError?.(error as Error);\n });\n }\n return () => {\n closed = true;\n live?.();\n };\n },\n\n observeById(\n id: string | number,\n onResult: (row: M | undefined, meta: RowSnapshotMeta) => void,\n onError?: (error: Error) => void,\n options?: ObserveByIdOptions\n ) {\n let closed = false;\n let liveDelivered = false;\n let signature: string | undefined;\n\n // Same ordering and de-duplication rules as `observe`, for one row.\n const deliver = (row: M | undefined, fromLive: boolean) => {\n if (closed) return;\n if (fromLive) liveDelivered = true;\n else if (liveDelivered) return;\n const next = row === undefined ? \"\\u0000missing\" : JSON.stringify(row);\n if (signature !== undefined && next === signature) return;\n signature = next;\n onResult(row, { fromCache: false, hasPendingWrites: false });\n };\n\n // Subscribe first, for the reason `observe` does: a replayed row\n // makes the fetch one whose answer would be dropped.\n const live = options?.realtime !== false && !isUnsupported(client.listenById)\n ? client.listenById(id, (row) => deliver(row, true), onError, byIdRead(options))\n : undefined;\n if (!liveDelivered) {\n client.findById(id, byIdRead(options)).then((row) => deliver(row, false)).catch((error) => {\n if (!closed) onError?.(error as Error);\n });\n }\n return () => {\n closed = true;\n live?.();\n };\n },\n\n // Fluent builder instantiation\n where(columnOrCondition: string | LogicalCondition, operator?: WhereFilterOp, value?: unknown) {\n const builder = new SDKQueryBuilder<M>(client);\n if (typeof columnOrCondition === \"object\") {\n return builder.where(columnOrCondition);\n }\n return builder.where(columnOrCondition as keyof M & string, operator!, value as WhereValueFor<WhereFilterOp, M[keyof M & string]>);\n },\n orderBy(\n column: FieldPath<M> | ComputedSortField | RelationAggregateSort,\n direction?: \"asc\" | \"desc\",\n nulls?: NullsPlacement\n ) {\n return new SDKQueryBuilder<M>(client).orderBy(column, direction, nulls);\n },\n limit(count: number) {\n return new SDKQueryBuilder<M>(client).limit(count);\n },\n offset(count: number) {\n return new SDKQueryBuilder<M>(client).offset(count);\n },\n search(searchString: string, options?: { explain?: boolean }) {\n return new SDKQueryBuilder<M>(client).search(searchString, options);\n },\n vectorSearch(\n property: string,\n vector: number[],\n options?: { distance?: \"cosine\" | \"l2\" | \"inner_product\"; threshold?: number }\n ) {\n return new SDKQueryBuilder<M>(client).vectorSearch(property, vector, options);\n },\n include(...relations: (string | IncludeSpec)[]) {\n return new SDKQueryBuilder<M>(client).include(...relations);\n },\n fields(...columns: (FieldPath<M> | string)[]) {\n return new SDKQueryBuilder<M>(client).fields(...columns);\n },\n distinct(enabled?: boolean) {\n return new SDKQueryBuilder<M>(client).distinct(enabled);\n },\n after(cursor: string) {\n return new SDKQueryBuilder<M>(client).after(cursor);\n },\n\n /**\n * `count`/`sum`/`avg`/`min`/`max` over the matching rows, optionally\n * grouped — `GET /<collection>/aggregate`.\n *\n * The REST route has served this since aggregates landed and the SDK\n * had no method for it, so the only way to reach it from a typed client\n * was to hand-build the URL. `findAll()` and a reduce is the thing this\n * exists to replace: wrong under a `limit`, unaffordable without one.\n */\n async aggregate(params: AggregateParams<M>): Promise<AggregateResult> {\n const qs = buildAggregateQueryString(params);\n const raw = await transport.request<{ data: AggregateRow[]; meta?: AggregateMeta }>(\n `${basePath}/aggregate${qs}`, { method: \"GET\" }\n );\n // The route pages a grouped aggregate like a listing — the list\n // default when no `limit` was sent — and says `hasMore`. Returning\n // `raw.data` alone dropped that, so a \"sum by customer\" over 300\n // customers came back as 50 with nothing to say it was cut off.\n // Non-enumerable, so the result is still just the rows to a spread\n // or a `JSON.stringify`.\n const rows: AggregateResult = raw.data || [];\n if (raw.meta) Object.defineProperty(rows, \"meta\", { value: raw.meta, enumerable: false });\n return rows;\n },\n\n // `listen`/`listenById` are part of the contract, so they are always\n // here to call. Without a socket they are stubs that say why, rather\n // than absent properties every call site had to null-check — a check\n // that was indistinguishable from \"does this transport support\n // realtime?\", which is `isUnsupported()` and is what the admin panel\n // actually asks before choosing to poll instead.\n listen: unsupportedMethod(NO_SOCKET),\n listenById: unsupportedMethod(NO_SOCKET),\n listenCount: unsupportedMethod(NO_SOCKET)\n };\n\n if (ws) {\n client.listen = (params: FindParams<M> | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void) => {\n let active = true;\n // Refused before anything opens, and through `onError` — where the\n // server's own refusals of a subscription (a vector search, a limit\n // over the ceiling) arrive — rather than thrown into the effect\n // that called this.\n const refusal = listenRefusal(params);\n if (refusal) {\n queueMicrotask(() => {\n if (active) onError?.(refusal);\n });\n return () => {\n active = false;\n };\n }\n let lastUpdateId = 0;\n // The last total a `count()` actually returned. A later count that\n // fails says nothing about how big the collection is, so it must\n // not be allowed to replace this with the length of one page.\n let lastKnownTotal: number | undefined;\n const window = resolveFindWindow(params);\n const unsub = ws.listenCollection(\n {\n path: slug,\n filter: params?.where,\n // The group used to be dropped here, so a subscription\n // filtered with `or(...)` was pushed every row instead.\n logical: params?.logical,\n limit: params?.limit,\n // `offset` used to be stringified into `startAfter`, which\n // is a cursor *row*, not a row count — so the server was\n // handed \"20\" where it expected a keyset value, and the\n // offset it does understand never arrived at all.\n offset: window.driverOffset,\n // `page` reaches the server as itself rather than being\n // resolved into an offset here, so the server resolves the\n // window once. A `page` with an `offset` never gets this\n // far: `resolveFindWindow` above refuses the pair.\n page: params?.page,\n // The three a subscription could not ask for. A `listen()`\n // is the same query as the `find()` beside it, so it takes\n // the same parameters — and until it did, the two returned\n // different row shapes for one query.\n include: params?.include,\n fields: params?.fields,\n distinct: params?.distinct,\n // The list form, so a multi-key sort reaches the socket\n // whole. Indexing `[0]`/`[1]` here read a tuple-of-tuples as\n // a field name and a direction, and a live subscription came\n // back in a different order from the same query fetched.\n orderBy: normalizeOrderBy(params?.orderBy),\n searchString: params?.searchString,\n searchExplain: params?.searchExplain,\n // Forwarded so the SERVER can refuse it. `realtimeService`\n // rejects a subscription carrying `vectorSearch` — a\n // subscription is re-run on every matching write and\n // nothing there computes distances — and the docs promise\n // that refusal. Both producers hand-list their fields and\n // both omitted this one, so the guard could not fire and\n // `.vectorSearch(…).listen()` returned an ordinary\n // `id DESC` listing with no `_distance` and no error.\n vectorSearch: params?.vectorSearch\n },\n (incomingRows: Record<string, unknown>[], frameMeta?: CollectionUpdateMeta) => {\n const currentUpdateId = ++lastUpdateId;\n // What the server pages by when the caller names no limit.\n // A hardcoded 20 here described a window the rows had not\n // come from, and any app sizing its next request off\n // `meta.limit` was told the wrong number.\n const requestedLimit = window.limit;\n const offset = window.offset;\n\n // WS client already delivers flat rows — just cast\n const rows = incomingRows as M[];\n\n const emit = (total: number, hasMore: boolean, nextCursor?: string) => {\n if (!active || currentUpdateId !== lastUpdateId) return;\n onUpdate({\n data: rows,\n meta: {\n total,\n limit: frameMeta?.limit ?? requestedLimit,\n offset: frameMeta?.offset ?? offset,\n hasMore,\n ...(nextCursor && { nextCursor })\n }\n });\n };\n\n // The frame's own metadata, when it carries any.\n //\n // Every push used to be followed by a `GET /count` from\n // here — one extra round trip per write, per subscriber,\n // forever, and a window in which the count and the rows\n // described different states of the collection. The server\n // already knows the query; it counts beside the rows now,\n // inside the same RLS-bound transaction that read them.\n if (frameMeta && frameMeta.total !== undefined) {\n lastKnownTotal = frameMeta.total;\n emit(frameMeta.total, frameMeta.hasMore, frameMeta.nextCursor);\n return;\n }\n\n // A frame whose count failed (`partial`), or a server too\n // old to send `meta` at all. A count that failed is not\n // evidence about the size of the collection, so keep the\n // last real answer.\n if (lastKnownTotal !== undefined) {\n emit(lastKnownTotal, offset + rows.length < lastKnownTotal, frameMeta?.nextCursor);\n return;\n }\n\n // Nothing to go on yet. Ask once — and only once, on the\n // first push of a subscription that has never had a total.\n //\n // A push that lands while the count is in flight bumps\n // `lastUpdateId`, and `emit` drops the stale one, so the\n // wait cannot deliver a total belonging to an older page.\n client.count(params)\n .then((total) => {\n lastKnownTotal = total;\n emit(total, offset + rows.length < total, frameMeta?.nextCursor);\n })\n .catch(() => {\n // With no count to go on, the only defensible total\n // is a lower bound: the rows on this page plus the\n // ones paged past to reach them. Reporting\n // `rows.length` claimed a collection read at offset\n // 10 held two rows.\n emit(offset + rows.length, rows.length >= requestedLimit, frameMeta?.nextCursor);\n });\n },\n onError\n );\n\n return () => {\n active = false;\n unsub();\n };\n };\n\n /**\n * The count as a subscription of its own: `countOnly` asks the server\n * to read no rows. One that predates it reads the single row\n * `limit: 1` asks for and counts as usual, and the frame's `meta`\n * answers either way. One that sends no `meta` at all is asked with\n * `count()` on each frame instead.\n */\n client.listenCount = (params: FindParams<M> | undefined, onCount: (total: number) => void, onError?: (error: Error) => void) => {\n let active = true;\n let last: number | undefined;\n const emit = (total: number) => {\n if (!active || total === last) return;\n last = total;\n onCount(total);\n };\n const unsub = ws.listenCollection(\n {\n path: slug,\n filter: params?.where,\n logical: params?.logical,\n searchString: params?.searchString,\n limit: 1,\n countOnly: true\n },\n (_rows, meta) => {\n if (typeof meta?.total === \"number\") emit(meta.total);\n // `partial`: the count failed this time; the last total stands.\n else if (!meta) client.count(params).then(emit).catch((error: unknown) => {\n if (active) onError?.(error instanceof Error ? error : new Error(String(error)));\n });\n },\n onError\n );\n return () => {\n active = false;\n unsub();\n };\n };\n\n client.listenById = (id: string | number, onUpdate: (data: M | undefined) => void, onError?: (error: Error) => void, options?: ByIdReadOptions) => {\n return ws.listenOne(\n {\n path: slug,\n id: String(id),\n // The relations every frame loads. Without them a to-many\n // relation was absent from each frame, and a form built on\n // one saved it as empty.\n ...(options?.include !== undefined ? { include: options.include } : {})\n },\n (row: Record<string, unknown> | null) => {\n if (row) {\n onUpdate(row as M);\n } else {\n onUpdate(undefined);\n }\n },\n onError\n );\n };\n }\n\n return client;\n}\n","import { RebaseApiError } from \"./transport\";\n\n/**\n * True when a read failed because there was neither network nor local data.\n *\n * The code is `OFFLINE`. It was `\"offline\"`, the one lowercase code in a\n * surface whose every other code is `SCREAMING_SNAKE_CASE` — including the\n * `NETWORK_ERROR` it sits next to — so `e.code === \"OFFLINE\"`, which is what\n * anyone writes, never matched.\n */\nexport function isOfflineError(error: unknown): boolean {\n return error instanceof RebaseApiError && error.code === \"OFFLINE\";\n}\n","/**\n * Persistence backends for the SDK's offline support.\n *\n * The store is a dumb, namespaced key/value surface with two areas: a read\n * cache (normalized rows, query snapshots and sync bookkeeping) and a mutation\n * queue (local writes waiting to reach the server). All structure — per-user\n * prefixes, the `row|`/`q|`/`meta|` namespaces, mutation ordering — is owned by\n * the {@link OfflineManager}; the store only promises that a prefix listing\n * comes back in lexicographic key order, which is what makes the queue a FIFO.\n *\n * Two implementations ship with the SDK:\n * - {@link IndexedDBOfflineStore} — the browser default; survives reloads.\n * - {@link MemoryOfflineStore} — the fallback everywhere IndexedDB does not\n * exist (Node, React Native, tests); survives only the process.\n *\n * Environments with neither (React Native + AsyncStorage, Electron main, …)\n * implement this interface and pass it via `offline.store`.\n */\n\n/** A cached value plus the moment it was written, for LRU eviction. */\nexport interface OfflineCacheEntry {\n value: unknown;\n cachedAt: number;\n}\n\n/** A cache entry with its key, as returned by prefix listings. */\nexport interface OfflineCacheRecord extends OfflineCacheEntry {\n key: string;\n}\n\n/** What a mutation has to put back if the server rejects it. */\nexport interface MutationRollback {\n /**\n * The rows as they were locally *before* this mutation was applied, keyed\n * by id. A `null` value means \"the row did not exist\" — restoring it is a\n * delete, not a write.\n */\n rows: Record<string, Record<string, unknown> | null>;\n}\n\n/**\n * A local write waiting to be replayed against the server.\n *\n * `mutationId` orders the queue globally (not per collection): a create in one\n * collection may be the parent a later insert in another references, so replay\n * must preserve the order the app issued the writes in. It is lexicographically\n * time-ordered and carries a random suffix, so two browser tabs writing in the\n * same millisecond produce distinct, still-roughly-ordered ids instead of\n * silently overwriting each other's queue entry.\n */\nexport interface PendingMutation {\n /** Unique, lexicographically sortable identity — also the queue key suffix. */\n mutationId: string;\n collection: string;\n type: \"create\" | \"createMany\" | \"update\" | \"updateMany\" | \"delete\" | \"deleteMany\";\n /** Target row id for update/delete, and the (client-generated) id of an offline create. */\n id?: string | number;\n /** Target row ids for `deleteMany`. */\n ids?: (string | number)[];\n /** `{ id, data }` entries for `updateMany`. */\n updates?: { id: string | number; data: Record<string, unknown> }[];\n /**\n * True when the SDK minted this create's id itself. Only such creates may\n * cancel out against a later offline delete: a freshly generated UUID\n * cannot name a row the server already has, while a caller-supplied id\n * can — and there the delete must still replay to remove the server row.\n */\n generatedId?: boolean;\n /** The payload: a row for create/update, an array of rows for createMany. */\n data?: Record<string, unknown> | Record<string, unknown>[];\n /**\n * For an `update` that consecutive edits were coalesced into: each edit,\n * in the order the app made them. `data` is their merge, and is what is\n * sent. Kept so a refusal of the merge over one field can be told apart\n * from a refusal of the rest: the merge is split back into these and each\n * replayed on its own, so only the edit the server refuses is undone.\n */\n parts?: Record<string, unknown>[];\n upsert?: boolean;\n /**\n * The request as it was already sent once, for a write tried online\n * that failed on the network before it was queued. Nothing says whether\n * it reached the server — only the answer may have been lost — so replay\n * keeps its key. For a create, sent with the rows as queued (their minted\n * ids, which later offline writes may reference), the server either takes\n * them, or, having committed the first attempt, refuses the key as held\n * for a different request, and this request is sent again for its stored\n * answer. Replaying under a fresh key wrote the rows a second time.\n *\n * An `update` or `delete` carries only the key: its queued request is the\n * one it sent, since nothing is merged into an op that has `sent`.\n */\n sent?: {\n idempotencyKey: string;\n /** `create`: the row it was called with; `createMany`: the rows. */\n data?: Record<string, unknown> | Record<string, unknown>[];\n /** `create`: the `id` argument it was called with. */\n id?: string | number;\n /** `createMany`: the conflict target the batch was sent with. */\n onConflict?: readonly string[];\n };\n queuedAt: number;\n /**\n * The layout this entry was written in — {@link QUEUE_SCHEMA_VERSION} at\n * the time. Absent on entries from before it was stamped, which are\n * version 1. An entry from a newer layout is never replayed by an older\n * client: it waits for a client that can read it.\n */\n schemaVersion?: number;\n /** How many times replay has been attempted (diagnostics for a stuck queue). */\n attempts?: number;\n /** The last replay failure's message, when there was one. */\n lastError?: string;\n /** Local state to restore if the server rejects this mutation. */\n rollback?: MutationRollback;\n /**\n * An `update`'s precondition: the version of the row its edit was made\n * against, sent as `If-Match` on replay. Set only on a client with\n * `offline.onConflict`, and only when that version is known — see\n * `OfflineConfig.onConflict`.\n */\n ifMatch?: string;\n}\n\n/**\n * The layout of a {@link PendingMutation} this SDK writes and can replay.\n * Bump it with any change an older client would misread, so that client stops\n * at the entry instead of replaying it blindly.\n */\nexport const QUEUE_SCHEMA_VERSION = 1;\n\nexport interface OfflineStore {\n getCache(key: string): Promise<OfflineCacheEntry | undefined>;\n setCache(key: string, entry: OfflineCacheEntry): Promise<void>;\n /** Write many entries at once — one transaction where the backend has them. */\n setCacheMany(entries: { key: string; entry: OfflineCacheEntry }[]): Promise<void>;\n deleteCache(keys: string[]): Promise<void>;\n /** Every cache key starting with `prefix`, with its write time (for eviction). */\n listCache(prefix: string): Promise<{ key: string; cachedAt: number }[]>;\n /** As {@link listCache}, but with the values — the local query engine's input. */\n listCacheEntries(prefix: string): Promise<OfflineCacheRecord[]>;\n\n enqueue(key: string, mutation: PendingMutation): Promise<void>;\n dequeue(key: string): Promise<void>;\n /** Queued mutations whose key starts with `prefix`, in lexicographic key order. */\n listQueue(prefix: string): Promise<PendingMutation[]>;\n\n /** Remove every cache entry and queued mutation whose key starts with `prefix`. */\n clear(prefix: string): Promise<void>;\n}\n\n// ─── Mutation ids ────────────────────────────────────────────────────────────\n\n/**\n * Monotonic within a tab, unique across tabs, and sortable as a plain string:\n * `<ms base36, padded>-<counter>-<random>`. The padding is what keeps\n * lexicographic order equal to chronological order, and the random suffix is\n * what stops two tabs from writing the same queue key in the same millisecond\n * — which would silently drop one of the two writes.\n */\nlet mutationCounter = 0;\nexport function createMutationId(now: number = Date.now()): string {\n const time = now.toString(36).padStart(10, \"0\");\n const counter = (mutationCounter = (mutationCounter + 1) % 1_679_616).toString(36).padStart(4, \"0\");\n const random = Math.random().toString(36).slice(2, 10).padStart(8, \"0\");\n return `${time}-${counter}-${random}`;\n}\n\n// ─── Memory ──────────────────────────────────────────────────────────────────\n\n/**\n * In-memory store: the default outside the browser and the workhorse of the\n * test suite. Values are deep-copied on the way in and out so a caller\n * mutating a returned row cannot silently edit the \"persisted\" copy — the\n * IndexedDB implementation gets the same guarantee for free from structured\n * cloning, and the two must not differ in aliasing behaviour.\n */\nexport class MemoryOfflineStore implements OfflineStore {\n private cache = new Map<string, OfflineCacheEntry>();\n private queue = new Map<string, PendingMutation>();\n\n async getCache(key: string): Promise<OfflineCacheEntry | undefined> {\n const entry = this.cache.get(key);\n return entry ? structuredClone(entry) : undefined;\n }\n\n async setCache(key: string, entry: OfflineCacheEntry): Promise<void> {\n this.cache.set(key, structuredClone(entry));\n }\n\n async setCacheMany(entries: { key: string; entry: OfflineCacheEntry }[]): Promise<void> {\n for (const { key, entry } of entries) this.cache.set(key, structuredClone(entry));\n }\n\n async deleteCache(keys: string[]): Promise<void> {\n for (const key of keys) this.cache.delete(key);\n }\n\n async listCache(prefix: string): Promise<{ key: string; cachedAt: number }[]> {\n const out: { key: string; cachedAt: number }[] = [];\n for (const [key, entry] of this.cache) {\n if (key.startsWith(prefix)) out.push({ key, cachedAt: entry.cachedAt });\n }\n return out;\n }\n\n async listCacheEntries(prefix: string): Promise<OfflineCacheRecord[]> {\n const out: OfflineCacheRecord[] = [];\n for (const [key, entry] of this.cache) {\n if (key.startsWith(prefix)) out.push({ key, ...structuredClone(entry) });\n }\n out.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n return out;\n }\n\n async enqueue(key: string, mutation: PendingMutation): Promise<void> {\n this.queue.set(key, structuredClone(mutation));\n }\n\n async dequeue(key: string): Promise<void> {\n this.queue.delete(key);\n }\n\n async listQueue(prefix: string): Promise<PendingMutation[]> {\n return [...this.queue.entries()]\n .filter(([key]) => key.startsWith(prefix))\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([, mutation]) => structuredClone(mutation));\n }\n\n async clear(prefix: string): Promise<void> {\n for (const key of [...this.cache.keys()]) {\n if (key.startsWith(prefix)) this.cache.delete(key);\n }\n for (const key of [...this.queue.keys()]) {\n if (key.startsWith(prefix)) this.queue.delete(key);\n }\n }\n}\n\n// ─── IndexedDB ───────────────────────────────────────────────────────────────\n\nconst IDB_NAME = \"rebase-offline\";\n/**\n * v2 introduced the normalized row cache and string mutation ids. A v1\n * database holds whole-response blobs under keys this version cannot read and\n * queue entries ordered by a numeric `seq` this version no longer writes, so\n * the upgrade drops both stores rather than trying to translate them. Offline\n * support had not shipped in a release when v2 landed, so nothing in the wild\n * loses a queued write to this.\n */\nconst IDB_VERSION = 2;\nconst CACHE_STORE = \"cache\";\nconst QUEUE_STORE = \"queue\";\n\n/** The exclusive upper bound of an IDBKeyRange covering every key under `prefix`. */\nfunction prefixRange(prefix: string): IDBKeyRange {\n return IDBKeyRange.bound(prefix, prefix + \"\", false, false);\n}\n\nfunction requestToPromise<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error ?? new Error(\"IndexedDB request failed\"));\n });\n}\n\n/** Resolve when the whole transaction commits, not just when the last request returns. */\nfunction transactionDone(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onabort = tx.onerror = () => reject(tx.error ?? new Error(\"IndexedDB transaction failed\"));\n });\n}\n\n/**\n * IndexedDB-backed store — the browser default, so cached rows and queued\n * writes survive a reload or a browser restart. Everything lives in one\n * database with two object stores; keys are the manager's full prefixed\n * strings, so multiple users (scopes) share the database without ever\n * sharing entries.\n */\nexport class IndexedDBOfflineStore implements OfflineStore {\n private dbPromise?: Promise<IDBDatabase>;\n /**\n * Where everything goes once the database turns out to belong to a newer\n * version of the SDK — see {@link backend}.\n */\n private fallback?: MemoryOfflineStore;\n\n /**\n * The database, or the in-memory stand-in when it cannot be opened at\n * this version.\n *\n * A tab still running an older build (a stale tab, a rolled-back deploy)\n * that opens a database a newer build upgraded gets a `VersionError` —\n * and every read and every write after it failed with an opaque\n * IndexedDB rejection, so the app went down instead of degrading. It now\n * works in memory for the life of the page, says so once, and leaves the\n * newer build's data alone.\n */\n private async backend(): Promise<IDBDatabase | MemoryOfflineStore> {\n if (this.fallback) return this.fallback;\n try {\n return await this.open();\n } catch (error) {\n if ((error as { name?: string } | null)?.name !== \"VersionError\") throw error;\n if (!this.fallback) {\n console.warn(\n \"[rebase offline] The offline database on this device was written by a newer version of the \" +\n \"SDK. This page keeps its offline data in memory only, and leaves that database untouched; \" +\n \"reload to pick up the newer version.\"\n );\n this.fallback = new MemoryOfflineStore();\n }\n return this.fallback;\n }\n }\n\n private open(): Promise<IDBDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(IDB_NAME, IDB_VERSION);\n request.onupgradeneeded = (event) => {\n const db = request.result;\n // A v1 database speaks a key layout this version cannot\n // read; keeping it would surface as corrupt cache entries\n // and un-replayable mutations. Start clean instead.\n if (event.oldVersion > 0 && event.oldVersion < 2) {\n if (db.objectStoreNames.contains(CACHE_STORE)) db.deleteObjectStore(CACHE_STORE);\n if (db.objectStoreNames.contains(QUEUE_STORE)) db.deleteObjectStore(QUEUE_STORE);\n }\n if (!db.objectStoreNames.contains(CACHE_STORE)) db.createObjectStore(CACHE_STORE);\n if (!db.objectStoreNames.contains(QUEUE_STORE)) db.createObjectStore(QUEUE_STORE);\n };\n request.onsuccess = () => {\n const db = request.result;\n // Another tab asking for a newer version needs this\n // connection out of the way, or its upgrade blocks forever.\n db.onversionchange = () => {\n db.close();\n this.dbPromise = undefined;\n };\n resolve(db);\n };\n // Reset so a transient failure (private browsing quota, a\n // version race with another tab) can be retried instead of\n // poisoning every later call with the same rejection.\n request.onerror = () => {\n this.dbPromise = undefined;\n reject(request.error ?? new Error(\"Failed to open IndexedDB\"));\n };\n request.onblocked = () => {\n this.dbPromise = undefined;\n reject(new Error(\"IndexedDB upgrade blocked by another tab\"));\n };\n });\n }\n return this.dbPromise;\n }\n\n private async store(db: IDBDatabase, name: string, mode: IDBTransactionMode): Promise<IDBObjectStore> {\n return db.transaction(name, mode).objectStore(name);\n }\n\n async getCache(key: string): Promise<OfflineCacheEntry | undefined> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.getCache(key);\n const store = await this.store(db, CACHE_STORE, \"readonly\");\n const entry = await requestToPromise(store.get(key));\n return entry as OfflineCacheEntry | undefined;\n }\n\n async setCache(key: string, entry: OfflineCacheEntry): Promise<void> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.setCache(key, entry);\n const store = await this.store(db, CACHE_STORE, \"readwrite\");\n await requestToPromise(store.put(entry, key));\n }\n\n async setCacheMany(entries: { key: string; entry: OfflineCacheEntry }[]): Promise<void> {\n if (entries.length === 0) return;\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.setCacheMany(entries);\n const store = await this.store(db, CACHE_STORE, \"readwrite\");\n for (const { key, entry } of entries) store.put(entry, key);\n // One commit for the whole batch: a `find` writing 200 rows must not\n // be 200 round-trips through the transaction queue.\n await transactionDone(store.transaction);\n }\n\n async deleteCache(keys: string[]): Promise<void> {\n if (keys.length === 0) return;\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.deleteCache(keys);\n const store = await this.store(db, CACHE_STORE, \"readwrite\");\n for (const key of keys) store.delete(key);\n await transactionDone(store.transaction);\n }\n\n async listCache(prefix: string): Promise<{ key: string; cachedAt: number }[]> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.listCache(prefix);\n const store = await this.store(db, CACHE_STORE, \"readonly\");\n const [keys, entries] = await Promise.all([\n requestToPromise(store.getAllKeys(prefixRange(prefix))),\n requestToPromise(store.getAll(prefixRange(prefix)))\n ]);\n return keys.map((key, i) => ({\n key: String(key),\n cachedAt: (entries[i] as OfflineCacheEntry)?.cachedAt ?? 0\n }));\n }\n\n async listCacheEntries(prefix: string): Promise<OfflineCacheRecord[]> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.listCacheEntries(prefix);\n const store = await this.store(db, CACHE_STORE, \"readonly\");\n const [keys, entries] = await Promise.all([\n requestToPromise(store.getAllKeys(prefixRange(prefix))),\n requestToPromise(store.getAll(prefixRange(prefix)))\n ]);\n return keys.map((key, i) => {\n const entry = entries[i] as OfflineCacheEntry | undefined;\n return { key: String(key), value: entry?.value, cachedAt: entry?.cachedAt ?? 0 };\n });\n }\n\n async enqueue(key: string, mutation: PendingMutation): Promise<void> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.enqueue(key, mutation);\n const store = await this.store(db, QUEUE_STORE, \"readwrite\");\n await requestToPromise(store.put(mutation, key));\n }\n\n async dequeue(key: string): Promise<void> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.dequeue(key);\n const store = await this.store(db, QUEUE_STORE, \"readwrite\");\n await requestToPromise(store.delete(key));\n }\n\n async listQueue(prefix: string): Promise<PendingMutation[]> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.listQueue(prefix);\n const store = await this.store(db, QUEUE_STORE, \"readonly\");\n // getAll on a key range returns values in key order, which is the\n // FIFO guarantee this interface promises.\n const entries = await requestToPromise(store.getAll(prefixRange(prefix)));\n return entries as PendingMutation[];\n }\n\n async clear(prefix: string): Promise<void> {\n const db = await this.backend();\n if (db instanceof MemoryOfflineStore) return db.clear(prefix);\n const cache = await this.store(db, CACHE_STORE, \"readwrite\");\n await requestToPromise(cache.delete(prefixRange(prefix)));\n const queue = await this.store(db, QUEUE_STORE, \"readwrite\");\n await requestToPromise(queue.delete(prefixRange(prefix)));\n }\n}\n"],"mappings":"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"}
|
package/dist/offline-store.d.ts
CHANGED
|
@@ -67,6 +67,14 @@ export interface PendingMutation {
|
|
|
67
67
|
generatedId?: boolean;
|
|
68
68
|
/** The payload: a row for create/update, an array of rows for createMany. */
|
|
69
69
|
data?: Record<string, unknown> | Record<string, unknown>[];
|
|
70
|
+
/**
|
|
71
|
+
* For an `update` that consecutive edits were coalesced into: each edit,
|
|
72
|
+
* in the order the app made them. `data` is their merge, and is what is
|
|
73
|
+
* sent. Kept so a refusal of the merge over one field can be told apart
|
|
74
|
+
* from a refusal of the rest: the merge is split back into these and each
|
|
75
|
+
* replayed on its own, so only the edit the server refuses is undone.
|
|
76
|
+
*/
|
|
77
|
+
parts?: Record<string, unknown>[];
|
|
70
78
|
upsert?: boolean;
|
|
71
79
|
/**
|
|
72
80
|
* The request as it was already sent once, for a write tried online
|
|
@@ -91,13 +99,33 @@ export interface PendingMutation {
|
|
|
91
99
|
onConflict?: readonly string[];
|
|
92
100
|
};
|
|
93
101
|
queuedAt: number;
|
|
102
|
+
/**
|
|
103
|
+
* The layout this entry was written in — {@link QUEUE_SCHEMA_VERSION} at
|
|
104
|
+
* the time. Absent on entries from before it was stamped, which are
|
|
105
|
+
* version 1. An entry from a newer layout is never replayed by an older
|
|
106
|
+
* client: it waits for a client that can read it.
|
|
107
|
+
*/
|
|
108
|
+
schemaVersion?: number;
|
|
94
109
|
/** How many times replay has been attempted (diagnostics for a stuck queue). */
|
|
95
110
|
attempts?: number;
|
|
96
111
|
/** The last replay failure's message, when there was one. */
|
|
97
112
|
lastError?: string;
|
|
98
113
|
/** Local state to restore if the server rejects this mutation. */
|
|
99
114
|
rollback?: MutationRollback;
|
|
115
|
+
/**
|
|
116
|
+
* An `update`'s precondition: the version of the row its edit was made
|
|
117
|
+
* against, sent as `If-Match` on replay. Set only on a client with
|
|
118
|
+
* `offline.onConflict`, and only when that version is known — see
|
|
119
|
+
* `OfflineConfig.onConflict`.
|
|
120
|
+
*/
|
|
121
|
+
ifMatch?: string;
|
|
100
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* The layout of a {@link PendingMutation} this SDK writes and can replay.
|
|
125
|
+
* Bump it with any change an older client would misread, so that client stops
|
|
126
|
+
* at the entry instead of replaying it blindly.
|
|
127
|
+
*/
|
|
128
|
+
export declare const QUEUE_SCHEMA_VERSION = 1;
|
|
101
129
|
export interface OfflineStore {
|
|
102
130
|
getCache(key: string): Promise<OfflineCacheEntry | undefined>;
|
|
103
131
|
setCache(key: string, entry: OfflineCacheEntry): Promise<void>;
|
|
@@ -158,6 +186,23 @@ export declare class MemoryOfflineStore implements OfflineStore {
|
|
|
158
186
|
*/
|
|
159
187
|
export declare class IndexedDBOfflineStore implements OfflineStore {
|
|
160
188
|
private dbPromise?;
|
|
189
|
+
/**
|
|
190
|
+
* Where everything goes once the database turns out to belong to a newer
|
|
191
|
+
* version of the SDK — see {@link backend}.
|
|
192
|
+
*/
|
|
193
|
+
private fallback?;
|
|
194
|
+
/**
|
|
195
|
+
* The database, or the in-memory stand-in when it cannot be opened at
|
|
196
|
+
* this version.
|
|
197
|
+
*
|
|
198
|
+
* A tab still running an older build (a stale tab, a rolled-back deploy)
|
|
199
|
+
* that opens a database a newer build upgraded gets a `VersionError` —
|
|
200
|
+
* and every read and every write after it failed with an opaque
|
|
201
|
+
* IndexedDB rejection, so the app went down instead of degrading. It now
|
|
202
|
+
* works in memory for the life of the page, says so once, and leaves the
|
|
203
|
+
* newer build's data alone.
|
|
204
|
+
*/
|
|
205
|
+
private backend;
|
|
161
206
|
private open;
|
|
162
207
|
private store;
|
|
163
208
|
getCache(key: string): Promise<OfflineCacheEntry | undefined>;
|