@rebasepro/server 0.17.3 → 0.18.1
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/LICENSE +0 -1
- package/README.md +5 -1
- package/bin/rebase-server.js +16 -1
- package/dist/{GCSStorageController-Bl7nmhdv.js → GCSStorageController-ZXPoNqW3.js} +21 -10
- package/dist/GCSStorageController-ZXPoNqW3.js.map +1 -0
- package/dist/{S3StorageController-CWvvrRpF.js → S3StorageController-5pAXyv31.js} +24 -10
- package/dist/S3StorageController-5pAXyv31.js.map +1 -0
- package/dist/admin-roles-vYdp_Pil.js +36 -0
- package/dist/admin-roles-vYdp_Pil.js.map +1 -0
- package/dist/{admin_block-BX6YULvJ.js → admin_block-0Xu0r6eZ.js} +4 -1
- package/dist/admin_block-0Xu0r6eZ.js.map +1 -0
- package/dist/api/errors.d.ts +37 -0
- package/dist/api/live-schema-routes.d.ts +14 -0
- package/dist/api/logs-routes.d.ts +8 -0
- package/dist/api/rest/api-generator.d.ts +33 -0
- package/dist/api/root-error-handler.d.ts +62 -0
- package/dist/api/schema-drift.d.ts +39 -0
- package/dist/api/types.d.ts +55 -0
- package/dist/{ast-schema-editor-CvkYOvri.js → ast-schema-editor-CNgFJ3NF.js} +23 -7
- package/dist/ast-schema-editor-CNgFJ3NF.js.map +1 -0
- package/dist/auth/api-keys/api-key-routes.d.ts +12 -0
- package/dist/auth/auth-hooks.d.ts +13 -2
- package/dist/auth/exclude-auth-secrets.d.ts +14 -0
- package/dist/auth/jwt.d.ts +22 -1
- package/dist/auth/mfa-routes.d.ts +10 -1
- package/dist/auth/middleware.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/registration-policy.d.ts +27 -0
- package/dist/auth/rls-scope.d.ts +9 -1
- package/dist/auth/routes.d.ts +8 -1
- package/dist/auth/seed-admin.d.ts +73 -0
- package/dist/auth/session-routes.d.ts +10 -0
- package/dist/{auth-DkbzMZUS.js → auth-BRiOuyq8.js} +283 -198
- package/dist/auth-BRiOuyq8.js.map +1 -0
- package/dist/{backup-CRdZkA6c.js → backup-C8P6Cl3G.js} +2 -2
- package/dist/{backup-CRdZkA6c.js.map → backup-C8P6Cl3G.js.map} +1 -1
- package/dist/boot/boot.d.ts +18 -0
- package/dist/boot/bundle.d.ts +29 -6
- package/dist/boot/ddl-bootstrap.d.ts +2 -0
- package/dist/boot/env.d.ts +30 -1
- package/dist/boot/fetch-bundle.d.ts +24 -1
- package/dist/boot/options.d.ts +0 -8
- package/dist/boot/provision.d.ts +58 -18
- package/dist/boot/resource-adapters.d.ts +11 -1
- package/dist/boot/resource-resolvers.d.ts +96 -0
- package/dist/boot/sources.d.ts +27 -20
- package/dist/collections/validate-config.d.ts +29 -0
- package/dist/{contract-routes-CHPVKTZW.js → contract-routes-CusnEB5h.js} +24 -9
- package/dist/{contract-routes-CHPVKTZW.js.map → contract-routes-CusnEB5h.js.map} +1 -1
- package/dist/cron/cron-loader.d.ts +7 -1
- package/dist/cron/cron-scheduler.d.ts +48 -2
- package/dist/{cron-loader-d9WMFENB.js → cron-loader-DnmIePn_.js} +23 -4
- package/dist/cron-loader-DnmIePn_.js.map +1 -0
- package/dist/{cron-routes-DSC-kZXI.js → cron-routes-D3x2ydMa.js} +9 -6
- package/dist/cron-routes-D3x2ydMa.js.map +1 -0
- package/dist/{cron-scheduler-BB82dWuU.js → cron-scheduler-BpPKpl4i.js} +148 -28
- package/dist/cron-scheduler-BpPKpl4i.js.map +1 -0
- package/dist/{cron-store-KeN8rN63.js → cron-store-CCQXwgVL.js} +5 -5
- package/dist/{cron-store-KeN8rN63.js.map → cron-store-CCQXwgVL.js.map} +1 -1
- package/dist/{ddl-bootstrap-5YZCZ8qk.js → ddl-bootstrap-CfNvxMuK.js} +26 -2
- package/dist/{ddl-bootstrap-5YZCZ8qk.js.map → ddl-bootstrap-CfNvxMuK.js.map} +1 -1
- package/dist/email/index.d.ts +1 -0
- package/dist/email/unconfigured.d.ts +20 -0
- package/dist/env.d.ts +25 -0
- package/dist/errors-HjfaPlvY.js +385 -0
- package/dist/errors-HjfaPlvY.js.map +1 -0
- package/dist/{function-loader-LLdmBFoL.js → function-loader-xnbDAPfa.js} +9 -3
- package/dist/{function-loader-LLdmBFoL.js.map → function-loader-xnbDAPfa.js.map} +1 -1
- package/dist/function-routes-gQ0EShVG.js +267 -0
- package/dist/function-routes-gQ0EShVG.js.map +1 -0
- package/dist/functions/define-function.d.ts +6 -3
- package/dist/functions/function-routes.d.ts +8 -4
- package/dist/functions/index.js +128 -23
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-B2pL3X8b.js → history-recorder-B1FwXx9J.js} +3 -3
- package/dist/{history-recorder-B2pL3X8b.js.map → history-recorder-B1FwXx9J.js.map} +1 -1
- package/dist/{history-store-BUJ-37Sm.js → history-store-LHXaQywp.js} +2 -2
- package/dist/{history-store-BUJ-37Sm.js.map → history-store-LHXaQywp.js.map} +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.es.js +16229 -7676
- package/dist/index.es.js.map +1 -1
- package/dist/init.d.ts +33 -13
- package/dist/{jobs-XXXcZrmj.js → jobs-DkkD9mPV.js} +5 -5
- package/dist/{jobs-XXXcZrmj.js.map → jobs-DkkD9mPV.js.map} +1 -1
- package/dist/{jwt-DNeOn39r.js → jwt-DkhXwMzR.js} +9 -314
- package/dist/jwt-DkhXwMzR.js.map +1 -0
- package/dist/keys-g8lbVC_o.js +230 -0
- package/dist/keys-g8lbVC_o.js.map +1 -0
- package/dist/{logger-DS03e908.js → logger-DO2PZc4i.js} +197 -18
- package/dist/logger-DO2PZc4i.js.map +1 -0
- package/dist/{logs-routes-DB72iQSr.js → logs-routes-CbsTpozn.js} +74 -6
- package/dist/logs-routes-CbsTpozn.js.map +1 -0
- package/dist/{openapi-generator-CpZp2Fai.js → openapi-generator-BIBbO1Tq.js} +221 -38
- package/dist/openapi-generator-BIBbO1Tq.js.map +1 -0
- package/dist/{proxy-CMymhnwG.js → proxy-Czngl3p9.js} +2 -2
- package/dist/{proxy-CMymhnwG.js.map → proxy-Czngl3p9.js.map} +1 -1
- package/dist/{query-parser-DqXNaBIH.js → query-parser-C68Q9EX4.js} +35 -6
- package/dist/{query-parser-DqXNaBIH.js.map → query-parser-C68Q9EX4.js.map} +1 -1
- package/dist/queues/runtime.d.ts +56 -0
- package/dist/{request-timeout-C8gkc-j7.js → request-timeout-DESvlfrS.js} +4 -6
- package/dist/request-timeout-DESvlfrS.js.map +1 -0
- package/dist/schema-edit/project-root.d.ts +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js → schema-editor-routes-CcZKh50q.js} +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js.map → schema-editor-routes-CcZKh50q.js.map} +1 -1
- package/dist/{schemas-DBxgjM9A.js → schemas-C3234HWE.js} +2746 -72
- package/dist/schemas-C3234HWE.js.map +1 -0
- package/dist/singleton.d.ts +6 -4
- package/dist/src-DHK4fHkw.js +1002 -0
- package/dist/src-DHK4fHkw.js.map +1 -0
- package/dist/{src-C9gKj23J.js → src-Dq-I3Ybx.js} +507 -54
- package/dist/src-Dq-I3Ybx.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +5 -0
- package/dist/storage/LocalStorageController.d.ts +14 -0
- package/dist/storage/S3StorageController.d.ts +8 -0
- package/dist/storage/image-transform.d.ts +35 -0
- package/dist/storage/keys.d.ts +23 -0
- package/dist/storage/policies.d.ts +22 -0
- package/dist/storage/routes.d.ts +9 -0
- package/dist/storage/tus-handler.d.ts +43 -1
- package/dist/storage/types.d.ts +28 -0
- package/dist/types-BfKcm9do.js.map +1 -1
- package/dist/utils/dev-port.d.ts +18 -0
- package/dist/utils/logger.d.ts +60 -0
- package/dist/utils/request-logger.d.ts +2 -1
- package/package.json +30 -24
- package/dist/GCSStorageController-Bl7nmhdv.js.map +0 -1
- package/dist/S3StorageController-CWvvrRpF.js.map +0 -1
- package/dist/admin_block-BX6YULvJ.js.map +0 -1
- package/dist/ast-schema-editor-CvkYOvri.js.map +0 -1
- package/dist/auth-DkbzMZUS.js.map +0 -1
- package/dist/cron-loader-d9WMFENB.js.map +0 -1
- package/dist/cron-routes-DSC-kZXI.js.map +0 -1
- package/dist/cron-scheduler-BB82dWuU.js.map +0 -1
- package/dist/errors-DBwpj9N8.js +0 -231
- package/dist/errors-DBwpj9N8.js.map +0 -1
- package/dist/function-routes-ClT6UQpD.js +0 -41
- package/dist/function-routes-ClT6UQpD.js.map +0 -1
- package/dist/jwt-DNeOn39r.js.map +0 -1
- package/dist/logger-DS03e908.js.map +0 -1
- package/dist/logs-routes-DB72iQSr.js.map +0 -1
- package/dist/openapi-generator-CpZp2Fai.js.map +0 -1
- package/dist/request-timeout-C8gkc-j7.js.map +0 -1
- package/dist/schemas-DBxgjM9A.js.map +0 -1
- package/dist/src-C9gKj23J.js.map +0 -1
- package/dist/src-CcOl05Oq.js +0 -552
- package/dist/src-CcOl05Oq.js.map +0 -1
- package/dist/utils/logging.d.ts +0 -5
|
@@ -3,7 +3,7 @@ import __rebaseProcess from "process";
|
|
|
3
3
|
globalThis.process ??= __rebaseProcess;
|
|
4
4
|
__rebaseCreateRequire(import.meta.url);
|
|
5
5
|
import { r as __require, t as __commonJSMin } from "./rolldown-runtime-dW7B1o5h.js";
|
|
6
|
-
import {
|
|
6
|
+
import { A as REST_TO_CANONICAL, C as isPostgresCollectionConfig, D as ALL_WHERE_FILTER_OPS, E as getDataSourceCapabilities, N as sortKeyToString, O as CANONICAL_TO_REST, P as toCanonicalOp, S as getDeclaredSubcollections, j as isRelationAggregateSort, k as NULL_OPS, p as resolveResourceRefs, w as isRelationalCollectionConfig } from "./src-DHK4fHkw.js";
|
|
7
7
|
//#region ../types/src/errors.ts
|
|
8
8
|
/**
|
|
9
9
|
* The single error type thrown across the entire Rebase client surface —
|
|
@@ -36,12 +36,18 @@ var RebaseApiError = class extends Error {
|
|
|
36
36
|
code;
|
|
37
37
|
/** Structured error payload from the server, when present. */
|
|
38
38
|
details;
|
|
39
|
+
/** See {@link RebaseErrorInit.requestId}. Quote it in a bug report. */
|
|
40
|
+
requestId;
|
|
41
|
+
/** See {@link RebaseErrorInit.retryAfterSeconds}. */
|
|
42
|
+
retryAfterSeconds;
|
|
39
43
|
constructor(message, init = {}) {
|
|
40
44
|
super(message);
|
|
41
45
|
this.name = "RebaseApiError";
|
|
42
46
|
this.status = init.status;
|
|
43
47
|
this.code = init.code;
|
|
44
48
|
this.details = init.details;
|
|
49
|
+
this.requestId = init.requestId;
|
|
50
|
+
this.retryAfterSeconds = init.retryAfterSeconds;
|
|
45
51
|
if (init.cause !== void 0) this.cause = init.cause;
|
|
46
52
|
}
|
|
47
53
|
};
|
|
@@ -55,11 +61,76 @@ var RebaseApiError = class extends Error {
|
|
|
55
61
|
* @group Errors
|
|
56
62
|
*/
|
|
57
63
|
var RebaseClientError = class extends RebaseApiError {
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
/**
|
|
65
|
+
* `init` is the same one {@link RebaseApiError} takes, and it is what makes
|
|
66
|
+
* `code` reachable at all.
|
|
67
|
+
*
|
|
68
|
+
* The constructor used to accept a message and nothing else, so every
|
|
69
|
+
* client-side failure — an undefined filter value, an unknown accessor,
|
|
70
|
+
* `listen()` on a client built with `realtime: false`, a function name with
|
|
71
|
+
* a `/` in it, `refreshSession()` while signed out — arrived with `code ===
|
|
72
|
+
* undefined`. The documented `switch (e.code)` in this file's own example
|
|
73
|
+
* fell to `default: throw e` for all of them, and the only client-side error
|
|
74
|
+
* that *did* carry a code was `OFFLINE`, because that one path minted a
|
|
75
|
+
* `RebaseApiError` instead.
|
|
76
|
+
*/
|
|
77
|
+
constructor(message, init = {}) {
|
|
78
|
+
super(message, init);
|
|
60
79
|
this.name = "RebaseClientError";
|
|
61
80
|
}
|
|
62
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* Brand for a contract method a particular client cannot serve.
|
|
84
|
+
*
|
|
85
|
+
* `Symbol.for` rather than a fresh symbol: two copies of `@rebasepro/types` in
|
|
86
|
+
* one tree — which happens, see `docs/dependency-duplication-traps.md` — must
|
|
87
|
+
* agree about it, and a module-local symbol would not.
|
|
88
|
+
*/
|
|
89
|
+
var UNSUPPORTED_METHOD = Symbol.for("rebase.unsupportedMethod");
|
|
90
|
+
/**
|
|
91
|
+
* Build the stub a client installs for a contract method it cannot serve.
|
|
92
|
+
*
|
|
93
|
+
* `listen`, `listenById` and `count` are part of `SDKCollectionClient`, not
|
|
94
|
+
* optional extras — a caller should be able to write
|
|
95
|
+
* `client.data.posts.count()` without asking first, and a transport that cannot
|
|
96
|
+
* serve it should answer with a sentence naming the configuration that would,
|
|
97
|
+
* rather than with `undefined is not a function` at the call site. Where the
|
|
98
|
+
* transport genuinely cannot (a client built with `realtime: false`, a driver
|
|
99
|
+
* with no `listenCollection`), it installs one of these instead of omitting the
|
|
100
|
+
* method.
|
|
101
|
+
*
|
|
102
|
+
* @param message What to tell the caller, naming the fix.
|
|
103
|
+
* @group Errors
|
|
104
|
+
*/
|
|
105
|
+
function unsupportedMethod(message) {
|
|
106
|
+
const stub = () => {
|
|
107
|
+
throw new RebaseClientError(message, { code: "REALTIME_DISABLED" });
|
|
108
|
+
};
|
|
109
|
+
stub[UNSUPPORTED_METHOD] = true;
|
|
110
|
+
return stub;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Can this method actually do anything?
|
|
114
|
+
*
|
|
115
|
+
* `true` for a stub from {@link unsupportedMethod} **and** for a method that is
|
|
116
|
+
* simply not there — a partial client, a hand-built test double, an
|
|
117
|
+
* implementation written against an older shape of the interface. Both mean the
|
|
118
|
+
* same thing to a caller, so both answer the same way, and an adapter that
|
|
119
|
+
* checks this cannot be caught out by either.
|
|
120
|
+
*
|
|
121
|
+
* Ordinary code does not need it: calling the method and letting it throw is
|
|
122
|
+
* the normal path. Adapters do — the admin panel chooses between subscribing
|
|
123
|
+
* and a one-shot `find()` by asking whether the client can listen, and a UI
|
|
124
|
+
* that subscribes into a throw is worse than one that polls. This is the
|
|
125
|
+
* question `if (accessor.listen)` used to be asking, made explicit now that the
|
|
126
|
+
* method is always there to call.
|
|
127
|
+
*
|
|
128
|
+
* @group Errors
|
|
129
|
+
*/
|
|
130
|
+
function isUnsupported(method) {
|
|
131
|
+
if (typeof method !== "function") return true;
|
|
132
|
+
return method[UNSUPPORTED_METHOD] === true;
|
|
133
|
+
}
|
|
63
134
|
//#endregion
|
|
64
135
|
//#region ../types/src/types/entities.ts
|
|
65
136
|
/**
|
|
@@ -268,6 +339,7 @@ var policy = {
|
|
|
268
339
|
roles
|
|
269
340
|
}),
|
|
270
341
|
authenticated: () => ({ kind: "authenticated" }),
|
|
342
|
+
registered: () => ({ kind: "registered" }),
|
|
271
343
|
serverContext: () => ({ kind: "serverContext" }),
|
|
272
344
|
existsIn: (args) => ({
|
|
273
345
|
kind: "existsIn",
|
|
@@ -1282,6 +1354,51 @@ var toSnakeCase = (str) => {
|
|
|
1282
1354
|
return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
|
|
1283
1355
|
};
|
|
1284
1356
|
/**
|
|
1357
|
+
* Are these two identifiers one edit apart, ignoring case?
|
|
1358
|
+
*
|
|
1359
|
+
* Tight on purpose. A suggester that fires on two edits offers `columnWidth`
|
|
1360
|
+
* for `colWith` and `readOnly` for `required`, and a "did you mean" that is
|
|
1361
|
+
* usually wrong teaches people to stop reading them. One edit covers what
|
|
1362
|
+
* actually happens — a dropped letter (`multilne`), a doubled one, a transposed
|
|
1363
|
+
* pair (`validaton`), a wrong case — and nothing else.
|
|
1364
|
+
*
|
|
1365
|
+
* Lives here rather than beside either caller because both the CLI's manifest
|
|
1366
|
+
* reader and the server's collection validator answer the same question about
|
|
1367
|
+
* an unknown key, and two copies of a similarity rule is two rules the moment
|
|
1368
|
+
* one of them is tuned.
|
|
1369
|
+
*/
|
|
1370
|
+
function isNearMiss(a, b) {
|
|
1371
|
+
const x = a.toLowerCase();
|
|
1372
|
+
const y = b.toLowerCase();
|
|
1373
|
+
if (x === y) return true;
|
|
1374
|
+
if (Math.abs(x.length - y.length) > 1) return false;
|
|
1375
|
+
const [shorter, longer] = x.length <= y.length ? [x, y] : [y, x];
|
|
1376
|
+
let i = 0;
|
|
1377
|
+
let j = 0;
|
|
1378
|
+
let edits = 0;
|
|
1379
|
+
while (i < shorter.length && j < longer.length) {
|
|
1380
|
+
if (shorter[i] === longer[j]) {
|
|
1381
|
+
i++;
|
|
1382
|
+
j++;
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (++edits > 1) return false;
|
|
1386
|
+
if (shorter.length === longer.length) i++;
|
|
1387
|
+
j++;
|
|
1388
|
+
}
|
|
1389
|
+
return edits + (longer.length - j) + (shorter.length - i) <= 1;
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* The key in `known` that `key` was probably meant to be, if any.
|
|
1393
|
+
*
|
|
1394
|
+
* `undefined` when nothing is close — which is the common case for a key that
|
|
1395
|
+
* is simply from a newer version, and the reason the caller must be able to say
|
|
1396
|
+
* "unknown" without saying "wrong".
|
|
1397
|
+
*/
|
|
1398
|
+
function suggestNearMiss(known, key) {
|
|
1399
|
+
return known.find((candidate) => isNearMiss(candidate, key));
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1285
1402
|
* Deep clone a value, preserving function references and class instances.
|
|
1286
1403
|
* Unlike structuredClone, this handles objects that contain functions
|
|
1287
1404
|
* (e.g. CollectionConfig with target(), childCollections(), callbacks).
|
|
@@ -1664,8 +1781,7 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
|
1664
1781
|
targetSlug: targetCollection.slug,
|
|
1665
1782
|
onUpdate: relation.onUpdate,
|
|
1666
1783
|
onDelete: relation.onDelete,
|
|
1667
|
-
overrides: relation.overrides
|
|
1668
|
-
validation: relation.validation
|
|
1784
|
+
overrides: relation.overrides
|
|
1669
1785
|
};
|
|
1670
1786
|
const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
|
|
1671
1787
|
switch (relation.kind) {
|
|
@@ -1842,9 +1958,60 @@ function resolveCollectionRelations(collection) {
|
|
|
1842
1958
|
_resolvedRelationsCache.set(collection, relations);
|
|
1843
1959
|
return relations;
|
|
1844
1960
|
}
|
|
1961
|
+
/**
|
|
1962
|
+
* The `type: "relation"` property that declares a link, or `undefined` for one
|
|
1963
|
+
* that only exists in the collection's `relations` array.
|
|
1964
|
+
*
|
|
1965
|
+
* Both declaration sites end up in {@link resolveCollectionRelations}, and only
|
|
1966
|
+
* one of them has a property to carry field-level facts — `name`, `admin`, and
|
|
1967
|
+
* the one this exists for, `validation.required`.
|
|
1968
|
+
*/
|
|
1969
|
+
function relationDeclaringProperty(collection, relation) {
|
|
1970
|
+
const resolved = resolveCollectionRelations(collection);
|
|
1971
|
+
for (const [key, raw] of Object.entries(collection.properties ?? {})) {
|
|
1972
|
+
const prop = raw;
|
|
1973
|
+
if (prop?.type !== "relation") continue;
|
|
1974
|
+
if (resolved[key] === relation) return prop;
|
|
1975
|
+
const addressed = prop.relation?.relationName;
|
|
1976
|
+
if (addressed && findRelation(resolved, addressed) === relation) return prop;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Must every row of this collection point at a target through this link?
|
|
1981
|
+
*
|
|
1982
|
+
* Read from the declaring property's `validation.required` — the same key every
|
|
1983
|
+
* other field uses, and the only place it lives.
|
|
1984
|
+
*
|
|
1985
|
+
* `RelationBase` carried its own `validation.required` until 0.18, which made
|
|
1986
|
+
* this two questions rather than one. They were answered by different readers:
|
|
1987
|
+
* the Postgres DDL generator asked the property (so the foreign-key column was
|
|
1988
|
+
* `NOT NULL`) and the SDK type generator asked the relation (so the generated
|
|
1989
|
+
* `Insert` type made the field optional). A `create()` that left the relation
|
|
1990
|
+
* out therefore typechecked and then failed at the database with a not-null
|
|
1991
|
+
* violation, and the two `required`s had to be written twice, identically, for
|
|
1992
|
+
* the pair to agree.
|
|
1993
|
+
*
|
|
1994
|
+
* A relation with no declaring property — an entry in `relations` nothing
|
|
1995
|
+
* points at — is not required. There is no field to fill in.
|
|
1996
|
+
*/
|
|
1997
|
+
function isRelationRequired(collection, relation) {
|
|
1998
|
+
return Boolean(relationDeclaringProperty(collection, relation)?.validation?.required);
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* The table a collection reads and writes.
|
|
2002
|
+
*
|
|
2003
|
+
* `table` when it is set, otherwise `toSnakeCase(slug)` — which is what made it
|
|
2004
|
+
* safe to drop `table` from the required fields on the config type: the runtime
|
|
2005
|
+
* had always derived it, and the type was demanding a value it did not need.
|
|
2006
|
+
*
|
|
2007
|
+
* The `||` chain is load-bearing. `toSnakeCase(undefined)` returns `""`, not
|
|
2008
|
+
* `undefined`, so the previous `??` chain short-circuited on the empty string
|
|
2009
|
+
* and the name fallback could never run — a safety net that read like one and
|
|
2010
|
+
* caught nothing. It was unreachable while `slug` was required; it stops being
|
|
2011
|
+
* unreachable the moment anything constructs a config without one.
|
|
2012
|
+
*/
|
|
1845
2013
|
function getTableName(collection) {
|
|
1846
|
-
|
|
1847
|
-
return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
|
|
2014
|
+
return (isRelationalCollectionConfig(collection) ? collection.table : void 0) || toSnakeCase(collection.slug) || toSnakeCase(collection.name);
|
|
1848
2015
|
}
|
|
1849
2016
|
/**
|
|
1850
2017
|
* The field key a database column is served and addressed under.
|
|
@@ -2012,6 +2179,17 @@ var FOREIGN_CONVENTION_UIDS = /* @__PURE__ */ new Map([
|
|
|
2012
2179
|
]);
|
|
2013
2180
|
new RegExp(String.raw`rebase\.uid\(\)\s*=\s*'(${[...FOREIGN_CONVENTION_UIDS.keys()].join("|")})'`, "i");
|
|
2014
2181
|
//#endregion
|
|
2182
|
+
//#region ../common/src/util/builders.ts
|
|
2183
|
+
/**
|
|
2184
|
+
* At runtime this is a plain identity function: a resource handle written where
|
|
2185
|
+
* a key belongs — `dataSource: analytics` — becomes its key, so past this point
|
|
2186
|
+
* a collection is plain data. The signature above is the rest of the point.
|
|
2187
|
+
* @group Builder
|
|
2188
|
+
*/
|
|
2189
|
+
function defineCollection(collection) {
|
|
2190
|
+
return resolveResourceRefs(collection);
|
|
2191
|
+
}
|
|
2192
|
+
//#endregion
|
|
2015
2193
|
//#region ../common/src/util/auth-default-policies.ts
|
|
2016
2194
|
/**
|
|
2017
2195
|
* Default RLS policies injected by the schema generator.
|
|
@@ -2880,7 +3058,7 @@ var CollectionRegistry = class {
|
|
|
2880
3058
|
dataSources = {};
|
|
2881
3059
|
/**
|
|
2882
3060
|
* Global lifecycle callbacks applied to every collection.
|
|
2883
|
-
* Runs on all data paths (REST, WebSocket, `rebase.
|
|
3061
|
+
* Runs on all data paths (REST, WebSocket, `rebase.dataAsAdmin`).
|
|
2884
3062
|
* Execution order: global → collection → property callbacks.
|
|
2885
3063
|
*/
|
|
2886
3064
|
_globalCallbacks;
|
|
@@ -3018,7 +3196,7 @@ var CollectionRegistry = class {
|
|
|
3018
3196
|
else {
|
|
3019
3197
|
const declared = resolveCollectionRelations(collection)[key];
|
|
3020
3198
|
if (declared) relationProperty.resolvedRelation = declared;
|
|
3021
|
-
else console.warn(`Relation property '${key}' on '${collection.slug}'
|
|
3199
|
+
else console.warn(`Relation property '${key}' on '${collection.slug}' names no relation: it has no \`relation\` block, and the collection's \`relations\` array has no entry called '${key}'. The field will render no picker, generate no foreign key, and return nothing from \`include()\`.`);
|
|
3022
3200
|
}
|
|
3023
3201
|
}
|
|
3024
3202
|
return newProperty;
|
|
@@ -3111,6 +3289,122 @@ var CollectionRegistry = class {
|
|
|
3111
3289
|
}
|
|
3112
3290
|
};
|
|
3113
3291
|
//#endregion
|
|
3292
|
+
//#region ../common/src/collections/default-collections.ts
|
|
3293
|
+
/**
|
|
3294
|
+
* Default users collection.
|
|
3295
|
+
*
|
|
3296
|
+
* Prepended to the developer's collections array by the admin and server.
|
|
3297
|
+
* Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
|
|
3298
|
+
* override by defining their own collection with `slug: "users"`.
|
|
3299
|
+
*
|
|
3300
|
+
* Schema only — no `admin` block. This package is on the backend's dependency path,
|
|
3301
|
+
* where that field does not exist: `@rebasepro/cms-types` adds it by declaration
|
|
3302
|
+
* merging, and a BaaS install never installs that. The scaffolded
|
|
3303
|
+
* `config/collections/users.ts` carries the presentation for projects that want this
|
|
3304
|
+
* collection in their panel, which is also where it is editable.
|
|
3305
|
+
*/
|
|
3306
|
+
var defaultUsersCollection = defineCollection({
|
|
3307
|
+
name: "Users",
|
|
3308
|
+
singularName: "User",
|
|
3309
|
+
slug: "users",
|
|
3310
|
+
auth: true,
|
|
3311
|
+
table: "users",
|
|
3312
|
+
schema: "rebase",
|
|
3313
|
+
securityRules: [{
|
|
3314
|
+
operation: "select",
|
|
3315
|
+
roles: ["admin"]
|
|
3316
|
+
}, {
|
|
3317
|
+
operations: [
|
|
3318
|
+
"insert",
|
|
3319
|
+
"update",
|
|
3320
|
+
"delete"
|
|
3321
|
+
],
|
|
3322
|
+
roles: ["admin"]
|
|
3323
|
+
}],
|
|
3324
|
+
properties: {
|
|
3325
|
+
id: {
|
|
3326
|
+
name: "ID",
|
|
3327
|
+
type: "string",
|
|
3328
|
+
isId: "uuid"
|
|
3329
|
+
},
|
|
3330
|
+
email: {
|
|
3331
|
+
name: "Email",
|
|
3332
|
+
type: "string",
|
|
3333
|
+
validation: {
|
|
3334
|
+
required: true,
|
|
3335
|
+
unique: true
|
|
3336
|
+
}
|
|
3337
|
+
},
|
|
3338
|
+
displayName: {
|
|
3339
|
+
name: "Name",
|
|
3340
|
+
type: "string",
|
|
3341
|
+
columnName: "display_name",
|
|
3342
|
+
validation: { required: true }
|
|
3343
|
+
},
|
|
3344
|
+
photoURL: {
|
|
3345
|
+
name: "Photo URL",
|
|
3346
|
+
type: "string",
|
|
3347
|
+
columnName: "photo_url"
|
|
3348
|
+
},
|
|
3349
|
+
roles: {
|
|
3350
|
+
name: "Roles",
|
|
3351
|
+
type: "array",
|
|
3352
|
+
columnType: "text[]",
|
|
3353
|
+
of: {
|
|
3354
|
+
name: "Role",
|
|
3355
|
+
type: "string",
|
|
3356
|
+
enum: {
|
|
3357
|
+
admin: "Admin",
|
|
3358
|
+
editor: "Editor",
|
|
3359
|
+
viewer: "Viewer"
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
},
|
|
3363
|
+
passwordHash: {
|
|
3364
|
+
name: "Password Hash",
|
|
3365
|
+
type: "string",
|
|
3366
|
+
columnName: "password_hash",
|
|
3367
|
+
excludeFromApi: true
|
|
3368
|
+
},
|
|
3369
|
+
emailVerified: {
|
|
3370
|
+
name: "Email Verified",
|
|
3371
|
+
type: "boolean",
|
|
3372
|
+
columnName: "email_verified",
|
|
3373
|
+
defaultValue: false
|
|
3374
|
+
},
|
|
3375
|
+
emailVerificationToken: {
|
|
3376
|
+
name: "Email Verification Token",
|
|
3377
|
+
type: "string",
|
|
3378
|
+
columnName: "email_verification_token",
|
|
3379
|
+
excludeFromApi: true
|
|
3380
|
+
},
|
|
3381
|
+
emailVerificationSentAt: {
|
|
3382
|
+
name: "Email Verification Sent At",
|
|
3383
|
+
type: "date",
|
|
3384
|
+
columnName: "email_verification_sent_at"
|
|
3385
|
+
},
|
|
3386
|
+
metadata: {
|
|
3387
|
+
name: "Metadata",
|
|
3388
|
+
type: "map",
|
|
3389
|
+
keyValue: true,
|
|
3390
|
+
properties: {},
|
|
3391
|
+
defaultValue: {}
|
|
3392
|
+
},
|
|
3393
|
+
createdAt: {
|
|
3394
|
+
name: "Created At",
|
|
3395
|
+
type: "date",
|
|
3396
|
+
columnName: "created_at",
|
|
3397
|
+
autoValue: "on_create"
|
|
3398
|
+
},
|
|
3399
|
+
updatedAt: {
|
|
3400
|
+
name: "Updated At",
|
|
3401
|
+
type: "date",
|
|
3402
|
+
columnName: "updated_at",
|
|
3403
|
+
autoValue: "on_update"
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
});
|
|
3407
|
+
//#endregion
|
|
3114
3408
|
//#region ../common/src/data/sort-dialect.ts
|
|
3115
3409
|
/**
|
|
3116
3410
|
* Sort-order wire codec.
|
|
@@ -3146,7 +3440,23 @@ function normalizeOrderBy(orderBy) {
|
|
|
3146
3440
|
if (!orderBy || orderBy.length === 0) return void 0;
|
|
3147
3441
|
const list = Array.isArray(orderBy[0]) ? orderBy : [orderBy];
|
|
3148
3442
|
if (list.length === 0) return void 0;
|
|
3149
|
-
return list.map((
|
|
3443
|
+
return list.map((entry, index) => toStrictTuple(entry, index));
|
|
3444
|
+
}
|
|
3445
|
+
/** A sort whose *shape* is unusable, as opposed to one naming a field that does not exist. */
|
|
3446
|
+
var OrderBySpecError = class extends Error {
|
|
3447
|
+
code = "INVALID_ORDER_BY";
|
|
3448
|
+
constructor(detail) {
|
|
3449
|
+
super(`Invalid \`orderBy\`: ${detail}. Expected a field name, or a list of [field, direction] pairs like [["roles","asc"],["created_at","desc"]]`);
|
|
3450
|
+
this.name = "OrderBySpecError";
|
|
3451
|
+
}
|
|
3452
|
+
};
|
|
3453
|
+
function toStrictTuple(raw, index) {
|
|
3454
|
+
if (!Array.isArray(raw)) throw new OrderBySpecError(`entry ${index} has no field name`);
|
|
3455
|
+
const key = isRelationAggregateSort(raw[0]) ? sortKeyToString(raw[0]) : raw[0];
|
|
3456
|
+
if (typeof key !== "string" || key.trim() === "") throw new OrderBySpecError(`entry ${index} has no field name`);
|
|
3457
|
+
const direction = raw[1];
|
|
3458
|
+
if (direction !== void 0 && direction !== "asc" && direction !== "desc") throw new OrderBySpecError(`entry ${index} has direction '${String(direction)}'`);
|
|
3459
|
+
return [key, direction ?? "asc"];
|
|
3150
3460
|
}
|
|
3151
3461
|
/**
|
|
3152
3462
|
* Serialize a sort to the wire.
|
|
@@ -3192,7 +3502,11 @@ var QueryBuilder = class {
|
|
|
3192
3502
|
}
|
|
3193
3503
|
where(columnOrCondition, operator, value) {
|
|
3194
3504
|
if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
|
|
3195
|
-
|
|
3505
|
+
const next = columnOrCondition;
|
|
3506
|
+
this.params.logical = this.params.logical ? {
|
|
3507
|
+
type: "and",
|
|
3508
|
+
conditions: [this.params.logical, next]
|
|
3509
|
+
} : next;
|
|
3196
3510
|
return this;
|
|
3197
3511
|
}
|
|
3198
3512
|
if (!this.params.where) this.params.where = {};
|
|
@@ -3777,6 +4091,36 @@ function readTuple(field, raw) {
|
|
|
3777
4091
|
if (isOperatorShaped(op)) throw new UnknownFilterOperatorError(field, op);
|
|
3778
4092
|
}
|
|
3779
4093
|
/**
|
|
4094
|
+
* Encode the `<op>.<value>` half of a wire condition.
|
|
4095
|
+
*
|
|
4096
|
+
* This is the single leaf encoder. Both wire positions that carry a condition
|
|
4097
|
+
* — a top-level query parameter (`?status=eq.active`) and a leaf inside an
|
|
4098
|
+
* `and(...)`/`or(...)` group (`or(status.eq.active,…)`) — go through it, so a
|
|
4099
|
+
* rule expressed here holds in both. The group serializer used to carry its
|
|
4100
|
+
* own copy, and the copy had drifted on every rule that matters: `null` went
|
|
4101
|
+
* out as the four-character string, the empty list as `()`, and an operator
|
|
4102
|
+
* this dialect does not have was silently rewritten to `eq` — a filter that
|
|
4103
|
+
* ran, returned rows, and answered a different question than the one asked.
|
|
4104
|
+
*
|
|
4105
|
+
* `escapeScalar` is the one thing the two positions legitimately disagree
|
|
4106
|
+
* about. A scalar in a query parameter owns the whole value and needs no
|
|
4107
|
+
* escaping; a scalar inside a group sits between the same commas a list item
|
|
4108
|
+
* does, so a comma in it would end the condition early.
|
|
4109
|
+
*/
|
|
4110
|
+
function serializeOperatorAndValue(op, value, { escapeScalar, where }) {
|
|
4111
|
+
if (typeof op !== "string") throw new TypeError(`${where}: operator must be a string, got ${typeof op}`);
|
|
4112
|
+
const restOp = CANONICAL_OP_LOOKUP.get(op);
|
|
4113
|
+
if (!restOp) throw new TypeError(`${where}: unknown operator "${op}". Valid operators: ${Object.keys(CANONICAL_TO_REST).join(", ")}`);
|
|
4114
|
+
if (value === null && (op === "==" || op === "!=")) return op === "==" ? "isnull.null" : "notnull.null";
|
|
4115
|
+
if (NULL_OPS.has(op)) return `${restOp}.null`;
|
|
4116
|
+
if (Array.isArray(value)) {
|
|
4117
|
+
if (value.length === 0) return `${restOp}.(${EMPTY_LIST_TOKEN})`;
|
|
4118
|
+
return `${restOp}.(${value.map((v) => escapeWireValue(stringifyValue(v))).join(",")})`;
|
|
4119
|
+
}
|
|
4120
|
+
const scalar = stringifyValue(value);
|
|
4121
|
+
return `${restOp}.${escapeScalar ? escapeWireValue(scalar) : scalar}`;
|
|
4122
|
+
}
|
|
4123
|
+
/**
|
|
3780
4124
|
* Serialize a single canonical condition tuple to a PostgREST dot-string.
|
|
3781
4125
|
*
|
|
3782
4126
|
* Throws `TypeError` if the input is not a valid `[WhereFilterOp, unknown]` tuple.
|
|
@@ -3789,15 +4133,10 @@ function readTuple(field, raw) {
|
|
|
3789
4133
|
function serializeTuple(tuple) {
|
|
3790
4134
|
if (!Array.isArray(tuple) || tuple.length !== 2) throw new TypeError(`serializeTuple: expected a [WhereFilterOp, value] tuple, got ${JSON.stringify(tuple)}`);
|
|
3791
4135
|
const [op, value] = tuple;
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
if (Array.isArray(value)) {
|
|
3797
|
-
if (value.length === 0) return `${restOp}.(${EMPTY_LIST_TOKEN})`;
|
|
3798
|
-
return `${restOp}.(${value.map((v) => escapeWireValue(stringifyValue(v))).join(",")})`;
|
|
3799
|
-
}
|
|
3800
|
-
return `${restOp}.${stringifyValue(value)}`;
|
|
4136
|
+
return serializeOperatorAndValue(op, value, {
|
|
4137
|
+
escapeScalar: false,
|
|
4138
|
+
where: "serializeTuple"
|
|
4139
|
+
});
|
|
3801
4140
|
}
|
|
3802
4141
|
/**
|
|
3803
4142
|
* Convert `FilterValues` (or `WireFilterValues`) to a PostgREST-style
|
|
@@ -3902,10 +4241,21 @@ function deserializeFilter(query) {
|
|
|
3902
4241
|
/**
|
|
3903
4242
|
* Serialize a `LogicalCondition` or `FilterCondition` to its wire-format string.
|
|
3904
4243
|
*
|
|
4244
|
+
* Leaf encoding is {@link serializeOperatorAndValue}, the same function
|
|
4245
|
+
* `serializeTuple` uses, so `null`, the empty list and an unknown operator
|
|
4246
|
+
* behave identically inside a group and in a query parameter.
|
|
4247
|
+
*
|
|
4248
|
+
* @throws {TypeError} when a leaf names an operator this dialect does not have.
|
|
4249
|
+
* It used to fall back to `eq`, which turned `age >= 18` into `age = 18` with
|
|
4250
|
+
* no diagnostic anywhere.
|
|
4251
|
+
*
|
|
3905
4252
|
* @example
|
|
3906
4253
|
* serializeLogicalCondition({ column: "status", operator: "==", value: "active" })
|
|
3907
4254
|
* // → "status.eq.active"
|
|
3908
4255
|
*
|
|
4256
|
+
* serializeLogicalCondition({ column: "deleted_at", operator: "==", value: null })
|
|
4257
|
+
* // → "deleted_at.isnull.null"
|
|
4258
|
+
*
|
|
3909
4259
|
* serializeLogicalCondition({ type: "or", conditions: [...] })
|
|
3910
4260
|
* // → "or(status.eq.active,status.eq.pending)"
|
|
3911
4261
|
*/
|
|
@@ -3914,12 +4264,60 @@ function serializeLogicalCondition(cond) {
|
|
|
3914
4264
|
const inner = (cond.conditions ?? []).map(serializeLogicalCondition).join(",");
|
|
3915
4265
|
return `${cond.type}(${inner})`;
|
|
3916
4266
|
}
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
4267
|
+
return `${escapeWireValue(cond.column)}.${serializeOperatorAndValue(cond.operator, cond.value, {
|
|
4268
|
+
escapeScalar: true,
|
|
4269
|
+
where: "serializeLogicalCondition"
|
|
4270
|
+
})}`;
|
|
4271
|
+
}
|
|
4272
|
+
/**
|
|
4273
|
+
* Split a leaf condition into `column`, operator token and value.
|
|
4274
|
+
*
|
|
4275
|
+
* The naive reading — column is everything before the first dot, operator is
|
|
4276
|
+
* everything up to the second — cannot express a relation path. A filter on
|
|
4277
|
+
* `author.name` serializes to `author.name.eq.bob` and came back as the column
|
|
4278
|
+
* `author` with the operator `name`, which resolves to nothing, so the
|
|
4279
|
+
* fallback made it `author == "eq.bob"`: a condition that runs and matches
|
|
4280
|
+
* nothing, on a column the caller never named.
|
|
4281
|
+
*
|
|
4282
|
+
* So the operator is found rather than assumed: it is the first dot-separated
|
|
4283
|
+
* segment after the column that resolves to a real operator. Everything before
|
|
4284
|
+
* it is the column, everything after is the value. `version.eq.1.2.3` reads as
|
|
4285
|
+
* `version == "1.2.3"` because the scan stops at the first match — the `eq` at
|
|
4286
|
+
* offset 1, not a later segment — and `metadata->>x.eq.5` never had dots in the
|
|
4287
|
+
* column to begin with.
|
|
4288
|
+
*
|
|
4289
|
+
* Returns `undefined` when no segment resolves — `status.active`, an equality
|
|
4290
|
+
* written without an operator, which the caller handles.
|
|
4291
|
+
*/
|
|
4292
|
+
function splitLeafCondition(str) {
|
|
4293
|
+
let limit = str.length;
|
|
4294
|
+
for (let i = 0; i < str.length; i++) {
|
|
4295
|
+
if (str[i] === "\\") {
|
|
4296
|
+
i++;
|
|
4297
|
+
continue;
|
|
4298
|
+
}
|
|
4299
|
+
if (str[i] === "(") {
|
|
4300
|
+
limit = i;
|
|
4301
|
+
break;
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
const dots = [];
|
|
4305
|
+
for (let i = 0; i < limit; i++) {
|
|
4306
|
+
if (str[i] === "\\") {
|
|
4307
|
+
i++;
|
|
4308
|
+
continue;
|
|
4309
|
+
}
|
|
4310
|
+
if (str[i] === ".") dots.push(i);
|
|
4311
|
+
}
|
|
4312
|
+
for (let i = 1; i < dots.length; i++) {
|
|
4313
|
+
const operator = toCanonicalOp(str.substring(dots[i - 1] + 1, dots[i]));
|
|
4314
|
+
if (!operator) continue;
|
|
4315
|
+
return {
|
|
4316
|
+
column: unescapeWireValue(str.substring(0, dots[i - 1])),
|
|
4317
|
+
operator,
|
|
4318
|
+
value: str.substring(dots[i] + 1)
|
|
4319
|
+
};
|
|
3921
4320
|
}
|
|
3922
|
-
return `${cond.column}.${restOp}.${escapeWireValue(stringifyValue(cond.value))}`;
|
|
3923
4321
|
}
|
|
3924
4322
|
function deserializeLogicalCondition(str, nesting = 0) {
|
|
3925
4323
|
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)\`.`);
|
|
@@ -3932,28 +4330,34 @@ function deserializeLogicalCondition(str, nesting = 0) {
|
|
|
3932
4330
|
conditions: splitGroupItems(innerStr).map((part) => deserializeLogicalCondition(part, nesting + 1))
|
|
3933
4331
|
};
|
|
3934
4332
|
}
|
|
3935
|
-
const
|
|
3936
|
-
if (
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
}
|
|
3949
|
-
const
|
|
3950
|
-
|
|
3951
|
-
const operator = toCanonicalOp(opStr) ?? "==";
|
|
3952
|
-
if (valueStr.startsWith("(") && valueStr.endsWith(")")) return {
|
|
4333
|
+
const leaf = splitLeafCondition(str);
|
|
4334
|
+
if (!leaf) {
|
|
4335
|
+
const firstDot = str.indexOf(".");
|
|
4336
|
+
if (firstDot === -1) return {
|
|
4337
|
+
column: unescapeWireValue(str),
|
|
4338
|
+
operator: "==",
|
|
4339
|
+
value: true
|
|
4340
|
+
};
|
|
4341
|
+
return {
|
|
4342
|
+
column: unescapeWireValue(str.substring(0, firstDot)),
|
|
4343
|
+
operator: "==",
|
|
4344
|
+
value: unescapeWireValue(str.substring(firstDot + 1))
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
const { column, operator, value: valueStr } = leaf;
|
|
4348
|
+
if (NULL_OPS.has(operator)) return {
|
|
3953
4349
|
column,
|
|
3954
4350
|
operator,
|
|
3955
|
-
value:
|
|
4351
|
+
value: null
|
|
3956
4352
|
};
|
|
4353
|
+
if (valueStr.startsWith("(") && valueStr.endsWith(")")) {
|
|
4354
|
+
const inner = valueStr.slice(1, -1);
|
|
4355
|
+
return {
|
|
4356
|
+
column,
|
|
4357
|
+
operator,
|
|
4358
|
+
value: inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner)
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
3957
4361
|
return {
|
|
3958
4362
|
column,
|
|
3959
4363
|
operator,
|
|
@@ -3962,6 +4366,15 @@ function deserializeLogicalCondition(str, nesting = 0) {
|
|
|
3962
4366
|
}
|
|
3963
4367
|
//#endregion
|
|
3964
4368
|
//#region ../common/src/data/buildRebaseData.ts
|
|
4369
|
+
/**
|
|
4370
|
+
* What a client says when its data source cannot subscribe.
|
|
4371
|
+
*
|
|
4372
|
+
* Named rather than inlined so the sentence a caller sees does not depend on
|
|
4373
|
+
* which of the two adapters below happened to build the client.
|
|
4374
|
+
*/
|
|
4375
|
+
var noRealtime = (slug) => `Realtime is not available for "${slug}": its data source does not support subscriptions.`;
|
|
4376
|
+
/** What a client says when its data source cannot count. */
|
|
4377
|
+
var noCount = (slug) => `Counting is not available for "${slug}": its data source does not support it.`;
|
|
3965
4378
|
function createPrimaryKeyResolver(options) {
|
|
3966
4379
|
const cache = /* @__PURE__ */ new Map();
|
|
3967
4380
|
const warned = /* @__PURE__ */ new Set();
|
|
@@ -4256,7 +4669,11 @@ var SdkQueryBuilder = class {
|
|
|
4256
4669
|
}
|
|
4257
4670
|
where(columnOrCondition, operator, value) {
|
|
4258
4671
|
if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
|
|
4259
|
-
|
|
4672
|
+
const next = columnOrCondition;
|
|
4673
|
+
this.params.logical = this.params.logical ? {
|
|
4674
|
+
type: "and",
|
|
4675
|
+
conditions: [this.params.logical, next]
|
|
4676
|
+
} : next;
|
|
4260
4677
|
return this;
|
|
4261
4678
|
}
|
|
4262
4679
|
if (!this.params.where) this.params.where = {};
|
|
@@ -4276,7 +4693,7 @@ var SdkQueryBuilder = class {
|
|
|
4276
4693
|
/** Called again, this adds a tie-breaker rather than replacing the sort. */
|
|
4277
4694
|
orderBy(column, direction = "asc") {
|
|
4278
4695
|
const existing = normalizeOrderBy(this.params.orderBy) ?? [];
|
|
4279
|
-
this.params.orderBy = [...existing, [column, direction]];
|
|
4696
|
+
this.params.orderBy = [...existing, [sortKeyToString(column), direction]];
|
|
4280
4697
|
return this;
|
|
4281
4698
|
}
|
|
4282
4699
|
limit(count) {
|
|
@@ -4308,11 +4725,39 @@ var SdkQueryBuilder = class {
|
|
|
4308
4725
|
async find() {
|
|
4309
4726
|
return this.client.find(this.params);
|
|
4310
4727
|
}
|
|
4728
|
+
/**
|
|
4729
|
+
* Page through everything this query matches, one row at a time.
|
|
4730
|
+
*
|
|
4731
|
+
* `.limit()` on the builder becomes the page size, so the ceiling on a
|
|
4732
|
+
* single `find()` is not a ceiling on what the query can read.
|
|
4733
|
+
*/
|
|
4734
|
+
iterate(options) {
|
|
4735
|
+
return this.client.iterate({
|
|
4736
|
+
...this.params,
|
|
4737
|
+
...this.params.limit !== void 0 && { pageSize: this.params.limit },
|
|
4738
|
+
...options
|
|
4739
|
+
});
|
|
4740
|
+
}
|
|
4741
|
+
/** Collect everything this query matches into one array. */
|
|
4742
|
+
findAll(options) {
|
|
4743
|
+
return this.client.findAll({
|
|
4744
|
+
...this.params,
|
|
4745
|
+
...this.params.limit !== void 0 && { pageSize: this.params.limit },
|
|
4746
|
+
...options
|
|
4747
|
+
});
|
|
4748
|
+
}
|
|
4749
|
+
/**
|
|
4750
|
+
* Count the records matching this query.
|
|
4751
|
+
*
|
|
4752
|
+
* This used to answer `0` when the client had no `count` — a number, from a
|
|
4753
|
+
* source that had not counted anything, indistinguishable from an empty
|
|
4754
|
+
* collection. It now does what the client does, which on a source that
|
|
4755
|
+
* cannot count is throw and say so.
|
|
4756
|
+
*/
|
|
4311
4757
|
async count() {
|
|
4312
|
-
return this.client.count
|
|
4758
|
+
return this.client.count(this.params);
|
|
4313
4759
|
}
|
|
4314
4760
|
listen(onUpdate, onError) {
|
|
4315
|
-
if (!this.client.listen) throw new Error("Listen is only available when the driver supports realtime.");
|
|
4316
4761
|
return this.client.listen(this.params, onUpdate, onError);
|
|
4317
4762
|
}
|
|
4318
4763
|
};
|
|
@@ -4340,6 +4785,14 @@ function toSdkCollectionClient(snap, slug = "collection") {
|
|
|
4340
4785
|
const s = await snap.findById(id);
|
|
4341
4786
|
return s ? entityToRow(s) : void 0;
|
|
4342
4787
|
},
|
|
4788
|
+
async get(id) {
|
|
4789
|
+
const s = await snap.findById(id);
|
|
4790
|
+
if (!s) throw new RebaseApiError(`No record with id ${JSON.stringify(String(id))} in "${slug}".`, {
|
|
4791
|
+
status: 404,
|
|
4792
|
+
code: "NOT_FOUND"
|
|
4793
|
+
});
|
|
4794
|
+
return entityToRow(s);
|
|
4795
|
+
},
|
|
4343
4796
|
async create(data, id) {
|
|
4344
4797
|
return entityToRow(await snap.create(data, id));
|
|
4345
4798
|
},
|
|
@@ -4370,12 +4823,12 @@ function toSdkCollectionClient(snap, slug = "collection") {
|
|
|
4370
4823
|
if (!snap.deleteMany) throw new Error("Bulk deletes are not supported by this collection's data source. Fall back to delete() per record.");
|
|
4371
4824
|
await snap.deleteMany(ids);
|
|
4372
4825
|
},
|
|
4373
|
-
count: snap.count ? (params) => snap.count(params) :
|
|
4826
|
+
count: snap.count ? (params) => snap.count(params) : unsupportedMethod(noCount(slug)),
|
|
4374
4827
|
listen: snap.listen ? (params, onUpdate, onError) => snap.listen(params, (res) => onUpdate({
|
|
4375
4828
|
data: res.data.map(entityToRow),
|
|
4376
4829
|
meta: res.meta
|
|
4377
|
-
}), onError) :
|
|
4378
|
-
listenById: snap.listenById ? (id, onUpdate, onError) => snap.listenById(id, (s) => onUpdate(s ? entityToRow(s) : void 0), onError) :
|
|
4830
|
+
}), onError) : unsupportedMethod(noRealtime(slug)),
|
|
4831
|
+
listenById: snap.listenById ? (id, onUpdate, onError) => snap.listenById(id, (s) => onUpdate(s ? entityToRow(s) : void 0), onError) : unsupportedMethod(noRealtime(slug)),
|
|
4379
4832
|
where(columnOrCondition, operator, value) {
|
|
4380
4833
|
const builder = new SdkQueryBuilder(client);
|
|
4381
4834
|
if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
|
|
@@ -4419,7 +4872,7 @@ function wrapAsSdkData(entityData) {
|
|
|
4419
4872
|
* Build a flat {@link RebaseSdkData} from a `DataDriver`.
|
|
4420
4873
|
*
|
|
4421
4874
|
* This is the developer-facing SDK data layer used by backend framework
|
|
4422
|
-
* callbacks & scripts (`context.data` / `rebase.
|
|
4875
|
+
* callbacks & scripts (`context.data` / `rebase.dataAsAdmin`). It returns flat rows —
|
|
4423
4876
|
* identical in shape to the frontend SDK client, down to how a relation is
|
|
4424
4877
|
* served: a foreign key stays a foreign key, and a relation named in `include`
|
|
4425
4878
|
* arrives as the target's own columns. The `{ __type: "relation" }` envelope is
|
|
@@ -4474,6 +4927,6 @@ function buildRoutedRebaseData({ defaultData, sources, resolveKey }) {
|
|
|
4474
4927
|
} });
|
|
4475
4928
|
}
|
|
4476
4929
|
//#endregion
|
|
4477
|
-
export {
|
|
4930
|
+
export { ANONYMOUS_USER_ID as A, buildCompositeId as C, ListLimitError as D, toSnakeCase as E, Vector as F, RebaseApiError as I, RebaseClientError as L, EntityReference as M, EntityRelation as N, MAX_LIST_LIMIT as O, GeoPoint as P, isUnsupported as R, resolveCollectionRelations as S, suggestNearMiss as T, getEffectiveSecurityRules as _, deserializeLogicalCondition as a, getTableName as b, collectAllPages as c, normalizeOrderBy as d, serializeOrderBy as f, resolveDataSource as g, createDataSourceRegistry as h, deserializeFilter as i, isAnonymousUid as j, resolveClientListLimit as k, paginateFind as l, CollectionRegistry as m, buildSdkData as n, serializeFilter as o, defaultUsersCollection as p, UnknownFilterOperatorError as r, serializeLogicalCondition as s, buildRoutedRebaseData as t, resolveFindWindow as u, fieldKeyForColumn as v, resolvePrimaryKeys as w, isRelationRequired as x, findRelation as y, unsupportedMethod as z };
|
|
4478
4931
|
|
|
4479
|
-
//# sourceMappingURL=src-
|
|
4932
|
+
//# sourceMappingURL=src-Dq-I3Ybx.js.map
|