@rebasepro/server 0.13.0 → 0.13.1-canary.g06dbe5b
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/dist/{admin_block-CcSGdH7M.js → admin_block-H86dCPsj.js} +62 -2
- package/dist/admin_block-H86dCPsj.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +74 -6
- package/dist/api/errors.d.ts +0 -6
- package/dist/api/openapi-generator.d.ts +14 -0
- package/dist/api/rest/api-generator.d.ts +1 -1
- package/dist/api/rest/idempotency.d.ts +62 -4
- package/dist/api/rest/query-parser.d.ts +15 -1
- package/dist/api/rest/write-validation.d.ts +52 -9
- package/dist/auth/adapter-middleware.d.ts +3 -1
- package/dist/auth/apple-oauth.d.ts +16 -11
- package/dist/auth/bitbucket-oauth.d.ts +2 -4
- package/dist/auth/builtin-auth-adapter.d.ts +2 -0
- package/dist/auth/discord-oauth.d.ts +5 -6
- package/dist/auth/facebook-oauth.d.ts +2 -4
- package/dist/auth/github-oauth.d.ts +2 -4
- package/dist/auth/gitlab-oauth.d.ts +6 -4
- package/dist/auth/google-oauth.d.ts +1 -0
- package/dist/auth/index.d.ts +8 -1
- package/dist/auth/interfaces.d.ts +68 -1
- package/dist/auth/jwt.d.ts +40 -0
- package/dist/auth/linkedin-oauth.d.ts +2 -4
- package/dist/auth/mfa-gate.d.ts +42 -0
- package/dist/auth/mfa-routes.d.ts +26 -1
- package/dist/auth/mfa.d.ts +16 -0
- package/dist/auth/microsoft-oauth.d.ts +19 -4
- package/dist/auth/oauth-code-flow.d.ts +66 -0
- package/dist/auth/oauth-signin-policy.d.ts +61 -0
- package/dist/auth/oidc-id-token.d.ts +61 -0
- package/dist/auth/rate-limiter.d.ts +37 -2
- package/dist/auth/rls-scope.d.ts +25 -0
- package/dist/auth/routes.d.ts +8 -0
- package/dist/auth/slack-oauth.d.ts +2 -4
- package/dist/auth/spotify-oauth.d.ts +2 -4
- package/dist/auth/twitter-oauth.d.ts +2 -5
- package/dist/{auth-CuC9M2x6.js → auth-DkAHqvf5.js} +1604 -391
- package/dist/auth-DkAHqvf5.js.map +1 -0
- package/dist/{backup-CVggVhR2.js → backup-DLluVyuA.js} +2 -2
- package/dist/{backup-CVggVhR2.js.map → backup-DLluVyuA.js.map} +1 -1
- package/dist/boot/boot.d.ts +19 -0
- package/dist/boot/ddl-bootstrap.d.ts +70 -0
- package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-BB1U05sS.js} +3 -3
- package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-BB1U05sS.js.map} +1 -1
- package/dist/cron/cron-scheduler.d.ts +8 -3
- package/dist/cron/define-cron.d.ts +17 -3
- package/dist/{cron-loader-B1S2MCSl.js → cron-loader-3U5aGILy.js} +2 -2
- package/dist/{cron-loader-B1S2MCSl.js.map → cron-loader-3U5aGILy.js.map} +1 -1
- package/dist/{cron-routes-CrQ0tK-_.js → cron-routes-rZgwlOz4.js} +2 -2
- package/dist/{cron-routes-CrQ0tK-_.js.map → cron-routes-rZgwlOz4.js.map} +1 -1
- package/dist/{cron-scheduler-B3RFt0HS.js → cron-scheduler-FJAaCAXm.js} +30 -5
- package/dist/cron-scheduler-FJAaCAXm.js.map +1 -0
- package/dist/{cron-store-BywZsyfZ.js → cron-store-9NmUDfzL.js} +66 -47
- package/dist/cron-store-9NmUDfzL.js.map +1 -0
- package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
- package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
- package/dist/email/html.d.ts +54 -0
- package/dist/email/index.d.ts +3 -0
- package/dist/email/link-base.d.ts +39 -0
- package/dist/email/smtp-email-service.d.ts +7 -1
- package/dist/email/templates.d.ts +9 -1
- package/dist/email/types.d.ts +11 -1
- package/dist/{errors-CgkCzoj7.js → errors-B1WZEdsK.js} +3 -11
- package/dist/errors-B1WZEdsK.js.map +1 -0
- package/dist/function-loader-D1SwtCa5.js +139 -0
- package/dist/function-loader-D1SwtCa5.js.map +1 -0
- package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
- package/dist/function-routes-Btcez1T-.js.map +1 -0
- package/dist/functions/define-function.d.ts +25 -6
- package/dist/functions/function-loader.d.ts +23 -0
- package/dist/functions/function-routes.d.ts +7 -1
- package/dist/functions/request-timeout.d.ts +34 -0
- package/dist/history/history-routes.d.ts +6 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.es.js +1960 -794
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +10 -1
- package/dist/init/process-safety.d.ts +25 -0
- package/dist/init.d.ts +27 -0
- package/dist/{jwt-DD6EtpGj.js → jwt-Cuq6MwNy.js} +70 -8
- package/dist/jwt-Cuq6MwNy.js.map +1 -0
- package/dist/logger-DfvF_8r-.js +190 -0
- package/dist/logger-DfvF_8r-.js.map +1 -0
- package/dist/openapi-generator-BQxxxDpb.js +865 -0
- package/dist/openapi-generator-BQxxxDpb.js.map +1 -0
- package/dist/request-timeout-RivJsME0.js +65 -0
- package/dist/request-timeout-RivJsME0.js.map +1 -0
- package/dist/schema-editor-routes-DZFKGXgq.js +437 -0
- package/dist/schema-editor-routes-DZFKGXgq.js.map +1 -0
- package/dist/serve-spa.d.ts +5 -4
- package/dist/services/outbound-url-guard.d.ts +53 -0
- package/dist/services/routed-realtime-service.d.ts +10 -2
- package/dist/services/webhook-service.d.ts +76 -1
- package/dist/singleton.d.ts +25 -8
- package/dist/{src-Cum9kox5.js → src-By48Ffg0.js} +322 -70
- package/dist/src-By48Ffg0.js.map +1 -0
- package/dist/{src-_qQ3RNCK.js → src-Ca6NhxKs.js} +79 -2
- package/dist/src-Ca6NhxKs.js.map +1 -0
- package/dist/storage/LocalStorageController.d.ts +12 -1
- package/dist/storage/image-transform.d.ts +57 -0
- package/dist/storage/keys.d.ts +98 -0
- package/dist/storage/routes.d.ts +11 -0
- package/dist/storage/tus-handler.d.ts +7 -1
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/logging.d.ts +0 -4
- package/dist/utils/sql.d.ts +11 -6
- package/package.json +7 -6
- package/dist/admin_block-CcSGdH7M.js.map +0 -1
- package/dist/auth-CuC9M2x6.js.map +0 -1
- package/dist/backend-CIxN4FVm.js +0 -15
- package/dist/backend-CIxN4FVm.js.map +0 -1
- package/dist/cron-scheduler-B3RFt0HS.js.map +0 -1
- package/dist/cron-store-BywZsyfZ.js.map +0 -1
- package/dist/errors-CgkCzoj7.js.map +0 -1
- package/dist/function-loader-B_1fYfUY.js +0 -86
- package/dist/function-loader-B_1fYfUY.js.map +0 -1
- package/dist/function-routes-C0cLIy3N.js.map +0 -1
- package/dist/jwt-DD6EtpGj.js.map +0 -1
- package/dist/logger-BYU66ENZ.js +0 -94
- package/dist/logger-BYU66ENZ.js.map +0 -1
- package/dist/openapi-generator-BEwyiaAr.js +0 -597
- package/dist/openapi-generator-BEwyiaAr.js.map +0 -1
- package/dist/schema-editor-routes-CbF20Mf1.js +0 -248
- package/dist/schema-editor-routes-CbF20Mf1.js.map +0 -1
- package/dist/src-Cum9kox5.js.map +0 -1
- package/dist/src-_qQ3RNCK.js.map +0 -1
|
@@ -2,7 +2,64 @@ import { createRequire as __createRequire } from "module";
|
|
|
2
2
|
import "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
4
|
import { r as __require, t as __commonJSMin } from "./rolldown-runtime-DSJWtz9O.js";
|
|
5
|
-
import {
|
|
5
|
+
import { a as getDeclaredSubcollections, d as NULL_OPS, f as REST_TO_CANONICAL, l as getDataSourceCapabilities, o as isPostgresCollectionConfig, p as toCanonicalOp, s as isRelationalCollectionConfig, u as CANONICAL_TO_REST } from "./src-Ca6NhxKs.js";
|
|
6
|
+
//#region ../types/src/errors.ts
|
|
7
|
+
/**
|
|
8
|
+
* The single error type thrown across the entire Rebase client surface —
|
|
9
|
+
* HTTP data/control-plane calls, realtime/WebSocket operations, and
|
|
10
|
+
* client-side logic errors (e.g. an unknown collection accessor). A `catch`
|
|
11
|
+
* block only ever needs to check for this one class:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { RebaseApiError } from "@rebasepro/client"; // re-exported
|
|
15
|
+
*
|
|
16
|
+
* try {
|
|
17
|
+
* await client.data.products.update(id, { price: 9 });
|
|
18
|
+
* } catch (e) {
|
|
19
|
+
* if (e instanceof RebaseApiError) {
|
|
20
|
+
* if (e.status === 404) { ... } // HTTP failures carry a status
|
|
21
|
+
* console.error(e.code, e.details);
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* `status` is present for HTTP failures and `undefined` otherwise, so its
|
|
27
|
+
* presence distinguishes transport-level errors from realtime/logic errors.
|
|
28
|
+
*
|
|
29
|
+
* @group Errors
|
|
30
|
+
*/
|
|
31
|
+
var RebaseApiError = class extends Error {
|
|
32
|
+
/** HTTP status code, or `undefined` for non-HTTP errors. */
|
|
33
|
+
status;
|
|
34
|
+
/** Stable machine-readable error code, when the server supplied one. See {@link RebaseErrorCode}. */
|
|
35
|
+
code;
|
|
36
|
+
/** Structured error payload from the server, when present. */
|
|
37
|
+
details;
|
|
38
|
+
constructor(message, init = {}) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "RebaseApiError";
|
|
41
|
+
this.status = init.status;
|
|
42
|
+
this.code = init.code;
|
|
43
|
+
this.details = init.details;
|
|
44
|
+
if (init.cause !== void 0) this.cause = init.cause;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Client-side logic error — raised before any request is made (e.g. accessing
|
|
49
|
+
* an unknown collection accessor when a typed dictionary is configured).
|
|
50
|
+
*
|
|
51
|
+
* A subclass of {@link RebaseApiError} (with no `status`), so a single
|
|
52
|
+
* `catch (e) { if (e instanceof RebaseApiError) ... }` handles it too.
|
|
53
|
+
*
|
|
54
|
+
* @group Errors
|
|
55
|
+
*/
|
|
56
|
+
var RebaseClientError = class extends RebaseApiError {
|
|
57
|
+
constructor(message) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = "RebaseClientError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
6
63
|
//#region ../types/src/types/entities.ts
|
|
7
64
|
/**
|
|
8
65
|
* Class used to create a reference to a entity in a different path.
|
|
@@ -129,7 +186,7 @@ var Vector = class {
|
|
|
129
186
|
//#endregion
|
|
130
187
|
//#region ../types/src/types/policy.ts
|
|
131
188
|
/**
|
|
132
|
-
* The id a request without a logged-in user reports as `
|
|
189
|
+
* The id a request without a logged-in user reports as `rebase.uid()`.
|
|
133
190
|
*
|
|
134
191
|
* A user-context request always sets `app.uid`: blank would read back as
|
|
135
192
|
* `NULL`, and `NULL` is how the trusted server context is recognised, so an
|
|
@@ -137,7 +194,7 @@ var Vector = class {
|
|
|
137
194
|
* therefore substitutes this sentinel at the single chokepoint where the GUC
|
|
138
195
|
* is set.
|
|
139
196
|
*
|
|
140
|
-
* The consequence for policy authors is that **`
|
|
197
|
+
* The consequence for policy authors is that **`rebase.uid() IS NOT NULL` is a
|
|
141
198
|
* tautology on the user path** — it is true for anonymous visitors too. Use
|
|
142
199
|
* {@link policy.authenticated} to mean "signed in", and
|
|
143
200
|
* {@link policy.serverContext} to mean "the trusted server context". Do not
|
|
@@ -154,7 +211,7 @@ var ANONYMOUS_USER_ID = "anonymous";
|
|
|
154
211
|
* JavaScript evaluator and the linter were all built on
|
|
155
212
|
* {@link ANONYMOUS_USER_ID}, while the request path scoped unauthenticated
|
|
156
213
|
* callers as `'anon'` — so `policy.authenticated()`, which compiled to
|
|
157
|
-
* `
|
|
214
|
+
* `rebase.uid() <> 'anonymous'`, was *true* for an anonymous visitor. The
|
|
158
215
|
* sanctioned way to write "signed in" granted to everyone, and the linter
|
|
159
216
|
* flagged the spelling that actually worked as a foreign convention.
|
|
160
217
|
*
|
|
@@ -235,6 +292,54 @@ var policy = {
|
|
|
235
292
|
authUid: () => ({ kind: "authUid" }),
|
|
236
293
|
authRoles: () => ({ kind: "authRoles" })
|
|
237
294
|
};
|
|
295
|
+
/** Largest `limit` a client may ask for on any surface. Above it, the read is refused. */
|
|
296
|
+
var MAX_LIST_LIMIT = 1e3;
|
|
297
|
+
/**
|
|
298
|
+
* Thrown by {@link resolveClientListLimit} for a `limit` the platform will not
|
|
299
|
+
* serve. Carries an HTTP status so an ingress that speaks HTTP can forward it
|
|
300
|
+
* verbatim, and `maxLimit` so one can be built without re-deriving the ceiling.
|
|
301
|
+
*
|
|
302
|
+
* @group Errors
|
|
303
|
+
*/
|
|
304
|
+
var ListLimitError = class ListLimitError extends RebaseApiError {
|
|
305
|
+
/** The ceiling that was exceeded — what the caller should page by instead. */
|
|
306
|
+
maxLimit;
|
|
307
|
+
constructor(message, maxLimit) {
|
|
308
|
+
super(message, {
|
|
309
|
+
status: 400,
|
|
310
|
+
code: "INVALID_LIMIT"
|
|
311
|
+
});
|
|
312
|
+
this.name = "ListLimitError";
|
|
313
|
+
this.maxLimit = maxLimit;
|
|
314
|
+
Object.setPrototypeOf(this, ListLimitError.prototype);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Resolve a client-supplied list `limit` into a safe, always-defined value.
|
|
319
|
+
*
|
|
320
|
+
* - An absent / blank limit falls back to the mode default:
|
|
321
|
+
* `vectorDefaultLimit` for a vector search, otherwise `defaultLimit`.
|
|
322
|
+
* - A limit that is present must be an integer in `[1, maxLimit]`. Anything
|
|
323
|
+
* else — `0`, a negative, `1.5`, `abc`, `100000000` — throws
|
|
324
|
+
* {@link ListLimitError} rather than being coerced into range, because every
|
|
325
|
+
* coercion answers a question the caller did not ask with a page it cannot
|
|
326
|
+
* tell apart from the whole collection.
|
|
327
|
+
*
|
|
328
|
+
* The return is never `undefined` — no ingress that routes its client limit
|
|
329
|
+
* through this can produce an unbounded read.
|
|
330
|
+
*
|
|
331
|
+
* @throws {ListLimitError} when a present `limit` is not an integer in range.
|
|
332
|
+
*/
|
|
333
|
+
function resolveClientListLimit(rawLimit, opts = {}) {
|
|
334
|
+
const maxLimit = opts.maxLimit ?? 1e3;
|
|
335
|
+
if (rawLimit != null && String(rawLimit).trim() !== "") {
|
|
336
|
+
const parsed = typeof rawLimit === "number" ? rawLimit : Number(String(rawLimit).trim());
|
|
337
|
+
if (!Number.isInteger(parsed) || parsed < 1) throw new ListLimitError(`Invalid \`limit\`: ${String(rawLimit)}. Expected a whole number between 1 and ${maxLimit}.`, maxLimit);
|
|
338
|
+
if (parsed > maxLimit) throw new ListLimitError(`\`limit\` ${parsed} is above the maximum of ${maxLimit}. Ask for at most ${maxLimit} rows per read and page through the rest with \`offset\` — answering with a smaller page would be indistinguishable from there being no more rows.`, maxLimit);
|
|
339
|
+
return parsed;
|
|
340
|
+
}
|
|
341
|
+
return opts.vectorSearch ? opts.vectorDefaultLimit ?? 10 : opts.defaultLimit ?? 50;
|
|
342
|
+
}
|
|
238
343
|
(/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
239
344
|
(function(e) {
|
|
240
345
|
var t;
|
|
@@ -1382,10 +1487,21 @@ function singularizeForKey(name) {
|
|
|
1382
1487
|
* have `id` and `path` fields — these are relation-shaped objects from
|
|
1383
1488
|
* edge cases in the data pipeline (REST fallback, stale cache, custom data source).
|
|
1384
1489
|
*
|
|
1490
|
+
* When `targetPath` is given, also accepts a bare id. A relation column is a
|
|
1491
|
+
* foreign key, and the REST layer returns it as the scalar it is; only some
|
|
1492
|
+
* fetch paths hydrate it into an object. Which form a caller sees therefore
|
|
1493
|
+
* depends on how the row was loaded, and a caller that only accepted objects
|
|
1494
|
+
* reported half of its own data as a type error. The declared target is the
|
|
1495
|
+
* missing half: with it, an id is a relation that has not been fetched yet.
|
|
1496
|
+
*
|
|
1385
1497
|
* Returns null if the value cannot be coerced.
|
|
1386
1498
|
*/
|
|
1387
|
-
function normalizeToEntityRelation(value, propertyType) {
|
|
1499
|
+
function normalizeToEntityRelation(value, propertyType, targetPath) {
|
|
1388
1500
|
if (value instanceof EntityRelation) return value;
|
|
1501
|
+
if (targetPath && (typeof value === "string" || typeof value === "number")) {
|
|
1502
|
+
if (value === "") return null;
|
|
1503
|
+
return new EntityRelation(value, targetPath);
|
|
1504
|
+
}
|
|
1389
1505
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1390
1506
|
const obj = value;
|
|
1391
1507
|
if (!(obj.__type === "relation" || obj.__type === "reference" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation() || typeof obj.isEntityReference === "function" && obj.isEntityReference() || propertyType === "relation" && typeof obj.id !== "undefined" && typeof obj.path === "string")) return null;
|
|
@@ -1756,8 +1872,14 @@ function getSubcollections(collection) {
|
|
|
1756
1872
|
* Rebase's enforcement model is unified: authenticated (user-context) requests
|
|
1757
1873
|
* run under the restricted `rebase_user` role, so Postgres RLS binds *every*
|
|
1758
1874
|
* statement — reads and writes. A collection's `securityRules` are the whole
|
|
1759
|
-
* authorization model. The server context (auth flows, migrations,
|
|
1760
|
-
* `
|
|
1875
|
+
* authorization model. The server context (auth flows, migrations, raw
|
|
1876
|
+
* `rebase.sql`) runs as the owner and bypasses RLS.
|
|
1877
|
+
*
|
|
1878
|
+
* `rebase.dataAsAdmin` is **not** in that set, despite the name: it is scoped as
|
|
1879
|
+
* `{ uid: "service", roles: ["admin"] }`, so it runs as `rebase_user` like any
|
|
1880
|
+
* other caller and clears the baseline below through the *admin* arm, not the
|
|
1881
|
+
* server arm. Which is why `disableDefaultPolicies` plus a lone
|
|
1882
|
+
* `policy.serverContext()` rule locks it out too.
|
|
1761
1883
|
*
|
|
1762
1884
|
* Because RLS default-denies, every collection is **locked by default**: with
|
|
1763
1885
|
* no rules, only the server context and admins can touch it. The generator
|
|
@@ -2883,8 +3005,24 @@ var QueryBuilder = class {
|
|
|
2883
3005
|
/**
|
|
2884
3006
|
* Set a free-text search string if supported by the backend.
|
|
2885
3007
|
*/
|
|
2886
|
-
search(searchString) {
|
|
3008
|
+
search(searchString, options) {
|
|
2887
3009
|
this.params.searchString = searchString;
|
|
3010
|
+
if (options?.explain !== void 0) this.params.searchExplain = options.explain;
|
|
3011
|
+
return this;
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
3015
|
+
*
|
|
3016
|
+
* Postgres only, over a property declared as `type: "vector"`. Rows come
|
|
3017
|
+
* back with a `_distance`; `where` filters before the ordering.
|
|
3018
|
+
*/
|
|
3019
|
+
vectorSearch(property, vector, options) {
|
|
3020
|
+
this.params.vectorSearch = {
|
|
3021
|
+
property,
|
|
3022
|
+
vector,
|
|
3023
|
+
...options?.distance !== void 0 && { distance: options.distance },
|
|
3024
|
+
...options?.threshold !== void 0 && { threshold: options.threshold }
|
|
3025
|
+
};
|
|
2888
3026
|
return this;
|
|
2889
3027
|
}
|
|
2890
3028
|
/**
|
|
@@ -2941,6 +3079,30 @@ var RebasePaginationError = class RebasePaginationError extends Error {
|
|
|
2941
3079
|
Object.setPrototypeOf(this, RebasePaginationError.prototype);
|
|
2942
3080
|
}
|
|
2943
3081
|
};
|
|
3082
|
+
/**
|
|
3083
|
+
* Resolve `limit`/`offset`/`page` into the window a read will actually use.
|
|
3084
|
+
*
|
|
3085
|
+
* Lives here, next to the walk, for the reason at the top of this file: every
|
|
3086
|
+
* transport has to mean the same thing by "page two". Four of them did not —
|
|
3087
|
+
* the REST layer strode by {@link DEFAULT_LIST_LIMIT}, the local-first
|
|
3088
|
+
* evaluator by {@link DEFAULT_PAGE_SIZE}, the in-process accessor by 20, and
|
|
3089
|
+
* the published type documented a fourth number. Pages that overlap or skip
|
|
3090
|
+
* rows are the mildest of those outcomes.
|
|
3091
|
+
*
|
|
3092
|
+
* `page` wins over `offset`, as {@link FindParams} documents. `driverOffset`
|
|
3093
|
+
* is the value to hand a driver: it stays `undefined` when the caller named no
|
|
3094
|
+
* offset, because keyset pagination seeks with a `where` clause and must not
|
|
3095
|
+
* look like it is paging by offset.
|
|
3096
|
+
*/
|
|
3097
|
+
function resolveFindWindow(params) {
|
|
3098
|
+
const limit = params?.limit ?? 50;
|
|
3099
|
+
const offset = params?.page != null ? Math.max(0, (params.page - 1) * limit) : params?.offset ?? 0;
|
|
3100
|
+
return {
|
|
3101
|
+
limit,
|
|
3102
|
+
offset,
|
|
3103
|
+
driverOffset: params?.page != null ? offset : params?.offset
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
2944
3106
|
function normalizePageSize(raw) {
|
|
2945
3107
|
if (raw === void 0 || !Number.isFinite(raw)) return 200;
|
|
2946
3108
|
return Math.max(1, Math.floor(raw));
|
|
@@ -3060,8 +3222,10 @@ async function collectAllPages(find, params, label = "collection") {
|
|
|
3060
3222
|
* metadata, so type coercion is the responsibility of the server-side data
|
|
3061
3223
|
* driver which has access to the collection schema.
|
|
3062
3224
|
*
|
|
3063
|
-
*
|
|
3064
|
-
*
|
|
3225
|
+
* Structural characters inside a value are backslash-escaped: `,` → `\,`,
|
|
3226
|
+
* `(` → `\(`, `)` → `\)`, and a literal backslash as `\\`. Decoding is
|
|
3227
|
+
* deliberately conservative — only those four sequences are decoded, so a
|
|
3228
|
+
* backslash that arrives unescaped from an older client survives intact.
|
|
3065
3229
|
*
|
|
3066
3230
|
* @module
|
|
3067
3231
|
*/
|
|
@@ -3079,22 +3243,48 @@ function stringifyValue(value) {
|
|
|
3079
3243
|
return String(value);
|
|
3080
3244
|
}
|
|
3081
3245
|
/**
|
|
3082
|
-
*
|
|
3083
|
-
*
|
|
3246
|
+
* Characters that carry structure in the wire format and must therefore be
|
|
3247
|
+
* escaped inside a value: the separator, the group delimiters, and the escape
|
|
3248
|
+
* character itself.
|
|
3249
|
+
*
|
|
3250
|
+
* Parentheses are here because `and(...)`/`or(...)` groups are parsed by
|
|
3251
|
+
* tracking paren depth. A value containing one is not merely ambiguous, it
|
|
3252
|
+
* moves where the parser thinks the group ends.
|
|
3084
3253
|
*/
|
|
3085
|
-
|
|
3086
|
-
|
|
3254
|
+
var WIRE_SPECIALS = /[\\,()]/g;
|
|
3255
|
+
/**
|
|
3256
|
+
* Escape a value for the wire format: `\` → `\\`, `,` → `\,`, `(` → `\(`,
|
|
3257
|
+
* `)` → `\)`.
|
|
3258
|
+
*/
|
|
3259
|
+
function escapeWireValue(value) {
|
|
3260
|
+
return value.replace(WIRE_SPECIALS, (ch) => `\\${ch}`);
|
|
3087
3261
|
}
|
|
3088
3262
|
/**
|
|
3089
|
-
* Unescape a
|
|
3090
|
-
*
|
|
3263
|
+
* Unescape a wire-format value.
|
|
3264
|
+
*
|
|
3265
|
+
* **Conservative**, and deliberately so: only the four sequences
|
|
3266
|
+
* {@link escapeWireValue} actually produces are decoded. A backslash followed
|
|
3267
|
+
* by anything else is left exactly as it is.
|
|
3268
|
+
*
|
|
3269
|
+
* This used to consume the backslash before *any* character, which is
|
|
3270
|
+
* indistinguishable for anything this codec emitted — it only ever emits those
|
|
3271
|
+
* four — but not for input arriving from elsewhere. A client on an older
|
|
3272
|
+
* release sends a Windows path or a LIKE pattern with a literal `C:\x`
|
|
3273
|
+
* unescaped, and greedy unescaping silently turned it into `C:x`, changing
|
|
3274
|
+
* which rows matched. Decoding only what the encoder can produce makes the two
|
|
3275
|
+
* directions agree across versions.
|
|
3091
3276
|
*/
|
|
3092
|
-
function
|
|
3277
|
+
function unescapeWireValue(value) {
|
|
3093
3278
|
let result = "";
|
|
3094
|
-
for (let i = 0; i < value.length; i++)
|
|
3095
|
-
|
|
3096
|
-
i
|
|
3097
|
-
|
|
3279
|
+
for (let i = 0; i < value.length; i++) {
|
|
3280
|
+
const next = value[i + 1];
|
|
3281
|
+
if (value[i] === "\\" && (next === "\\" || next === "," || next === "(" || next === ")")) {
|
|
3282
|
+
result += next;
|
|
3283
|
+
i++;
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
result += value[i];
|
|
3287
|
+
}
|
|
3098
3288
|
return result;
|
|
3099
3289
|
}
|
|
3100
3290
|
/**
|
|
@@ -3112,12 +3302,42 @@ function splitListItems(inner) {
|
|
|
3112
3302
|
current += inner[i] + inner[i + 1];
|
|
3113
3303
|
i++;
|
|
3114
3304
|
} else if (inner[i] === ",") {
|
|
3115
|
-
items.push(
|
|
3305
|
+
items.push(unescapeWireValue(current));
|
|
3116
3306
|
current = "";
|
|
3117
3307
|
} else current += inner[i];
|
|
3118
|
-
items.push(
|
|
3308
|
+
items.push(unescapeWireValue(current));
|
|
3119
3309
|
return items;
|
|
3120
3310
|
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Split a group body on commas at paren depth 0, honouring escapes.
|
|
3313
|
+
*
|
|
3314
|
+
* The escape-awareness is the point. The splitter used to track only paren
|
|
3315
|
+
* depth, so a comma inside a scalar value ended a condition:
|
|
3316
|
+
* `or(name.eq.Doe, John,age.gte.18)` parsed as *three* conditions, the middle
|
|
3317
|
+
* one a fabricated `" John" == true`. On an `or` that widens the result set,
|
|
3318
|
+
* and nothing anywhere reports an error — the query simply stops meaning what
|
|
3319
|
+
* the caller wrote.
|
|
3320
|
+
*/
|
|
3321
|
+
function splitGroupItems(inner) {
|
|
3322
|
+
const parts = [];
|
|
3323
|
+
let depth = 0;
|
|
3324
|
+
let start = 0;
|
|
3325
|
+
for (let i = 0; i < inner.length; i++) {
|
|
3326
|
+
const ch = inner[i];
|
|
3327
|
+
if (ch === "\\" && i + 1 < inner.length) {
|
|
3328
|
+
i++;
|
|
3329
|
+
continue;
|
|
3330
|
+
}
|
|
3331
|
+
if (ch === "(") depth++;
|
|
3332
|
+
else if (ch === ")") depth--;
|
|
3333
|
+
else if (ch === "," && depth === 0) {
|
|
3334
|
+
parts.push(inner.slice(start, i));
|
|
3335
|
+
start = i + 1;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
parts.push(inner.slice(start));
|
|
3339
|
+
return parts;
|
|
3340
|
+
}
|
|
3121
3341
|
var REST_OP_LOOKUP = REST_TO_CANONICAL;
|
|
3122
3342
|
var CANONICAL_OP_LOOKUP = CANONICAL_TO_REST;
|
|
3123
3343
|
/**
|
|
@@ -3136,7 +3356,7 @@ function serializeTuple(tuple) {
|
|
|
3136
3356
|
if (typeof op !== "string") throw new TypeError(`serializeTuple: operator must be a string, got ${typeof op}`);
|
|
3137
3357
|
const restOp = CANONICAL_OP_LOOKUP[op];
|
|
3138
3358
|
if (!restOp) throw new TypeError(`serializeTuple: unknown operator "${op}". Valid operators: ${Object.keys(CANONICAL_TO_REST).join(", ")}`);
|
|
3139
|
-
if (Array.isArray(value)) return `${restOp}.(${value.map((v) =>
|
|
3359
|
+
if (Array.isArray(value)) return `${restOp}.(${value.map((v) => escapeWireValue(stringifyValue(v))).join(",")})`;
|
|
3140
3360
|
return `${restOp}.${stringifyValue(value)}`;
|
|
3141
3361
|
}
|
|
3142
3362
|
/**
|
|
@@ -3246,40 +3466,20 @@ function serializeLogicalCondition(cond) {
|
|
|
3246
3466
|
}
|
|
3247
3467
|
const restOp = CANONICAL_OP_LOOKUP[cond.operator] ?? "eq";
|
|
3248
3468
|
if (Array.isArray(cond.value)) {
|
|
3249
|
-
const items = cond.value.map((v) =>
|
|
3469
|
+
const items = cond.value.map((v) => escapeWireValue(stringifyValue(v))).join(",");
|
|
3250
3470
|
return `${cond.column}.${restOp}.(${items})`;
|
|
3251
3471
|
}
|
|
3252
|
-
return `${cond.column}.${restOp}.${stringifyValue(cond.value)}`;
|
|
3472
|
+
return `${cond.column}.${restOp}.${escapeWireValue(stringifyValue(cond.value))}`;
|
|
3253
3473
|
}
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
* `LogicalCondition` or `FilterCondition`.
|
|
3257
|
-
*
|
|
3258
|
-
* @example
|
|
3259
|
-
* deserializeLogicalCondition("status.eq.active")
|
|
3260
|
-
* // → { column: "status", operator: "==", value: "active" }
|
|
3261
|
-
*
|
|
3262
|
-
* deserializeLogicalCondition("or(status.eq.active,age.gte.18)")
|
|
3263
|
-
* // → { type: "or", conditions: [...] }
|
|
3264
|
-
*/
|
|
3265
|
-
function deserializeLogicalCondition(str) {
|
|
3474
|
+
function deserializeLogicalCondition(str, nesting = 0) {
|
|
3475
|
+
if (nesting > 32) throw new Error(`Filter groups nest more than 32 levels deep. Flatten the condition — \`or(a,or(b,c))\` is \`or(a,b,c)\`.`);
|
|
3266
3476
|
const logicalMatch = str.match(/^(and|or)\((.+)\)$/);
|
|
3267
3477
|
if (logicalMatch) {
|
|
3268
3478
|
const type = logicalMatch[1];
|
|
3269
3479
|
const innerStr = logicalMatch[2];
|
|
3270
|
-
const conditions = [];
|
|
3271
|
-
let depth = 0;
|
|
3272
|
-
let start = 0;
|
|
3273
|
-
for (let i = 0; i < innerStr.length; i++) if (innerStr[i] === "(") depth++;
|
|
3274
|
-
else if (innerStr[i] === ")") depth--;
|
|
3275
|
-
else if (innerStr[i] === "," && depth === 0) {
|
|
3276
|
-
conditions.push(deserializeLogicalCondition(innerStr.slice(start, i)));
|
|
3277
|
-
start = i + 1;
|
|
3278
|
-
}
|
|
3279
|
-
conditions.push(deserializeLogicalCondition(innerStr.slice(start)));
|
|
3280
3480
|
return {
|
|
3281
3481
|
type,
|
|
3282
|
-
conditions
|
|
3482
|
+
conditions: splitGroupItems(innerStr).map((part) => deserializeLogicalCondition(part, nesting + 1))
|
|
3283
3483
|
};
|
|
3284
3484
|
}
|
|
3285
3485
|
const firstDot = str.indexOf(".");
|
|
@@ -3294,7 +3494,7 @@ function deserializeLogicalCondition(str) {
|
|
|
3294
3494
|
if (secondDot === -1) return {
|
|
3295
3495
|
column,
|
|
3296
3496
|
operator: "==",
|
|
3297
|
-
value: rest
|
|
3497
|
+
value: unescapeWireValue(rest)
|
|
3298
3498
|
};
|
|
3299
3499
|
const opStr = rest.substring(0, secondDot);
|
|
3300
3500
|
const valueStr = rest.substring(secondDot + 1);
|
|
@@ -3307,7 +3507,7 @@ function deserializeLogicalCondition(str) {
|
|
|
3307
3507
|
return {
|
|
3308
3508
|
column,
|
|
3309
3509
|
operator,
|
|
3310
|
-
value: valueStr
|
|
3510
|
+
value: unescapeWireValue(valueStr)
|
|
3311
3511
|
};
|
|
3312
3512
|
}
|
|
3313
3513
|
//#endregion
|
|
@@ -3344,10 +3544,12 @@ function createPrimaryKeyResolver(options) {
|
|
|
3344
3544
|
* than postgres still serve rows with one, and this keeps them working.
|
|
3345
3545
|
*/
|
|
3346
3546
|
function rowToEntity(row, slug, primaryKeys = []) {
|
|
3547
|
+
const { _matches, ...values } = row;
|
|
3347
3548
|
return {
|
|
3348
3549
|
id: primaryKeys.length > 0 ? buildCompositeId(row, primaryKeys) : row.id,
|
|
3349
3550
|
path: slug,
|
|
3350
|
-
values
|
|
3551
|
+
values,
|
|
3552
|
+
..._matches ? { searchMatches: _matches } : {}
|
|
3351
3553
|
};
|
|
3352
3554
|
}
|
|
3353
3555
|
/**
|
|
@@ -3390,21 +3592,22 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3390
3592
|
const accessor = {
|
|
3391
3593
|
async find(params) {
|
|
3392
3594
|
const filter = params?.where ? deserializeFilter(params.where) : void 0;
|
|
3393
|
-
const limit = params
|
|
3394
|
-
const offset = params?.offset ?? 0;
|
|
3595
|
+
const { limit, offset, driverOffset } = resolveFindWindow(params);
|
|
3395
3596
|
const fetchService = driver.restFetchService;
|
|
3396
3597
|
const rows = fetchService ? await fetchService.fetchCollectionForRest(slug, {
|
|
3397
3598
|
filter,
|
|
3398
|
-
|
|
3399
|
-
|
|
3599
|
+
logical: params?.logical,
|
|
3600
|
+
limit,
|
|
3601
|
+
offset: driverOffset,
|
|
3400
3602
|
orderBy: params?.orderBy?.[0],
|
|
3401
3603
|
order: params?.orderBy?.[1],
|
|
3402
3604
|
searchString: params?.searchString
|
|
3403
3605
|
}, params?.include) : await driver.fetchCollection({
|
|
3404
3606
|
path: slug,
|
|
3405
|
-
limit
|
|
3406
|
-
offset:
|
|
3607
|
+
limit,
|
|
3608
|
+
offset: driverOffset,
|
|
3407
3609
|
filter,
|
|
3610
|
+
logical: params?.logical,
|
|
3408
3611
|
orderBy: params?.orderBy?.[0],
|
|
3409
3612
|
order: params?.orderBy?.[1],
|
|
3410
3613
|
searchString: params?.searchString
|
|
@@ -3414,7 +3617,9 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3414
3617
|
if (driver.count) {
|
|
3415
3618
|
total = await driver.count({
|
|
3416
3619
|
path: slug,
|
|
3417
|
-
filter
|
|
3620
|
+
filter,
|
|
3621
|
+
logical: params?.logical,
|
|
3622
|
+
searchString: params?.searchString
|
|
3418
3623
|
});
|
|
3419
3624
|
hasMore = offset + rows.length < total;
|
|
3420
3625
|
}
|
|
@@ -3466,30 +3671,48 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3466
3671
|
values: {}
|
|
3467
3672
|
} });
|
|
3468
3673
|
},
|
|
3674
|
+
updateMany: driver.updateMany ? async (updates) => {
|
|
3675
|
+
return (await driver.updateMany({
|
|
3676
|
+
path: slug,
|
|
3677
|
+
updates: updates.map((u) => ({
|
|
3678
|
+
id: u.id,
|
|
3679
|
+
values: u.data
|
|
3680
|
+
}))
|
|
3681
|
+
})).map((row) => rowToEntity(row, slug, getPks()));
|
|
3682
|
+
} : void 0,
|
|
3683
|
+
deleteMany: driver.deleteMany ? async (ids) => {
|
|
3684
|
+
await driver.deleteMany({
|
|
3685
|
+
path: slug,
|
|
3686
|
+
ids
|
|
3687
|
+
});
|
|
3688
|
+
} : void 0,
|
|
3469
3689
|
count: driver.count ? async (params) => {
|
|
3470
3690
|
const filter = params?.where ? deserializeFilter(params.where) : void 0;
|
|
3471
3691
|
return driver.count({
|
|
3472
3692
|
path: slug,
|
|
3473
|
-
filter
|
|
3693
|
+
filter,
|
|
3694
|
+
logical: params?.logical,
|
|
3695
|
+
searchString: params?.searchString
|
|
3474
3696
|
});
|
|
3475
3697
|
} : void 0,
|
|
3476
3698
|
listen: driver.listenCollection ? (params, onUpdate, onError) => {
|
|
3477
|
-
const limit = params
|
|
3478
|
-
const offset = params?.offset ?? 0;
|
|
3699
|
+
const { limit, offset, driverOffset } = resolveFindWindow(params);
|
|
3479
3700
|
const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
|
|
3480
3701
|
return driver.listenCollection({
|
|
3481
3702
|
path: slug,
|
|
3482
|
-
limit
|
|
3483
|
-
offset:
|
|
3703
|
+
limit,
|
|
3704
|
+
offset: driverOffset,
|
|
3484
3705
|
filter: params?.where,
|
|
3706
|
+
logical: params?.logical,
|
|
3485
3707
|
orderBy: params?.orderBy?.[0],
|
|
3486
3708
|
order: params?.orderBy?.[1],
|
|
3487
3709
|
searchString: params?.searchString,
|
|
3710
|
+
searchExplain: params?.searchExplain,
|
|
3488
3711
|
onUpdate: (entities) => {
|
|
3489
3712
|
onUpdate({
|
|
3490
3713
|
data: entities.map((row) => rowToEntity(normalize(row), slug, getPks())),
|
|
3491
3714
|
meta: {
|
|
3492
|
-
total: entities.length,
|
|
3715
|
+
total: offset + entities.length,
|
|
3493
3716
|
limit,
|
|
3494
3717
|
offset,
|
|
3495
3718
|
hasMore: entities.length >= limit
|
|
@@ -3522,8 +3745,11 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3522
3745
|
offset(count) {
|
|
3523
3746
|
return new QueryBuilder(accessor).offset(count);
|
|
3524
3747
|
},
|
|
3525
|
-
search(searchString) {
|
|
3526
|
-
return new QueryBuilder(accessor).search(searchString);
|
|
3748
|
+
search(searchString, options) {
|
|
3749
|
+
return new QueryBuilder(accessor).search(searchString, options);
|
|
3750
|
+
},
|
|
3751
|
+
vectorSearch(property, vector, options) {
|
|
3752
|
+
return new QueryBuilder(accessor).vectorSearch(property, vector, options);
|
|
3527
3753
|
},
|
|
3528
3754
|
include(...relations) {
|
|
3529
3755
|
return new QueryBuilder(accessor).include(...relations);
|
|
@@ -3611,8 +3837,18 @@ var SdkQueryBuilder = class {
|
|
|
3611
3837
|
this.params.offset = count;
|
|
3612
3838
|
return this;
|
|
3613
3839
|
}
|
|
3614
|
-
search(searchString) {
|
|
3840
|
+
search(searchString, options) {
|
|
3615
3841
|
this.params.searchString = searchString;
|
|
3842
|
+
if (options?.explain !== void 0) this.params.searchExplain = options.explain;
|
|
3843
|
+
return this;
|
|
3844
|
+
}
|
|
3845
|
+
vectorSearch(property, vector, options) {
|
|
3846
|
+
this.params.vectorSearch = {
|
|
3847
|
+
property,
|
|
3848
|
+
vector,
|
|
3849
|
+
...options?.distance !== void 0 && { distance: options.distance },
|
|
3850
|
+
...options?.threshold !== void 0 && { threshold: options.threshold }
|
|
3851
|
+
};
|
|
3616
3852
|
return this;
|
|
3617
3853
|
}
|
|
3618
3854
|
include(...relations) {
|
|
@@ -3666,9 +3902,24 @@ function toSdkCollectionClient(snap, slug = "collection") {
|
|
|
3666
3902
|
async update(id, data) {
|
|
3667
3903
|
return entityToRow(await snap.update(id, data));
|
|
3668
3904
|
},
|
|
3905
|
+
async updateMany(updates) {
|
|
3906
|
+
if (!Array.isArray(updates)) throw new TypeError("updateMany expects an array of { id, data } entries.");
|
|
3907
|
+
if (updates.length === 0) return [];
|
|
3908
|
+
if (!snap.updateMany) throw new Error("Bulk updates are not supported by this collection's data source. Fall back to update() per record.");
|
|
3909
|
+
return (await snap.updateMany(updates.map((u) => ({
|
|
3910
|
+
id: u.id,
|
|
3911
|
+
data: u.data
|
|
3912
|
+
})))).map(entityToRow);
|
|
3913
|
+
},
|
|
3669
3914
|
delete(id) {
|
|
3670
3915
|
return snap.delete(id);
|
|
3671
3916
|
},
|
|
3917
|
+
async deleteMany(ids) {
|
|
3918
|
+
if (!Array.isArray(ids)) throw new TypeError("deleteMany expects an array of ids.");
|
|
3919
|
+
if (ids.length === 0) return;
|
|
3920
|
+
if (!snap.deleteMany) throw new Error("Bulk deletes are not supported by this collection's data source. Fall back to delete() per record.");
|
|
3921
|
+
await snap.deleteMany(ids);
|
|
3922
|
+
},
|
|
3672
3923
|
count: snap.count ? (params) => snap.count(params) : void 0,
|
|
3673
3924
|
listen: snap.listen ? (params, onUpdate, onError) => snap.listen(params, (res) => onUpdate({
|
|
3674
3925
|
data: res.data.map(entityToRow),
|
|
@@ -3684,6 +3935,7 @@ function toSdkCollectionClient(snap, slug = "collection") {
|
|
|
3684
3935
|
limit: (count) => new SdkQueryBuilder(client).limit(count),
|
|
3685
3936
|
offset: (count) => new SdkQueryBuilder(client).offset(count),
|
|
3686
3937
|
search: (searchString) => new SdkQueryBuilder(client).search(searchString),
|
|
3938
|
+
vectorSearch: (property, vector, options) => new SdkQueryBuilder(client).vectorSearch(property, vector, options),
|
|
3687
3939
|
include: (...relations) => new SdkQueryBuilder(client).include(...relations)
|
|
3688
3940
|
};
|
|
3689
3941
|
return client;
|
|
@@ -3772,6 +4024,6 @@ function buildRoutedRebaseData({ defaultData, sources, resolveKey }) {
|
|
|
3772
4024
|
} });
|
|
3773
4025
|
}
|
|
3774
4026
|
//#endregion
|
|
3775
|
-
export { Vector as
|
|
4027
|
+
export { isAnonymousUid as C, Vector as D, GeoPoint as E, RebaseApiError as O, ANONYMOUS_USER_ID as S, EntityRelation as T, resolvePrimaryKeys as _, serializeFilter as a, MAX_LIST_LIMIT as b, paginateFind as c, createDataSourceRegistry as d, resolveDataSource as f, buildCompositeId as g, resolveCollectionRelations as h, deserializeLogicalCondition as i, RebaseClientError as k, resolveFindWindow as l, findRelation as m, buildSdkData as n, serializeLogicalCondition as o, getEffectiveSecurityRules as p, deserializeFilter as r, collectAllPages as s, buildRoutedRebaseData as t, CollectionRegistry as u, toSnakeCase as v, EntityReference as w, resolveClientListLimit as x, ListLimitError as y };
|
|
3776
4028
|
|
|
3777
|
-
//# sourceMappingURL=src-
|
|
4029
|
+
//# sourceMappingURL=src-By48Ffg0.js.map
|