@rebasepro/types 0.23.0 → 0.24.1-canary.g19596ef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/call_context.d.ts +8 -4
  2. package/dist/controllers/client.d.ts +113 -24
  3. package/dist/controllers/collection_registry.d.ts +17 -0
  4. package/dist/controllers/data.d.ts +570 -122
  5. package/dist/controllers/data_driver.d.ts +85 -1
  6. package/dist/controllers/index.d.ts +1 -0
  7. package/dist/controllers/storage-references.d.ts +114 -0
  8. package/dist/controllers/storage.d.ts +73 -13
  9. package/dist/errors.d.ts +15 -1
  10. package/dist/index.es.js +1228 -43
  11. package/dist/index.es.js.map +1 -1
  12. package/dist/types/admin_block.d.ts +1 -1
  13. package/dist/types/api_keys.d.ts +68 -36
  14. package/dist/types/auth_adapter.d.ts +97 -2
  15. package/dist/types/backend.d.ts +356 -9
  16. package/dist/types/backup.d.ts +102 -0
  17. package/dist/types/collection_patch.d.ts +74 -0
  18. package/dist/types/collections.d.ts +23 -0
  19. package/dist/types/cron.d.ts +58 -7
  20. package/dist/types/database_adapter.d.ts +34 -8
  21. package/dist/types/entities.d.ts +11 -0
  22. package/dist/types/history.d.ts +15 -1
  23. package/dist/types/index.d.ts +3 -0
  24. package/dist/types/project_manifest.d.ts +73 -1
  25. package/dist/types/properties.d.ts +39 -2
  26. package/dist/types/register.d.ts +43 -0
  27. package/dist/types/relations.d.ts +37 -1
  28. package/dist/types/resource_kinds.d.ts +20 -10
  29. package/dist/types/schema_editing.d.ts +39 -13
  30. package/dist/types/scopes.d.ts +272 -0
  31. package/dist/types/storage_authorize.d.ts +10 -1
  32. package/dist/types/storage_source.d.ts +2 -6
  33. package/dist/types/websockets.d.ts +32 -1
  34. package/dist/users/user.d.ts +7 -0
  35. package/package.json +1 -1
package/dist/index.es.js CHANGED
@@ -499,6 +499,7 @@ var ADMIN_PROPERTY_KEYS = [
499
499
  "disabled",
500
500
  "expanded",
501
501
  "Field",
502
+ "filledByServer",
502
503
  "Filter",
503
504
  "filterOperators",
504
505
  "fixedFilter",
@@ -700,12 +701,29 @@ var DEFAULT_CAPABILITIES = {
700
701
  supportsDocumentAdmin: true,
701
702
  supportsSchemaAdmin: true
702
703
  };
703
- var CAPABILITIES_REGISTRY = {
704
- postgres: POSTGRES_CAPABILITIES,
705
- firestore: FIREBASE_CAPABILITIES,
706
- mongodb: MONGODB_CAPABILITIES,
707
- "(default)": DEFAULT_CAPABILITIES
708
- };
704
+ var BUILT_IN_CAPABILITIES = /* @__PURE__ */ new Map([
705
+ ["postgres", POSTGRES_CAPABILITIES],
706
+ ["firestore", FIREBASE_CAPABILITIES],
707
+ ["mongodb", MONGODB_CAPABILITIES],
708
+ ["(default)", DEFAULT_CAPABILITIES]
709
+ ]);
710
+ /**
711
+ * Capabilities registered at runtime, on `globalThis` rather than in a module
712
+ * local: a third-party driver inlines its own copy of this package and
713
+ * registers through it, while the app and the server read through theirs. A
714
+ * module local made the registration land in a copy nobody else reads.
715
+ * See `module-state-across-copies.test.ts`.
716
+ */
717
+ var REGISTERED_CAPABILITIES_KEY = Symbol.for("@rebasepro/types.dataSourceCapabilities.v1");
718
+ function registeredCapabilities() {
719
+ const g = globalThis;
720
+ let registered = g[REGISTERED_CAPABILITIES_KEY];
721
+ if (!registered) {
722
+ registered = /* @__PURE__ */ new Map();
723
+ g[REGISTERED_CAPABILITIES_KEY] = registered;
724
+ }
725
+ return registered;
726
+ }
709
727
  /**
710
728
  * Look up capabilities for a given engine key.
711
729
  * If `engine` is undefined or not found, returns `DEFAULT_CAPABILITIES`.
@@ -713,14 +731,14 @@ var CAPABILITIES_REGISTRY = {
713
731
  */
714
732
  function getDataSourceCapabilities(engine) {
715
733
  if (!engine) return POSTGRES_CAPABILITIES;
716
- return CAPABILITIES_REGISTRY[engine] ?? DEFAULT_CAPABILITIES;
734
+ return registeredCapabilities().get(engine) ?? BUILT_IN_CAPABILITIES.get(engine) ?? DEFAULT_CAPABILITIES;
717
735
  }
718
736
  /**
719
737
  * Register custom capabilities for a third-party driver.
720
738
  * @group Models
721
739
  */
722
740
  function registerDataSourceCapabilities(capabilities) {
723
- CAPABILITIES_REGISTRY[capabilities.key] = capabilities;
741
+ registeredCapabilities().set(capabilities.key, capabilities);
724
742
  }
725
743
  //#endregion
726
744
  //#region src/types/collections.ts
@@ -837,6 +855,23 @@ var RELEVANCE_SORT_FIELD = "_score";
837
855
  * @group Models
838
856
  */
839
857
  var JUNCTION_PIVOT_KEY = "_pivot";
858
+ /**
859
+ * The key a row carries the totals of its counted relations under.
860
+ *
861
+ * An include that asks for a count — `include: { tags: { limit: 4, count: true } }`
862
+ * — serves, beside the (capped) `tags`, how many related rows there are in all:
863
+ * `{ id: 1, tags: [ …4 rows ], _count: { tags: 37 } }`. One object per row,
864
+ * keyed by relation name, so counting a second relation adds a key rather than
865
+ * a second top-level name.
866
+ *
867
+ * Leading underscore, like {@link JUNCTION_PIVOT_KEY} and `_matches`: it
868
+ * describes the read, not the record, and no column or form writes it. A
869
+ * collection with a property of this name cannot ask for a count — the read
870
+ * answers 400 rather than writing over the column.
871
+ *
872
+ * @group Models
873
+ */
874
+ var INCLUDE_COUNT_KEY = "_count";
840
875
  /** @group Models */
841
876
  function hasForeignKeyOnTarget(relation) {
842
877
  return relation.kind === "hasOne" || relation.kind === "hasMany";
@@ -1131,14 +1166,281 @@ function isBranchAdmin(admin) {
1131
1166
  }
1132
1167
  //#endregion
1133
1168
  //#region src/types/schema_editing.ts
1134
- var DEFAULT_COMMIT_PATHS = {
1135
- schemaFile: "backend/src/schema.generated.ts",
1136
- ddlFile: "drizzle/schema.sql",
1137
- policiesFile: "drizzle/policies.sql",
1138
- searchFile: "drizzle/search.sql",
1139
- vectorFile: "drizzle/vector.sql",
1140
- triggersFile: "drizzle/triggers.sql"
1141
- };
1169
+ /**
1170
+ * The SQL `db push` and `db generate` render — `schema.sql`, `policies.sql` and
1171
+ * the rest — is not here, and must not come back. Both commands regenerate it
1172
+ * from the collections before reading it, so nothing ever read a committed
1173
+ * copy; it lives in the gitignored `.rebase/sql/`. When the commit carried it,
1174
+ * it carried a second copy besides: these paths are from the project root,
1175
+ * while the CLI writes beside the backend, so a live edit left `drizzle/` and
1176
+ * `backend/drizzle/` disagreeing about the schema.
1177
+ */
1178
+ var DEFAULT_COMMIT_PATHS = { schemaFile: "backend/src/schema.generated.ts" };
1179
+ //#endregion
1180
+ //#region src/types/collection_patch.ts
1181
+ /**
1182
+ * What changed about a collection, as opposed to what the collection is.
1183
+ *
1184
+ * ## Why a save is a patch
1185
+ *
1186
+ * The admin panel edits a collection as JSON — the only form a browser and a
1187
+ * server can exchange it in — and JSON cannot carry most of what a collection
1188
+ * file holds: an entity action's `onClick`, a computed field's `value`, a
1189
+ * property shared from another module (`status: statusProperty`), an enum
1190
+ * imported from elsewhere (`enum: LOCALE_ENUM`), the comments around all of
1191
+ * them. A save that posted the whole collection and had it written back
1192
+ * rewrote every key it carried and deleted everything JSON had dropped, so
1193
+ * renaming a collection removed its actions' handlers, inlined its shared
1194
+ * property without the custom `Field`, and froze a copy of the enum.
1195
+ *
1196
+ * So a save sends what the person changed: the difference between the JSON
1197
+ * the editor loaded and the JSON it is saving. Anything JSON could not carry
1198
+ * is identical on both sides of that difference — dropped from both — and
1199
+ * cannot appear in it. The server applies the patch to the collection as it
1200
+ * is (for the plan) and writes only the patched keys into the file.
1201
+ *
1202
+ * ## The shape
1203
+ *
1204
+ * Two operations on paths of object keys. Arrays are values: an array that
1205
+ * changed is set whole. Positions in an array are not stable identities —
1206
+ * element 2 of an enum is not "the third value" once a value is inserted —
1207
+ * and the writer is the one place that can match an old element to its new
1208
+ * self (by `key`/`id`/`name`) and keep what JSON dropped from it.
1209
+ *
1210
+ * Paths are in the AUTHORING shape — presentation keys under `admin`, the
1211
+ * shape the file has — see {@link nestCollectionPatchPaths}.
1212
+ */
1213
+ /**
1214
+ * Keys the runtime adds to a collection that must never be written into its
1215
+ * source. `resolvedRelation` is the registry's resolved form of a relation; a
1216
+ * file holding one does not type-check (`target` is missing from it).
1217
+ */
1218
+ var RUNTIME_ONLY_COLLECTION_KEYS = ["resolvedRelation"];
1219
+ /** Path segments that reach an object's prototype rather than a key of it. */
1220
+ var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set([
1221
+ "__proto__",
1222
+ "constructor",
1223
+ "prototype"
1224
+ ]);
1225
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1226
+ /** A JSON value as JSON would carry it: functions, `undefined` and symbols dropped. */
1227
+ function toCollectionJson(value) {
1228
+ if (value === void 0) return void 0;
1229
+ const text = JSON.stringify(value);
1230
+ return text === void 0 ? void 0 : JSON.parse(text);
1231
+ }
1232
+ function jsonEqual(a, b) {
1233
+ if (a === b) return true;
1234
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, index) => jsonEqual(item, b[index]));
1235
+ if (isPlainObject(a) && isPlainObject(b)) {
1236
+ const keys = Object.keys(a);
1237
+ if (keys.length !== Object.keys(b).length) return false;
1238
+ return keys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && jsonEqual(a[key], b[key]));
1239
+ }
1240
+ return false;
1241
+ }
1242
+ /**
1243
+ * The operations that take `before` to `after`, both JSON values.
1244
+ *
1245
+ * Objects are compared key by key; anything else that differs — a primitive,
1246
+ * an array, a change of kind — is set whole.
1247
+ */
1248
+ function diffJsonValues(before, after, path = []) {
1249
+ if (jsonEqual(before, after)) return [];
1250
+ if (isPlainObject(before) && isPlainObject(after)) {
1251
+ const ops = [];
1252
+ for (const key of Object.keys(after)) if (!Object.prototype.hasOwnProperty.call(before, key)) ops.push({
1253
+ op: "set",
1254
+ path: [...path, key],
1255
+ value: after[key]
1256
+ });
1257
+ else ops.push(...diffJsonValues(before[key], after[key], [...path, key]));
1258
+ for (const key of Object.keys(before)) if (!Object.prototype.hasOwnProperty.call(after, key)) ops.push({
1259
+ op: "remove",
1260
+ path: [...path, key]
1261
+ });
1262
+ return ops;
1263
+ }
1264
+ return [{
1265
+ op: "set",
1266
+ path,
1267
+ value: after
1268
+ }];
1269
+ }
1270
+ function step(position, segment) {
1271
+ switch (position) {
1272
+ case "collection": return segment === "properties" ? "properties" : "other";
1273
+ case "properties": return "property";
1274
+ case "property":
1275
+ if (segment === "properties") return "properties";
1276
+ if (segment === "of") return "property";
1277
+ if (segment === "oneOf") return "oneOf";
1278
+ return "other";
1279
+ case "oneOf": return segment === "properties" ? "properties" : "other";
1280
+ default: return "other";
1281
+ }
1282
+ }
1283
+ function nestPath(path) {
1284
+ const nested = [];
1285
+ let position = "collection";
1286
+ for (const segment of path) {
1287
+ const moved = position === "collection" && ADMIN_COLLECTION_KEYS.includes(segment) || position === "property" && ADMIN_PROPERTY_KEYS.includes(segment);
1288
+ if (moved) nested.push("admin");
1289
+ nested.push(segment);
1290
+ position = moved ? "other" : step(position, segment);
1291
+ }
1292
+ return {
1293
+ path: nested,
1294
+ position
1295
+ };
1296
+ }
1297
+ function nestValue(position, value) {
1298
+ if (position === "property") {
1299
+ if (Array.isArray(value)) return value.map((entry) => isPlainObject(entry) ? nestAdminPropertyKeys(entry) : entry);
1300
+ return isPlainObject(value) ? nestAdminPropertyKeys(value) : value;
1301
+ }
1302
+ if (position === "properties" && isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, property]) => [key, isPlainObject(property) ? nestAdminPropertyKeys(property) : property]));
1303
+ if (position === "oneOf" && isPlainObject(value)) return isPlainObject(value.properties) ? {
1304
+ ...value,
1305
+ properties: nestValue("properties", value.properties)
1306
+ } : value;
1307
+ if (position === "collection" && isPlainObject(value)) {
1308
+ const nested = nestAdminCollectionKeys(value);
1309
+ return isPlainObject(nested.properties) ? {
1310
+ ...nested,
1311
+ properties: nestValue("properties", nested.properties)
1312
+ } : nested;
1313
+ }
1314
+ return value;
1315
+ }
1316
+ /**
1317
+ * The patch, with every flat presentation key moved into its `admin` block.
1318
+ *
1319
+ * The panel edits a flat view model — `icon` beside `name`, a property's
1320
+ * `readOnly` beside its `type` — while the file keeps them under `admin`. The
1321
+ * difference is taken on the flat model, because that is where a cleared field
1322
+ * shows up as a removed key (the `admin` copy the model was loaded with still
1323
+ * holds the old value), and moved here.
1324
+ *
1325
+ * The flat value wins over the `admin` copy, as in {@link nestAdminKeysOf}:
1326
+ * operations that were already under `admin` are ordered first, so the flat
1327
+ * one — what the form just wrote — is applied last.
1328
+ */
1329
+ function nestCollectionPatchPaths(patch) {
1330
+ return patch.map((op, index) => {
1331
+ const { path, position } = nestPath(op.path);
1332
+ const wasNested = op.path.includes("admin");
1333
+ return {
1334
+ moved: op.op === "remove" ? {
1335
+ op: "remove",
1336
+ path
1337
+ } : {
1338
+ op: "set",
1339
+ path,
1340
+ value: nestValue(position, op.value)
1341
+ },
1342
+ wasNested,
1343
+ index
1344
+ };
1345
+ }).sort((a, b) => a.wasNested === b.wasNested ? a.index - b.index : a.wasNested ? -1 : 1).map((entry) => entry.moved);
1346
+ }
1347
+ /**
1348
+ * The patch that takes the collection the editor loaded to the one it is
1349
+ * saving, in the authoring shape. Empty when nothing changed.
1350
+ *
1351
+ * Both sides go through JSON first, so whatever JSON cannot carry — a
1352
+ * function, a value it drops — is equally absent from both and never appears
1353
+ * as a change.
1354
+ */
1355
+ function diffCollections(loaded, saving) {
1356
+ return nestCollectionPatchPaths(diffJsonValues(toCollectionJson(loaded), toCollectionJson(saving)));
1357
+ }
1358
+ /**
1359
+ * Why a patch may not be applied, or an empty list.
1360
+ *
1361
+ * Checked by every door that accepts one: a path is a list of non-empty object
1362
+ * keys that cannot reach a prototype, and no runtime-only key may be written.
1363
+ */
1364
+ function collectionPatchProblems(patch) {
1365
+ if (!Array.isArray(patch)) return ["`patch` must be a list of operations."];
1366
+ const problems = [];
1367
+ patch.forEach((op, index) => {
1368
+ const where = `patch[${index}]`;
1369
+ if (!isPlainObject(op) || op.op !== "set" && op.op !== "remove") {
1370
+ problems.push(`${where} must be { op: "set", path, value } or { op: "remove", path }.`);
1371
+ return;
1372
+ }
1373
+ const path = op.path;
1374
+ if (!Array.isArray(path) || path.length === 0 || !path.every((segment) => typeof segment === "string" && segment.length > 0)) {
1375
+ problems.push(`${where}.path must be a non-empty list of object keys.`);
1376
+ return;
1377
+ }
1378
+ const unsafe = path.find((segment) => UNSAFE_SEGMENTS.has(segment));
1379
+ if (unsafe) problems.push(`${where}.path may not contain "${unsafe}".`);
1380
+ const runtime = path.find((segment) => RUNTIME_ONLY_COLLECTION_KEYS.includes(segment));
1381
+ if (runtime) problems.push(`${where} writes "${runtime}", which the runtime adds and a collection file must not hold.`);
1382
+ if (op.op === "set") {
1383
+ const key = keyIn(op.value, (key) => UNSAFE_SEGMENTS.has(key) || RUNTIME_ONLY_COLLECTION_KEYS.includes(key));
1384
+ if (key) problems.push(`${where}.value holds "${key}", which a collection file must not hold.`);
1385
+ }
1386
+ });
1387
+ return problems;
1388
+ }
1389
+ /**
1390
+ * The first runtime-only key (see {@link RUNTIME_ONLY_COLLECTION_KEYS}) found
1391
+ * anywhere in a value, or `undefined`. For the doors that take a whole
1392
+ * collection rather than a patch.
1393
+ */
1394
+ function runtimeOnlyKeyIn(value) {
1395
+ return keyIn(value, (key) => RUNTIME_ONLY_COLLECTION_KEYS.includes(key));
1396
+ }
1397
+ /** A patch every door may apply: {@link collectionPatchProblems} found nothing. */
1398
+ function isCollectionPatch(patch) {
1399
+ return collectionPatchProblems(patch).length === 0;
1400
+ }
1401
+ function keyIn(value, matches) {
1402
+ if (Array.isArray(value)) {
1403
+ for (const item of value) {
1404
+ const found = keyIn(item, matches);
1405
+ if (found) return found;
1406
+ }
1407
+ return;
1408
+ }
1409
+ if (!isPlainObject(value)) return void 0;
1410
+ for (const key of Object.keys(value)) {
1411
+ if (matches(key)) return key;
1412
+ const found = keyIn(value[key], matches);
1413
+ if (found) return found;
1414
+ }
1415
+ }
1416
+ /**
1417
+ * `target` with the patch applied, copying only what it changes.
1418
+ *
1419
+ * Everything the patch does not reach is shared with `target` by reference —
1420
+ * functions, thunks and all — which is what lets a planner see a relation's
1421
+ * `target()` on the patched collection exactly as it was. Intermediate objects
1422
+ * a `set` needs are created; a `remove` of a key that is not there is nothing
1423
+ * to do.
1424
+ */
1425
+ function applyCollectionPatch(target, patch) {
1426
+ let root = target;
1427
+ for (const op of patch) root = applyOp(root, op.path, op);
1428
+ return root;
1429
+ }
1430
+ function applyOp(node, path, op) {
1431
+ const [head, ...rest] = path;
1432
+ if (UNSAFE_SEGMENTS.has(head)) throw new Error(`A collection patch may not reach "${head}".`);
1433
+ const base = isPlainObject(node) ? { ...node } : {};
1434
+ if (rest.length === 0) {
1435
+ if (op.op === "remove") delete base[head];
1436
+ else base[head] = op.value;
1437
+ return base;
1438
+ }
1439
+ const child = Object.prototype.hasOwnProperty.call(base, head) ? base[head] : void 0;
1440
+ if (op.op === "remove" && !isPlainObject(child)) return node;
1441
+ base[head] = applyOp(child, rest, op);
1442
+ return base;
1443
+ }
1142
1444
  //#endregion
1143
1445
  //#region src/types/channel_bus.ts
1144
1446
  /**
@@ -1580,6 +1882,7 @@ registerResourceKind({
1580
1882
  amendResourceKind("database", { envBases: [
1581
1883
  "DATABASE_URL",
1582
1884
  "DATABASE_READ_URL",
1885
+ "DATABASE_DIRECT_URL",
1583
1886
  "ADMIN_CONNECTION_STRING",
1584
1887
  "REBASE_DRIVER",
1585
1888
  "DB_POOL_MAX",
@@ -1663,10 +1966,15 @@ amendResourceKind("bucket", {
1663
1966
  "S3_FORCE_PATH_STYLE",
1664
1967
  "GCS_BUCKET",
1665
1968
  "GCS_PROJECT_ID",
1666
- "GCS_KEY_FILENAME"
1969
+ "GCS_KEY_FILENAME",
1970
+ "STORAGE_MAX_FILE_SIZE"
1667
1971
  ],
1668
1972
  envBasesByEngine: {
1669
- local: ["STORAGE_TYPE", "STORAGE_PATH"],
1973
+ local: [
1974
+ "STORAGE_TYPE",
1975
+ "STORAGE_PATH",
1976
+ "STORAGE_MAX_FILE_SIZE"
1977
+ ],
1670
1978
  s3: [
1671
1979
  "STORAGE_TYPE",
1672
1980
  "S3_BUCKET",
@@ -1674,13 +1982,15 @@ amendResourceKind("bucket", {
1674
1982
  "S3_ACCESS_KEY_ID",
1675
1983
  "S3_SECRET_ACCESS_KEY",
1676
1984
  "S3_ENDPOINT",
1677
- "S3_FORCE_PATH_STYLE"
1985
+ "S3_FORCE_PATH_STYLE",
1986
+ "STORAGE_MAX_FILE_SIZE"
1678
1987
  ],
1679
1988
  gcs: [
1680
1989
  "STORAGE_TYPE",
1681
1990
  "GCS_BUCKET",
1682
1991
  "GCS_PROJECT_ID",
1683
- "GCS_KEY_FILENAME"
1992
+ "GCS_KEY_FILENAME",
1993
+ "STORAGE_MAX_FILE_SIZE"
1684
1994
  ],
1685
1995
  azure: [],
1686
1996
  firebase: []
@@ -1698,19 +2008,73 @@ registerResourceKind({
1698
2008
  implicitDefault: false
1699
2009
  });
1700
2010
  amendResourceKind("topic", { envBases: [] });
1701
- var runtimeHolder = { current: null };
2011
+ /**
2012
+ * Declared handlers and installed runtimes, for topics and queues — on
2013
+ * `globalThis` under a shared symbol, never in module locals.
2014
+ *
2015
+ * A module local is one per COPY of this package, and two copies in one
2016
+ * process is the normal layout rather than an accident: `@rebasepro/server`
2017
+ * inlines this package into its own dist, while `config/resources.ts` imports
2018
+ * it from `node_modules`. With locals, a project's `.handler()` and
2019
+ * `.subscription()` went into the project's copy and the worker read the
2020
+ * server's empty one (`Job queue started {"tasks":0}`), and the runtime the
2021
+ * server installed was invisible to the project's `enqueue`/`publish` — so
2022
+ * every queue and topic threw "no runtime is installed" inside a running
2023
+ * backend. The CLI deriving the graph had the same split. Same mechanism and
2024
+ * reasoning as `registry()` in resources.ts; `module-state-across-copies.test.ts`
2025
+ * loads two copies to hold it, and scans for any other module-level state that
2026
+ * would split the same way.
2027
+ *
2028
+ * The version is in the symbol: a change to this object's SHAPE takes a new
2029
+ * suffix, so no copy ever reads a shape it does not know.
2030
+ */
2031
+ var WORK_KEY = Symbol.for("@rebasepro/types.workRegistry.v1");
2032
+ function work() {
2033
+ const g = globalThis;
2034
+ let shared = g[WORK_KEY];
2035
+ if (!shared) {
2036
+ shared = {
2037
+ topicRuntime: null,
2038
+ subscriptions: [],
2039
+ queueRuntime: null,
2040
+ queueConsumers: /* @__PURE__ */ new Map()
2041
+ };
2042
+ g[WORK_KEY] = shared;
2043
+ }
2044
+ return shared;
2045
+ }
2046
+ /**
2047
+ * Where `@rebasepro/server` publishes its client once it has booted (its
2048
+ * `singleton.ts`). Read only to tell the two causes of a missing runtime apart.
2049
+ */
2050
+ var SERVER_INSTANCE_SLOT = Symbol.for("@rebasepro/server:singleton-instance");
2051
+ /**
2052
+ * Why there is no runtime to send through, as far as this process can tell.
2053
+ *
2054
+ * Outside a backend — the CLI evaluating config, a script, a unit test — the
2055
+ * answer is that there is nothing to deliver to. Inside one it is not: the
2056
+ * backend installs a runtime at boot for what `config/resources.ts` declared,
2057
+ * so a missing one there means boot never saw the declaration, or the backend
2058
+ * carries a copy of this package too old to share the slot it installs into.
2059
+ * Saying "outside a backend" to somebody whose backend is running sends them
2060
+ * looking for the wrong thing.
2061
+ */
2062
+ function missingRuntimeCause(kind) {
2063
+ if (!(globalThis[SERVER_INSTANCE_SLOT] != null)) return "This works inside a running Rebase backend; this looks like code running outside one (a build, a script, or a test without a harness).";
2064
+ return `A Rebase backend is running in this process but installed no ${kind} runtime. It installs one at boot when config/resources.ts declares a ${kind}, so either this ${kind} is declared somewhere boot does not read, or the backend's copy of @rebasepro/types predates the shared runtime slot this copy reads — keep @rebasepro/server and @rebasepro/types on the same version.`;
2065
+ }
1702
2066
  /** Install the transport topics publish through. Called by the server at boot. */
1703
2067
  function setTopicRuntime(runtime) {
1704
- runtimeHolder.current = runtime;
2068
+ work().topicRuntime = runtime;
1705
2069
  }
1706
- var subscriptions = [];
1707
2070
  /** Every declared subscription, for the worker to wire and the graph to record. */
1708
2071
  function declaredSubscriptions(topic) {
2072
+ const { subscriptions } = work();
1709
2073
  return topic ? subscriptions.filter((s) => s.topic === topic) : subscriptions.slice();
1710
2074
  }
1711
2075
  /** Forget declared subscriptions. For tests, alongside `resetDeclaredResources`. */
1712
2076
  function resetDeclaredSubscriptions() {
1713
- subscriptions.length = 0;
2077
+ work().subscriptions.length = 0;
1714
2078
  }
1715
2079
  /**
1716
2080
  * Declare a topic.
@@ -1729,12 +2093,13 @@ function topic(key, options = {}) {
1729
2093
  return key;
1730
2094
  },
1731
2095
  async publish(event) {
1732
- const runtime = runtimeHolder.current;
1733
- if (!runtime) throw new Error(`Cannot publish to topic "${key}": no topic runtime is installed. Publishing works inside a running Rebase backend; this looks like config being evaluated outside one (a build, a script, or a test without a harness).`);
2096
+ const runtime = work().topicRuntime;
2097
+ if (!runtime) throw new Error(`Cannot publish to topic "${key}": no topic runtime is installed. ${missingRuntimeCause("topic")}`);
1734
2098
  await runtime.publish(key, event);
1735
2099
  },
1736
2100
  subscription(name, handler, subOptions = {}) {
1737
2101
  if (!name || name.trim() === "") throw new Error(`A subscription on topic "${key}" needs a non-empty name.`);
2102
+ const { subscriptions } = work();
1738
2103
  if (subscriptions.some((s) => s.topic === key && s.name === name)) throw new Error(`Topic "${key}" already has a subscription named "${name}". The name is what a job row records and what a retry counts against, so two cannot share one.`);
1739
2104
  subscriptions.push({
1740
2105
  topic: key,
@@ -1797,19 +2162,17 @@ registerResourceKind({
1797
2162
  optionKeys: ["maxAttempts"],
1798
2163
  implicitDefault: false
1799
2164
  });
1800
- var queueRuntimeHolder = { current: null };
1801
2165
  /** Install the transport queues enqueue through. Called by the server at boot. */
1802
2166
  function setQueueRuntime(runtime) {
1803
- queueRuntimeHolder.current = runtime;
2167
+ work().queueRuntime = runtime;
1804
2168
  }
1805
- var queueConsumers = /* @__PURE__ */ new Map();
1806
2169
  /** Every declared queue handler, for the worker to wire. */
1807
2170
  function declaredQueueConsumers() {
1808
- return [...queueConsumers.values()];
2171
+ return [...work().queueConsumers.values()];
1809
2172
  }
1810
2173
  /** Forget declared queue handlers. For tests, alongside `resetDeclaredResources`. */
1811
2174
  function resetDeclaredQueueConsumers() {
1812
- queueConsumers.clear();
2175
+ work().queueConsumers.clear();
1813
2176
  }
1814
2177
  /**
1815
2178
  * Declare a queue.
@@ -1831,11 +2194,12 @@ function queue(key, options = {}) {
1831
2194
  return key;
1832
2195
  },
1833
2196
  async enqueue(payload, enqueueOptions) {
1834
- const runtime = queueRuntimeHolder.current;
1835
- if (!runtime) throw new Error(`Cannot enqueue on queue "${key}": no queue runtime is installed. Enqueueing works inside a running Rebase backend; this looks like config being evaluated outside one (a build, a script, or a test without a harness).`);
2197
+ const runtime = work().queueRuntime;
2198
+ if (!runtime) throw new Error(`Cannot enqueue on queue "${key}": no queue runtime is installed. ${missingRuntimeCause("queue")}`);
1836
2199
  return runtime.enqueue(key, payload, enqueueOptions);
1837
2200
  },
1838
2201
  handler(fn) {
2202
+ const { queueConsumers } = work();
1839
2203
  if (queueConsumers.has(key)) throw new Error(`Queue "${key}" already has a handler. A queue has exactly one consumer; work that several things react to is a topic with several subscriptions.`);
1840
2204
  queueConsumers.set(key, {
1841
2205
  queue: key,
@@ -1971,6 +2335,426 @@ function isLazyComponentRef(ref) {
1971
2335
  return typeof ref === "object" && ref !== null && "__rebaseLazy" in ref && ref.__rebaseLazy === true;
1972
2336
  }
1973
2337
  //#endregion
2338
+ //#region src/types/auth_adapter.ts
2339
+ /**
2340
+ * The request header that asks for a data-plane request to run as another
2341
+ * user, named by uid.
2342
+ *
2343
+ * The server honours it only on a signed-in administrator's own session —
2344
+ * never on an API key or the service key — and only through an adapter that
2345
+ * implements {@link AuthAdapter.resolveUser}. Everywhere else a request that
2346
+ * carries it is refused, never run as its caller.
2347
+ *
2348
+ * @group Auth
2349
+ */
2350
+ var IMPERSONATE_HEADER = "x-rebase-impersonate";
2351
+ //#endregion
2352
+ //#region src/types/scopes.ts
2353
+ /**
2354
+ * Scopes — what a credential may do, named `resource:action`.
2355
+ *
2356
+ * One vocabulary for every way of reaching a Rebase backend: a person's
2357
+ * session, a service key, a personal key and an OAuth token for `/mcp` all
2358
+ * answer "may this caller do X?" with the same strings, so a grant reads the
2359
+ * same on a key, on a role and on a consent screen.
2360
+ *
2361
+ * A scope may carry a target — `data:read:posts` is `data:read` on the `posts`
2362
+ * collection only. The unqualified scope covers every target. Only the data
2363
+ * plane and app scopes take targets; see {@link SCOPE_TARGETS}.
2364
+ *
2365
+ * Two planes, held differently:
2366
+ *
2367
+ * - **The data plane** (`data:*`, `storage:*`, `functions:invoke`). Every
2368
+ * signed-in person holds all of it, because what a person may do with data
2369
+ * is decided row by row by the collection's `securityRules` and by storage
2370
+ * policies, never by a scope. On a key or a token these scopes narrow: a key
2371
+ * holding `data:read:posts` reaches nothing else, whatever RLS would allow.
2372
+ * - **The admin plane** (everything else). Nobody holds it implicitly. The
2373
+ * `admin` role holds all of it; any other role holds what the app declares
2374
+ * for it under `auth.roles` on the users collection.
2375
+ *
2376
+ * Apps declare their own scopes too (`auth.scopes`), named the same way —
2377
+ * `project:deploy`. Every signed-in person holds every app scope: like the data
2378
+ * plane, the function behind it decides whether the person may act, and the
2379
+ * scope exists so a key can be narrowed to that one action.
2380
+ *
2381
+ * @module
2382
+ */
2383
+ /** @group Auth */
2384
+ var DATA_PLANE_SCOPES = [
2385
+ "data:read",
2386
+ "data:write",
2387
+ "data:delete",
2388
+ "storage:read",
2389
+ "storage:write",
2390
+ "storage:delete",
2391
+ "functions:invoke"
2392
+ ];
2393
+ /** @group Auth */
2394
+ var ADMIN_SCOPES = [
2395
+ "users:read",
2396
+ "users:write",
2397
+ "schema:read",
2398
+ "schema:write",
2399
+ "database:read",
2400
+ "database:write",
2401
+ "backups:read",
2402
+ "cron:read",
2403
+ "cron:write",
2404
+ "logs:read",
2405
+ "keys:read",
2406
+ "keys:write"
2407
+ ];
2408
+ /** Every scope Rebase itself defines, in display order. @group Auth */
2409
+ var BUILT_IN_SCOPES = [...DATA_PLANE_SCOPES, ...ADMIN_SCOPES];
2410
+ /**
2411
+ * Scopes no key may hold.
2412
+ *
2413
+ * A key that could manage keys could mint its own successor, widen itself, or
2414
+ * revoke the keys other integrations run on — and revoking it would undo none
2415
+ * of that. Managing keys is for a person (or the service key).
2416
+ *
2417
+ * @group Auth
2418
+ */
2419
+ var KEY_MANAGEMENT_SCOPES = ["keys:read", "keys:write"];
2420
+ /** The one built-in role. It holds every scope and is the RLS `admin` role. @group Auth */
2421
+ var ADMIN_ROLE = "admin";
2422
+ /**
2423
+ * The built-in scopes that accept a target, and what the target names.
2424
+ *
2425
+ * - `data:*` — a collection slug
2426
+ * - `storage:*` — a storage source id (`(default)` for the default source)
2427
+ * - `functions:invoke` — a custom function's name
2428
+ *
2429
+ * @group Auth
2430
+ */
2431
+ var SCOPE_TARGETS = {
2432
+ "data:read": "collection",
2433
+ "data:write": "collection",
2434
+ "data:delete": "collection",
2435
+ "storage:read": "bucket",
2436
+ "storage:write": "bucket",
2437
+ "storage:delete": "bucket",
2438
+ "functions:invoke": "function"
2439
+ };
2440
+ /** @group Auth */
2441
+ var BUILT_IN_SCOPE_DESCRIPTIONS = {
2442
+ "data:read": {
2443
+ label: "Read data",
2444
+ description: "Read rows from collections, through the caller's row-level security."
2445
+ },
2446
+ "data:write": {
2447
+ label: "Create and update data",
2448
+ description: "Create and update rows in collections, through the caller's row-level security."
2449
+ },
2450
+ "data:delete": {
2451
+ label: "Delete data",
2452
+ description: "Delete rows from collections, through the caller's row-level security."
2453
+ },
2454
+ "storage:read": {
2455
+ label: "Read files",
2456
+ description: "List and download stored files."
2457
+ },
2458
+ "storage:write": {
2459
+ label: "Upload files",
2460
+ description: "Upload files and create folders."
2461
+ },
2462
+ "storage:delete": {
2463
+ label: "Delete files",
2464
+ description: "Delete stored files."
2465
+ },
2466
+ "functions:invoke": {
2467
+ label: "Call functions",
2468
+ description: "Call the backend's custom functions. A function can do anything its code does."
2469
+ },
2470
+ "users:read": {
2471
+ label: "See users",
2472
+ description: "List user accounts and their roles."
2473
+ },
2474
+ "users:write": {
2475
+ label: "Manage users",
2476
+ description: "Create, edit and delete accounts, reset passwords and second factors, and assign roles up to the holder's own."
2477
+ },
2478
+ "schema:read": {
2479
+ label: "See the schema",
2480
+ description: "Read the collection schema, plan schema changes and run the RLS audit."
2481
+ },
2482
+ "schema:write": {
2483
+ label: "Change the schema",
2484
+ description: "Apply schema changes: edits collection files and alters the database."
2485
+ },
2486
+ "database:read": {
2487
+ label: "Inspect the database",
2488
+ description: "List databases, tables, Postgres roles and branches."
2489
+ },
2490
+ "database:write": {
2491
+ label: "Run SQL",
2492
+ description: "Run SQL as the database owner, outside row-level security, and create or delete branches."
2493
+ },
2494
+ "backups:read": {
2495
+ label: "Download backups",
2496
+ description: "List and download database backups — every row, outside row-level security."
2497
+ },
2498
+ "cron:read": {
2499
+ label: "See cron jobs",
2500
+ description: "List cron jobs and read their run history."
2501
+ },
2502
+ "cron:write": {
2503
+ label: "Run cron jobs",
2504
+ description: "Trigger cron jobs and switch them on or off."
2505
+ },
2506
+ "logs:read": {
2507
+ label: "Read server logs",
2508
+ description: "Read the server's request and application logs."
2509
+ },
2510
+ "keys:read": {
2511
+ label: "See service keys",
2512
+ description: "List the project's service keys. Never grantable to a key."
2513
+ },
2514
+ "keys:write": {
2515
+ label: "Manage service keys",
2516
+ description: "Create, change and revoke the project's service keys. Never grantable to a key."
2517
+ }
2518
+ };
2519
+ /** The access model of an app that declares nothing: built-ins only. @group Auth */
2520
+ var EMPTY_ACCESS_MODEL = {
2521
+ roles: {},
2522
+ scopes: {}
2523
+ };
2524
+ var SCOPE_PART = /^[a-z][a-z0-9-]*$/;
2525
+ /**
2526
+ * Split `resource:action[:target]`. Returns null for anything else.
2527
+ *
2528
+ * The target is everything after the second colon, so a target may itself
2529
+ * contain colons. Resource and action are lower-case words.
2530
+ *
2531
+ * @group Auth
2532
+ */
2533
+ function parseScope(value) {
2534
+ if (typeof value !== "string") return null;
2535
+ const first = value.indexOf(":");
2536
+ if (first < 0) return null;
2537
+ const second = value.indexOf(":", first + 1);
2538
+ const resource = value.slice(0, first);
2539
+ const action = second < 0 ? value.slice(first + 1) : value.slice(first + 1, second);
2540
+ if (!SCOPE_PART.test(resource) || !SCOPE_PART.test(action)) return null;
2541
+ if (second < 0) return { scope: `${resource}:${action}` };
2542
+ const target = value.slice(second + 1);
2543
+ if (target.length === 0 || /\s/.test(target)) return null;
2544
+ return {
2545
+ scope: `${resource}:${action}`,
2546
+ target
2547
+ };
2548
+ }
2549
+ /** @group Auth */
2550
+ function isBuiltInScope(scope) {
2551
+ return BUILT_IN_SCOPES.includes(scope);
2552
+ }
2553
+ /** @group Auth */
2554
+ function isDataPlaneScope(scope) {
2555
+ return DATA_PLANE_SCOPES.includes(scope);
2556
+ }
2557
+ /** @group Auth */
2558
+ function isAdminScope(scope) {
2559
+ return ADMIN_SCOPES.includes(scope);
2560
+ }
2561
+ /** Does this list of roles include the built-in `admin` role? @group Auth */
2562
+ function hasAdminRole(roles) {
2563
+ return !!roles?.includes(ADMIN_ROLE);
2564
+ }
2565
+ /**
2566
+ * Does a set of held scopes grant `scope`, optionally on one `target`?
2567
+ *
2568
+ * The unqualified grant covers every target. A qualified grant covers its own
2569
+ * target only, and never answers an unqualified question: a key holding
2570
+ * `data:read:posts` is not asked "may you read data?" and told yes.
2571
+ *
2572
+ * @group Auth
2573
+ */
2574
+ function scopeGrants(held, scope, target) {
2575
+ for (const entry of held) {
2576
+ if (entry === scope) return true;
2577
+ if (target !== void 0 && entry === `${scope}:${target}`) return true;
2578
+ }
2579
+ return false;
2580
+ }
2581
+ /**
2582
+ * Does a set of held scopes grant `scope` on at least one target?
2583
+ *
2584
+ * For a surface that lists what is reachable — the collections an MCP client
2585
+ * may query — and filters per item afterwards.
2586
+ *
2587
+ * @group Auth
2588
+ */
2589
+ function scopeGrantsAny(held, scope) {
2590
+ const prefix = `${scope}:`;
2591
+ return held.some((entry) => entry === scope || entry.startsWith(prefix));
2592
+ }
2593
+ /**
2594
+ * The targets a held set narrows `scope` to: `"all"` when it holds the
2595
+ * unqualified scope, otherwise the listed targets (possibly none).
2596
+ *
2597
+ * @group Auth
2598
+ */
2599
+ function scopeTargets(held, scope) {
2600
+ const prefix = `${scope}:`;
2601
+ const targets = [];
2602
+ for (const entry of held) {
2603
+ if (entry === scope) return "all";
2604
+ if (entry.startsWith(prefix)) targets.push(entry.slice(prefix.length));
2605
+ }
2606
+ return targets;
2607
+ }
2608
+ /**
2609
+ * Every scope a person holding `roles` has.
2610
+ *
2611
+ * The data plane and every app scope always; the whole admin plane with the
2612
+ * `admin` role; otherwise each declared role's scopes. A role nobody declared
2613
+ * adds nothing — it may still mean something to an RLS policy.
2614
+ *
2615
+ * @group Auth
2616
+ */
2617
+ function scopesForRoles(roles, model = EMPTY_ACCESS_MODEL) {
2618
+ const held = /* @__PURE__ */ new Set([...DATA_PLANE_SCOPES, ...Object.keys(model.scopes)]);
2619
+ if (hasAdminRole(roles)) {
2620
+ for (const scope of ADMIN_SCOPES) held.add(scope);
2621
+ return [...held];
2622
+ }
2623
+ for (const role of roles ?? []) {
2624
+ const definition = Object.prototype.hasOwnProperty.call(model.roles, role) ? model.roles[role] : void 0;
2625
+ for (const scope of definition?.scopes ?? []) held.add(scope);
2626
+ }
2627
+ return [...held];
2628
+ }
2629
+ /**
2630
+ * Every scope this access model knows, unqualified: the built-ins, then the
2631
+ * app's own.
2632
+ *
2633
+ * @group Auth
2634
+ */
2635
+ function knownScopes(model = EMPTY_ACCESS_MODEL) {
2636
+ return [...BUILT_IN_SCOPES, ...Object.keys(model.scopes)];
2637
+ }
2638
+ /** Does this scope accept a target? @group Auth */
2639
+ function scopeAcceptsTarget(scope, model = EMPTY_ACCESS_MODEL) {
2640
+ if (isBuiltInScope(scope)) return SCOPE_TARGETS[scope] !== void 0;
2641
+ return (Object.prototype.hasOwnProperty.call(model.scopes, scope) ? model.scopes[scope] : void 0)?.target !== void 0;
2642
+ }
2643
+ /**
2644
+ * Check a list of scope strings against an access model. Returns the
2645
+ * problems, empty when every entry is a known scope with an acceptable target.
2646
+ *
2647
+ * @group Auth
2648
+ */
2649
+ function validateScopes(values, model = EMPTY_ACCESS_MODEL) {
2650
+ const known = new Set(knownScopes(model));
2651
+ const problems = [];
2652
+ for (const value of values) {
2653
+ const text = typeof value === "string" ? value : String(value);
2654
+ const parsed = typeof value === "string" ? parseScope(value) : null;
2655
+ if (!parsed) problems.push({
2656
+ scope: text,
2657
+ reason: "malformed"
2658
+ });
2659
+ else if (!known.has(parsed.scope)) problems.push({
2660
+ scope: text,
2661
+ reason: "unknown"
2662
+ });
2663
+ else if (parsed.target !== void 0 && !scopeAcceptsTarget(parsed.scope, model)) problems.push({
2664
+ scope: text,
2665
+ reason: "target-not-accepted"
2666
+ });
2667
+ }
2668
+ return problems;
2669
+ }
2670
+ /**
2671
+ * The entries of `requested` that `held` does not cover — what a credential
2672
+ * would hold beyond its minter. Empty means the request is within reach.
2673
+ *
2674
+ * `data:read:posts` is covered by `data:read`; `data:read` is not covered by
2675
+ * `data:read:posts`.
2676
+ *
2677
+ * @group Auth
2678
+ */
2679
+ function scopesBeyond(requested, held) {
2680
+ return requested.filter((entry) => {
2681
+ const parsed = parseScope(entry);
2682
+ if (!parsed) return true;
2683
+ return !scopeGrants(held, parsed.scope, parsed.target);
2684
+ });
2685
+ }
2686
+ /**
2687
+ * Narrow `requested` to what `held` covers, entry by entry. What a personal
2688
+ * key holds today when its owner has lost a role since it was minted.
2689
+ *
2690
+ * @group Auth
2691
+ */
2692
+ function intersectScopes(requested, held) {
2693
+ const beyond = new Set(scopesBeyond(requested, held));
2694
+ return requested.filter((entry) => !beyond.has(entry));
2695
+ }
2696
+ /**
2697
+ * How a scope is worded for a person: the built-in description, the app's
2698
+ * declared label, or the raw string when nothing describes it. A target is
2699
+ * named after the label.
2700
+ *
2701
+ * @group Auth
2702
+ */
2703
+ function describeScope(value, model = EMPTY_ACCESS_MODEL) {
2704
+ const parsed = parseScope(value);
2705
+ if (!parsed) return {
2706
+ label: value,
2707
+ description: ""
2708
+ };
2709
+ let base;
2710
+ if (isBuiltInScope(parsed.scope)) base = BUILT_IN_SCOPE_DESCRIPTIONS[parsed.scope];
2711
+ else if (Object.prototype.hasOwnProperty.call(model.scopes, parsed.scope)) {
2712
+ const definition = model.scopes[parsed.scope];
2713
+ base = {
2714
+ label: definition.label,
2715
+ description: definition.description ?? ""
2716
+ };
2717
+ }
2718
+ const label = base?.label ?? parsed.scope;
2719
+ return {
2720
+ label: parsed.target !== void 0 ? `${label}: ${parsed.target}` : label,
2721
+ description: base?.description ?? ""
2722
+ };
2723
+ }
2724
+ /** The roles of an access model, `admin` first. @group Auth */
2725
+ function summarizeRoles(model = EMPTY_ACCESS_MODEL) {
2726
+ return [{
2727
+ id: ADMIN_ROLE,
2728
+ name: "Admin",
2729
+ description: "Holds every scope. Row-level security does not limit it: it reads, changes and deletes every row.",
2730
+ scopes: [...ADMIN_SCOPES],
2731
+ builtIn: true
2732
+ }, ...Object.entries(model.roles).map(([id, definition]) => ({
2733
+ id,
2734
+ name: definition.name ?? id,
2735
+ ...definition.description ? { description: definition.description } : {},
2736
+ scopes: [...definition.scopes],
2737
+ builtIn: false
2738
+ }))];
2739
+ }
2740
+ /** Every scope of an access model, described, in display order. @group Auth */
2741
+ function summarizeScopes(model = EMPTY_ACCESS_MODEL) {
2742
+ const builtIn = BUILT_IN_SCOPES.map((scope) => ({
2743
+ scope,
2744
+ ...BUILT_IN_SCOPE_DESCRIPTIONS[scope],
2745
+ ...SCOPE_TARGETS[scope] ? { target: SCOPE_TARGETS[scope] } : {},
2746
+ plane: isDataPlaneScope(scope) ? "data" : "admin"
2747
+ }));
2748
+ const app = Object.entries(model.scopes).map(([scope, definition]) => ({
2749
+ scope,
2750
+ label: definition.label,
2751
+ description: definition.description ?? "",
2752
+ ...definition.target ? { target: definition.target } : {},
2753
+ plane: "app"
2754
+ }));
2755
+ return [...builtIn, ...app];
2756
+ }
2757
+ //#endregion
1974
2758
  //#region src/types/project_manifest.ts
1975
2759
  /**
1976
2760
  * Path prefixes the backend owns, which no static app may claim.
@@ -2008,6 +2792,91 @@ function reservedPrefixFor(path) {
2008
2792
  return RESERVED_BACKEND_PREFIXES.find((reserved) => normalized === reserved || normalized.startsWith(`${reserved}/`));
2009
2793
  }
2010
2794
  /**
2795
+ * Split a static app's declared `path` into the hostname it claims and the path
2796
+ * under it.
2797
+ *
2798
+ * `"/admin"` is `{ path: "/admin" }`, returned as written: the path rules
2799
+ * (leading slash, no `..`, reserved prefixes) belong to the caller, which
2800
+ * applies them to the path part of either form. `"https://admin.example.com/cms"`
2801
+ * is `{ host: "admin.example.com", path: "/cms" }`.
2802
+ *
2803
+ * Shared because three parties must read the same value the same way: the CLI
2804
+ * while `rebase.json` is edited, the control plane at deploy intake (a bundle can
2805
+ * come from any CLI, or none), and the runtime choosing which app answers.
2806
+ */
2807
+ function parseAppAddress(value) {
2808
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
2809
+ if (!value.startsWith("/") && isValidAppHost(value.toLowerCase().replace(/\/.*$/, ""))) return {
2810
+ ok: false,
2811
+ reason: `looks like a hostname — write it as a URL: "https://${value}"`
2812
+ };
2813
+ return {
2814
+ ok: true,
2815
+ address: { path: value }
2816
+ };
2817
+ }
2818
+ if (/[\s\\\u0000-\u001f\u007f]/.test(value)) return {
2819
+ ok: false,
2820
+ reason: "must not contain spaces, control characters or backslashes"
2821
+ };
2822
+ const authority = value.slice(value.indexOf("//") + 2).split(/[/?#]/)[0];
2823
+ if (/:[^@]*$/.test(authority) && !authority.startsWith("[")) return {
2824
+ ok: false,
2825
+ reason: "must not name a port — a hostname is served on 443"
2826
+ };
2827
+ let url;
2828
+ try {
2829
+ url = new URL(value);
2830
+ } catch {
2831
+ return {
2832
+ ok: false,
2833
+ reason: "is not a valid URL"
2834
+ };
2835
+ }
2836
+ if (url.protocol !== "https:") return {
2837
+ ok: false,
2838
+ reason: `must use https — the platform serves every hostname over TLS: "https://${url.host}${url.pathname === "/" ? "" : url.pathname}"`
2839
+ };
2840
+ if (url.username || url.password) return {
2841
+ ok: false,
2842
+ reason: "must not contain credentials"
2843
+ };
2844
+ if (url.port) return {
2845
+ ok: false,
2846
+ reason: "must not name a port — a hostname is served on 443"
2847
+ };
2848
+ if (url.search || url.hash || /[?#]/.test(value)) return {
2849
+ ok: false,
2850
+ reason: "must not have a query or fragment"
2851
+ };
2852
+ if (!isValidAppHost(url.hostname)) return {
2853
+ ok: false,
2854
+ reason: `names "${url.hostname}", which is not a public hostname`
2855
+ };
2856
+ const authorityAndPath = value.slice(value.indexOf("//") + 2);
2857
+ const slash = authorityAndPath.indexOf("/");
2858
+ return {
2859
+ ok: true,
2860
+ address: {
2861
+ host: url.hostname,
2862
+ path: slash === -1 ? "/" : authorityAndPath.slice(slash)
2863
+ }
2864
+ };
2865
+ }
2866
+ /**
2867
+ * Whether `host` is a public DNS name an app can be given: lowercase labels of
2868
+ * letters, digits and hyphens, at least two of them, not an IP address and not
2869
+ * `localhost`. Internationalised names arrive here already in punycode.
2870
+ */
2871
+ function isValidAppHost(host) {
2872
+ if (host.length === 0 || host.length > 253 || host !== host.toLowerCase()) return false;
2873
+ const labels = host.split(".");
2874
+ if (labels.length < 2) return false;
2875
+ const label = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
2876
+ if (!labels.every((part) => label.test(part))) return false;
2877
+ return !/^[0-9]+$/.test(labels[labels.length - 1]);
2878
+ }
2879
+ /**
2011
2880
  * Version of the bundle *format* itself.
2012
2881
  *
2013
2882
  * Bumped only when the on-disk layout changes in a way an older runtime could
@@ -2370,10 +3239,13 @@ var BATCH_REF_KEY = "$ref";
2370
3239
  * one, which is the case worth catching.
2371
3240
  *
2372
3241
  * Any `$`-prefixed key counts, because `{ $increment: 1 }` written to a number
2373
- * column as a JSON document is the failure this exists to prevent. No collection
2374
- * can declare a column whose value legitimately has a key beginning with `$`: a
2375
- * `map` property's sub-keys are declared, and `$` is not valid in the
2376
- * identifiers the DDL generators emit.
3242
+ * column as a JSON document is the failure this exists to prevent.
3243
+ *
3244
+ * Except on a `map` (pass `propertyType`): a map's value is a JSON document,
3245
+ * and one may legitimately carry `$` keys — a JSON Schema's `$schema`, a
3246
+ * MongoDB-style export. There, only a single-key object whose key is one of
3247
+ * {@link FIELD_OPERATORS} is an operation, and anything else is data. A caller
3248
+ * that does not know the property type gets the strict reading.
2377
3249
  *
2378
3250
  * The one exception is `{ $ref: … }`, the batch's backward reference. It stands
2379
3251
  * where a *value* goes and is resolved to one before the row is written, so it
@@ -2382,15 +3254,16 @@ var BATCH_REF_KEY = "$ref";
2382
3254
  *
2383
3255
  * @group Data
2384
3256
  */
2385
- function isFieldOperation(value) {
3257
+ function isFieldOperation(value, propertyType) {
2386
3258
  if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) return false;
2387
3259
  const keys = Object.keys(value);
2388
3260
  if (keys.length === 1 && keys[0] === "$ref") return false;
3261
+ if (propertyType === "map") return keys.length === 1 && FIELD_OPERATORS.includes(keys[0]);
2389
3262
  return keys.some((key) => key.startsWith("$"));
2390
3263
  }
2391
3264
  /** True when any value in a write payload is (or is attempting to be) one. @group Data */
2392
3265
  function hasFieldOperation(values) {
2393
- return !!values && Object.values(values).some(isFieldOperation);
3266
+ return !!values && Object.values(values).some((value) => isFieldOperation(value));
2394
3267
  }
2395
3268
  //#endregion
2396
3269
  //#region src/controllers/data_driver.ts
@@ -2458,9 +3331,33 @@ function resolveClientListLimit(rawLimit, opts = {}) {
2458
3331
  */
2459
3332
  var PUBLIC_STORAGE_PREFIX = "public/";
2460
3333
  /**
2461
- * True when a storage key/path points at a public object (lives under
2462
- * {@link PUBLIC_STORAGE_PREFIX}). The check is applied to the key *within the
2463
- * bucket* — strip any `bucket/` and `scheme://` prefixes first.
3334
+ * True when a storage **key** is public: it lies under
3335
+ * {@link PUBLIC_STORAGE_PREFIX}.
3336
+ *
3337
+ * The key is read exactly as it is — the canonical key within its bucket, the
3338
+ * one the server stores, serves, and hands a `storageAuthorize` hook as
3339
+ * `key` — and nothing is parsed out of it. That is the difference from
3340
+ * {@link isPublicStoragePath}, which reads a client-side *path* and so strips
3341
+ * a `scheme://` and a leading `default/` bucket segment first. On a key both are
3342
+ * wrong: `default/public/x` is a key in a folder called `default`, and a
3343
+ * request path `notes://public/x` names the key `notes:/public/x`. Neither is
3344
+ * public, and the server decides on this function so that it never says they
3345
+ * are.
3346
+ *
3347
+ * @group Models
3348
+ */
3349
+ function isPublicStorageKey(key) {
3350
+ if (!key || !key.startsWith("public/")) return false;
3351
+ return !key.split(/[\\/]/).includes("..");
3352
+ }
3353
+ /**
3354
+ * True when a storage key or client-side path points at a public object
3355
+ * (lives under {@link PUBLIC_STORAGE_PREFIX}).
3356
+ *
3357
+ * For a path as a client holds it: a `scheme://` prefix and a single leading
3358
+ * `default/` bucket segment are stripped first. The server and a
3359
+ * `storageAuthorize` hook, which hold the canonical key, use
3360
+ * {@link isPublicStorageKey}.
2464
3361
  *
2465
3362
  * @group Models
2466
3363
  */
@@ -2473,7 +3370,295 @@ function isPublicStoragePath(path) {
2473
3370
  if (p.split("/").some((seg) => seg === "..")) return false;
2474
3371
  return p.startsWith("public/") || p.startsWith(`default/public/`);
2475
3372
  }
3373
+ /**
3374
+ * The path the storage routes address an object by, from a key or from the
3375
+ * `storageUrl` an upload returned (`<scheme>://<bucket>/<key>`, which is what a
3376
+ * property with `includeBucketUrl: true` keeps in its column).
3377
+ *
3378
+ * The routes address a source's objects by key. On S3 and GCS the bucket in a
3379
+ * `storageUrl` is always the source's own — the server refuses any other — so
3380
+ * it is dropped: sent along, `s3://acme-media/products/a.png` reached the
3381
+ * server as the key `acme-media/products/a.png`, the preview found nothing and
3382
+ * a delete reported success while the object stayed. It worked on local disk
3383
+ * only, because `local://default/<key>` happens to carry the one bucket
3384
+ * segment the routes recognise — which is kept, as is any other local bucket.
3385
+ *
3386
+ * A bare key whose first folder is `default` is sent behind that bucket
3387
+ * segment. The routes read one leading `default/` (in any case) as the bucket,
3388
+ * so the key `default/photo.png` sent as it is names the root `photo.png`:
3389
+ * {@link defaultBucketPath} is the spelling that names the key itself.
3390
+ *
3391
+ * Shared by every storage source that talks to those routes, so they address
3392
+ * an object alike.
3393
+ *
3394
+ * @group Models
3395
+ */
3396
+ function storageObjectPath(keyOrUrl, bucket) {
3397
+ let filePath = keyOrUrl;
3398
+ let bucketQualified = false;
3399
+ const scheme = /^(local|s3|gs):\/\//.exec(filePath);
3400
+ if (scheme) {
3401
+ filePath = filePath.substring(scheme[0].length);
3402
+ if (scheme[1] !== "local") {
3403
+ const slash = filePath.indexOf("/");
3404
+ if (slash > 0) filePath = filePath.substring(slash + 1);
3405
+ } else bucketQualified = true;
3406
+ }
3407
+ if (bucket && filePath) {
3408
+ if (!filePath.startsWith(`${bucket}/`)) filePath = `${bucket}/${filePath}`;
3409
+ bucketQualified = true;
3410
+ }
3411
+ return bucketQualified ? filePath : defaultBucketPath(filePath);
3412
+ }
3413
+ /**
3414
+ * The request path of a key in the default bucket: the key itself, unless the
3415
+ * routes would read its first folder as the bucket — a first folder named
3416
+ * `default`, matched as the routes match it (case-insensitively, and only when
3417
+ * something follows it). Then the bucket segment goes in front, so exactly the
3418
+ * one segment the routes strip is the one added.
3419
+ */
3420
+ function defaultBucketPath(key) {
3421
+ const segments = key.split("/");
3422
+ return segments.length > 1 && segments[0].toLowerCase() === "default" ? `default/${key}` : key;
3423
+ }
3424
+ //#endregion
3425
+ //#region src/controllers/storage-references.ts
3426
+ /**
3427
+ * The scheme of a **storage reference**: a stored object named inside text.
3428
+ *
3429
+ * Text that embeds a file — a markdown body above all — cannot hold a private
3430
+ * object's URL: that URL carries a download token which expires in minutes,
3431
+ * after which every reader of the text gets a 401 for an image that is still in
3432
+ * the bucket. It holds a reference instead,
3433
+ * `rebase-storage:posts/cover.png` (`?storageId=media` for a named source),
3434
+ * and whoever renders the text exchanges each reference for a fresh URL with
3435
+ * {@link resolveStorageReferences} — `resolveStorageReferences(text, client)`
3436
+ * from `@rebasepro/client` on a site.
3437
+ *
3438
+ * @group Models
3439
+ */
3440
+ var STORAGE_REFERENCE_SCHEME = "rebase-storage:";
3441
+ /**
3442
+ * `encodeURIComponent`, plus the five characters it leaves alone that end or
3443
+ * break a markdown link destination: `(`, `)`, `'`, `!` and `*`.
3444
+ */
3445
+ var encodeReferencePart = (part) => encodeURIComponent(part).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
3446
+ /**
3447
+ * A reference as it appears in text: the scheme, then the characters
3448
+ * {@link storageReference} writes. Kept beside {@link STORAGE_REFERENCE_SCHEME};
3449
+ * the test round-trips names chosen to break it.
3450
+ */
3451
+ var STORAGE_REFERENCE_PATTERN = /rebase-storage:[A-Za-z0-9\-._~%/?=&]+/g;
3452
+ /**
3453
+ * The reference for an object — what text stores in place of its URL.
3454
+ *
3455
+ * Every part is percent-encoded, so the reference contains no space,
3456
+ * parenthesis or quote and survives as one markdown link destination whatever
3457
+ * the file was called.
3458
+ *
3459
+ * @group Models
3460
+ */
3461
+ function storageReference(key, storageId) {
3462
+ return `${STORAGE_REFERENCE_SCHEME}${key.replace(/^\/+/, "").split("/").map(encodeReferencePart).join("/")}${storageId && storageId !== "(default)" ? `?storageId=${encodeReferencePart(storageId)}` : ""}`;
3463
+ }
3464
+ /**
3465
+ * Whether a string — an image's `src`, a link's `href` — is a storage reference.
3466
+ *
3467
+ * @group Models
3468
+ */
3469
+ function isStorageReference(value) {
3470
+ return typeof value === "string" && value.startsWith("rebase-storage:");
3471
+ }
3472
+ /**
3473
+ * The object a storage reference names, or `null` for anything else — a URL, a
3474
+ * bare key, or a reference whose key would climb out of its prefix.
3475
+ *
3476
+ * @group Models
3477
+ */
3478
+ function parseStorageReference(value) {
3479
+ if (!isStorageReference(value)) return null;
3480
+ const rest = value.slice(15);
3481
+ const queryAt = rest.indexOf("?");
3482
+ const rawPath = queryAt === -1 ? rest : rest.slice(0, queryAt);
3483
+ const query = queryAt === -1 ? "" : rest.slice(queryAt + 1);
3484
+ let key;
3485
+ let storageId;
3486
+ try {
3487
+ key = rawPath.split("/").map(decodeURIComponent).join("/");
3488
+ for (const pair of query.split("&")) {
3489
+ const [name, raw] = pair.split("=");
3490
+ if (name === "storageId" && raw) storageId = decodeURIComponent(raw);
3491
+ }
3492
+ } catch {
3493
+ return null;
3494
+ }
3495
+ if (!key || key.split("/").some((segment) => segment === "..")) return null;
3496
+ return storageId && storageId !== "(default)" ? {
3497
+ key,
3498
+ storageId
3499
+ } : { key };
3500
+ }
3501
+ /** The JSON in a JWT's payload segment, or `undefined` for anything else. */
3502
+ function jwtPayload(token) {
3503
+ const segments = token.split(".");
3504
+ if (segments.length !== 3) return void 0;
3505
+ try {
3506
+ const base64 = segments[1].replace(/-/g, "+").replace(/_/g, "/");
3507
+ const binary = atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
3508
+ const json = new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)));
3509
+ const payload = JSON.parse(json);
3510
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : void 0;
3511
+ } catch {
3512
+ return;
3513
+ }
3514
+ }
3515
+ /**
3516
+ * The object a private file's download URL was minted for, or `null` for any
3517
+ * other URL.
3518
+ *
3519
+ * Text written before storage references stored such URLs —
3520
+ * `…/storage/file/posts/cover.png?token=…` — and their tokens expired minutes
3521
+ * later. The token still names the object it was minted for, so the URL can be
3522
+ * traded for a fresh one, or for a reference. Read without verifying the
3523
+ * token: it grants nothing here, it only says which object is meant, and the
3524
+ * key in the URL must agree with it. Host-agnostic, since a URL written
3525
+ * through one origin may be read through another.
3526
+ *
3527
+ * @group Models
3528
+ */
3529
+ function storageTargetOfDownloadUrl(url) {
3530
+ let parsed;
3531
+ try {
3532
+ parsed = new URL(url, "http://relative.invalid");
3533
+ } catch {
3534
+ return null;
3535
+ }
3536
+ const at = parsed.pathname.indexOf("/storage/file/");
3537
+ const token = parsed.searchParams.get("token");
3538
+ if (at === -1 || !token) return null;
3539
+ let key;
3540
+ try {
3541
+ key = parsed.pathname.slice(at + 14).split("/").map(decodeURIComponent).join("/");
3542
+ } catch {
3543
+ return null;
3544
+ }
3545
+ if (!key || key.split("/").some((segment) => segment === "..")) return null;
3546
+ const payload = jwtPayload(token);
3547
+ if (payload?.purpose !== "file-read" || typeof payload.path !== "string") return null;
3548
+ const slash = payload.path.indexOf("/");
3549
+ if (payload.path !== key && payload.path.slice(slash + 1) !== key) return null;
3550
+ const named = parsed.searchParams.get("storageId") ?? (typeof payload.storageId === "string" ? payload.storageId : void 0);
3551
+ return named && named !== "(default)" ? {
3552
+ key,
3553
+ storageId: named
3554
+ } : { key };
3555
+ }
3556
+ /**
3557
+ * A private file's download URL as it may appear in text. Parentheses are
3558
+ * allowed in the path, where an unencoded file name puts them; the query, a
3559
+ * token, has none, so the match ends where a markdown destination does.
3560
+ */
3561
+ var DOWNLOAD_URL_PATTERN = /(?:https?:\/\/[^\s()<>"'\]]+)?\/storage\/file\/[^\s<>"'?#\]]+\?[^\s()<>"'#\]]*/g;
3562
+ /** Each download URL in `text` that names an object, with its reference. */
3563
+ function downloadUrlsIn(text) {
3564
+ const found = /* @__PURE__ */ new Map();
3565
+ if (!text.includes("/storage/file/")) return found;
3566
+ for (const url of text.match(DOWNLOAD_URL_PATTERN) ?? []) {
3567
+ const target = storageTargetOfDownloadUrl(url);
3568
+ if (target) found.set(url, storageReference(target.key, target.storageId));
3569
+ }
3570
+ return found;
3571
+ }
3572
+ /**
3573
+ * Every object `text` names: its storage references, and the private files'
3574
+ * download URLs saved in it before references existed. An object named more
3575
+ * than once may be listed more than once.
3576
+ *
3577
+ * @group Models
3578
+ */
3579
+ function storageTargetsInText(text) {
3580
+ return [...text.includes("rebase-storage:") ? text.match(STORAGE_REFERENCE_PATTERN) ?? [] : [], ...downloadUrlsIn(text).values()].map((reference) => parseStorageReference(reference)).filter((target) => target !== null);
3581
+ }
3582
+ /**
3583
+ * `text` with every private file's download URL replaced by the storage
3584
+ * reference for its object — for rewriting text saved before references, so
3585
+ * it stores what new text stores. Any other URL is left as it is.
3586
+ *
3587
+ * @group Models
3588
+ */
3589
+ function storageReferencesForDownloadUrls(text) {
3590
+ const urls = downloadUrlsIn(text);
3591
+ if (urls.size === 0) return text;
3592
+ return text.replace(DOWNLOAD_URL_PATTERN, (url) => urls.get(url) ?? url);
3593
+ }
3594
+ /**
3595
+ * Exchange every storage reference in `text` for a URL, at the moment the text
3596
+ * is rendered.
3597
+ *
3598
+ * ```ts
3599
+ * const html = markdown.render(await resolveStorageReferences(post.body, client));
3600
+ * ```
3601
+ *
3602
+ * Each distinct object is asked for once — `getSignedUrl(key)` on its source —
3603
+ * so a private object gets a freshly minted download URL every time the text is
3604
+ * rendered. A reference that cannot be resolved (no such object, an unknown
3605
+ * source) is left as it was, so a missing object reads as a missing image
3606
+ * rather than as a failed render.
3607
+ *
3608
+ * A private file's download URL saved in the text before references existed
3609
+ * is renewed the same way: its expired token still names the object (see
3610
+ * {@link storageTargetOfDownloadUrl}). One that cannot be resolved is left as
3611
+ * it was.
3612
+ *
3613
+ * @group Models
3614
+ */
3615
+ async function resolveStorageReferences(text, from) {
3616
+ if (!text) return text;
3617
+ const downloadUrls = downloadUrlsIn(text);
3618
+ if (!text.includes("rebase-storage:") && downloadUrls.size === 0) return text;
3619
+ const resolve = typeof from === "function" ? from : resolverFromSources(from);
3620
+ const references = /* @__PURE__ */ new Set([...text.match(STORAGE_REFERENCE_PATTERN) ?? [], ...downloadUrls.values()]);
3621
+ const urls = /* @__PURE__ */ new Map();
3622
+ await Promise.all([...references].map(async (reference) => {
3623
+ const target = parseStorageReference(reference);
3624
+ if (!target) return;
3625
+ try {
3626
+ const url = await resolve(target);
3627
+ if (url) urls.set(reference, url);
3628
+ } catch {}
3629
+ }));
3630
+ if (urls.size === 0) return text;
3631
+ return (downloadUrls.size === 0 ? text : text.replace(DOWNLOAD_URL_PATTERN, (saved) => {
3632
+ const reference = downloadUrls.get(saved);
3633
+ const url = reference === void 0 ? void 0 : urls.get(reference);
3634
+ return url === void 0 ? saved : asLinkDestination(url);
3635
+ })).replace(STORAGE_REFERENCE_PATTERN, (reference) => {
3636
+ const url = urls.get(reference);
3637
+ return url === void 0 ? reference : asLinkDestination(url);
3638
+ });
3639
+ }
3640
+ /**
3641
+ * A URL as one markdown link destination: whitespace, parentheses, angle
3642
+ * brackets and quotes percent-encoded, since any of them ends the destination
3643
+ * and turns the image into literal text. The URL means the same thing either
3644
+ * way.
3645
+ */
3646
+ var asLinkDestination = (url) => url.replace(/[\s()<>"']/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
3647
+ /** A resolver over a client's storage sources, one source per named key. */
3648
+ function resolverFromSources(client) {
3649
+ const named = /* @__PURE__ */ new Map();
3650
+ return async ({ key, storageId }) => {
3651
+ let source = client.storage;
3652
+ if (storageId) {
3653
+ if (!named.has(storageId)) named.set(storageId, client.createStorageSource?.(storageId));
3654
+ source = named.get(storageId);
3655
+ }
3656
+ if (!source) return null;
3657
+ const config = await source.getSignedUrl(key);
3658
+ return config.fileNotFound ? null : config.url;
3659
+ };
3660
+ }
2476
3661
  //#endregion
2477
- export { ADMIN_COLLECTION_KEYS, ADMIN_PROPERTY_KEYS, ALL_WHERE_FILTER_OPS, ANONYMOUS_USER_ID, ANONYMOUS_USER_IDS, BATCH_REF_KEY, BUNDLE_FORMAT_VERSION, CANONICAL_TO_REST, DEFAULT_CAPABILITIES, DEFAULT_COMMIT_PATHS, DEFAULT_DATA_SOURCE_KEY, DEFAULT_FILTERABLE_RELATION_KINDS, DEFAULT_FUZZY_THRESHOLD, DEFAULT_LIST_LIMIT, DEFAULT_RESOURCE_KEY, DEFAULT_SEARCH_COLUMN, DEFAULT_SEARCH_LANGUAGE, DEFAULT_SEARCH_MODE, DEFAULT_SEARCH_WEIGHT, DEFAULT_STORAGE_SOURCE_KEY, DEFAULT_TENANT_BYPASS_ROLES, DEFAULT_VECTOR_LIST_LIMIT, EntityReference, EntityRelation, FIELD_OPERATORS, FIREBASE_CAPABILITIES, GeoPoint, JUNCTION_PIVOT_KEY, LEGACY_RLS_JWT_SQL, LEGACY_RLS_ROLES_SQL, LEGACY_RLS_SCHEMA, LEGACY_RLS_UID_SQL, LIST_OPS, ListLimitError, MAX_INCLUDE_DEPTH, MAX_LIST_LIMIT, MONGODB_CAPABILITIES, NULL_OPS, POSTGRES_CAPABILITIES, PUBLIC_STORAGE_PREFIX, REBASE_SCHEMA, RELEVANCE_SORT_FIELD, RESERVED_BACKEND_PREFIXES, RESOURCE_GRAPH_VERSION, REST_TO_CANONICAL, RLS_IS_ANONYMOUS_SQL, RLS_JWT_SQL, RLS_ROLES_SQL, RLS_UID_SQL, RUNTIME_CONTRACT_VERSION, RebaseApiError, RebaseClientError, SCHEMA_VERSION_HEADER, Vector, amendResourceKind, bucket, buildResourceGraph, canonicalSchemaPayload, computeSchemaVersion, database, declareCron, declareFunction, declareResource, declaredDataSources, declaredDatabaseExtensions, declaredQueueConsumers, declaredResources, declaredStorageSources, declaredSubscriptions, deserializeCollections, encodeRelationAggregateSort, envBasesForResource, findEnvSuffixCollision, findStorageSuffixCollision, getCollectionDataPath, getDataSourceCapabilities, getDeclaredSubcollections, hasFieldOperation, hasForeignKeyOnTarget, isAnonymousUid, isBranchAdmin, isChannelBusInstance, isDocumentAdmin, isFieldOperation, isFirebaseCollectionConfig, isLazyComponentRef, isManyToMany, isMongoDBCollectionConfig, isPostgresCollectionConfig, isPublicStoragePath, isRelationAggregateSort, isRelationalCollectionConfig, isResourceHandle, isSQLAdmin, isSchemaAdmin, isSchemaEditingAdmin, isSerializedCollectionRef, isTenantClaimSource, isTenantMembershipSource, isToMany, isUnsupported, isValidEngine, nestAdminCollectionKeys, nestAdminKeysOf, nestAdminPropertyKeys, parseEnvBoolean, parseRelationAggregateSort, policy, queue, registerDataSourceCapabilities, registerResourceKind, reservedPrefixFor, resetDeclaredQueueConsumers, resetDeclaredResources, resetDeclaredSubscriptions, resolveClientListLimit, resolveResourceRefs, resourceEnvSuffix, resourceId, resourceKeyOf, resourceKind, resourceKinds, resourceToDataSource, resourceToStorageSource, rewriteLegacyRlsFunctions, serializeCollections, setQueueRuntime, setTopicRuntime, sortKeyToString, storageEnvSuffix, toCanonicalOp, topic, unsupportedMethod, usesLegacyRlsFunctions };
3662
+ export { ADMIN_COLLECTION_KEYS, ADMIN_PROPERTY_KEYS, ADMIN_ROLE, ADMIN_SCOPES, ALL_WHERE_FILTER_OPS, ANONYMOUS_USER_ID, ANONYMOUS_USER_IDS, BATCH_REF_KEY, BUILT_IN_SCOPES, BUILT_IN_SCOPE_DESCRIPTIONS, BUNDLE_FORMAT_VERSION, CANONICAL_TO_REST, DATA_PLANE_SCOPES, DEFAULT_CAPABILITIES, DEFAULT_COMMIT_PATHS, DEFAULT_DATA_SOURCE_KEY, DEFAULT_FILTERABLE_RELATION_KINDS, DEFAULT_FUZZY_THRESHOLD, DEFAULT_LIST_LIMIT, DEFAULT_RESOURCE_KEY, DEFAULT_SEARCH_COLUMN, DEFAULT_SEARCH_LANGUAGE, DEFAULT_SEARCH_MODE, DEFAULT_SEARCH_WEIGHT, DEFAULT_STORAGE_SOURCE_KEY, DEFAULT_TENANT_BYPASS_ROLES, DEFAULT_VECTOR_LIST_LIMIT, EMPTY_ACCESS_MODEL, EntityReference, EntityRelation, FIELD_OPERATORS, FIREBASE_CAPABILITIES, GeoPoint, IMPERSONATE_HEADER, INCLUDE_COUNT_KEY, JUNCTION_PIVOT_KEY, KEY_MANAGEMENT_SCOPES, LEGACY_RLS_JWT_SQL, LEGACY_RLS_ROLES_SQL, LEGACY_RLS_SCHEMA, LEGACY_RLS_UID_SQL, LIST_OPS, ListLimitError, MAX_INCLUDE_DEPTH, MAX_LIST_LIMIT, MONGODB_CAPABILITIES, NULL_OPS, POSTGRES_CAPABILITIES, PUBLIC_STORAGE_PREFIX, REBASE_SCHEMA, RELEVANCE_SORT_FIELD, RESERVED_BACKEND_PREFIXES, RESOURCE_GRAPH_VERSION, REST_TO_CANONICAL, RLS_IS_ANONYMOUS_SQL, RLS_JWT_SQL, RLS_ROLES_SQL, RLS_UID_SQL, RUNTIME_CONTRACT_VERSION, RUNTIME_ONLY_COLLECTION_KEYS, RebaseApiError, RebaseClientError, SCHEMA_VERSION_HEADER, SCOPE_TARGETS, STORAGE_REFERENCE_SCHEME, Vector, amendResourceKind, applyCollectionPatch, bucket, buildResourceGraph, canonicalSchemaPayload, collectionPatchProblems, computeSchemaVersion, database, declareCron, declareFunction, declareResource, declaredDataSources, declaredDatabaseExtensions, declaredQueueConsumers, declaredResources, declaredStorageSources, declaredSubscriptions, describeScope, deserializeCollections, diffCollections, diffJsonValues, encodeRelationAggregateSort, envBasesForResource, findEnvSuffixCollision, findStorageSuffixCollision, getCollectionDataPath, getDataSourceCapabilities, getDeclaredSubcollections, hasAdminRole, hasFieldOperation, hasForeignKeyOnTarget, intersectScopes, isAdminScope, isAnonymousUid, isBranchAdmin, isBuiltInScope, isChannelBusInstance, isCollectionPatch, isDataPlaneScope, isDocumentAdmin, isFieldOperation, isFirebaseCollectionConfig, isLazyComponentRef, isManyToMany, isMongoDBCollectionConfig, isPostgresCollectionConfig, isPublicStorageKey, isPublicStoragePath, isRelationAggregateSort, isRelationalCollectionConfig, isResourceHandle, isSQLAdmin, isSchemaAdmin, isSchemaEditingAdmin, isSerializedCollectionRef, isStorageReference, isTenantClaimSource, isTenantMembershipSource, isToMany, isUnsupported, isValidAppHost, isValidEngine, knownScopes, nestAdminCollectionKeys, nestAdminKeysOf, nestAdminPropertyKeys, nestCollectionPatchPaths, parseAppAddress, parseEnvBoolean, parseRelationAggregateSort, parseScope, parseStorageReference, policy, queue, registerDataSourceCapabilities, registerResourceKind, reservedPrefixFor, resetDeclaredQueueConsumers, resetDeclaredResources, resetDeclaredSubscriptions, resolveClientListLimit, resolveResourceRefs, resolveStorageReferences, resourceEnvSuffix, resourceId, resourceKeyOf, resourceKind, resourceKinds, resourceToDataSource, resourceToStorageSource, rewriteLegacyRlsFunctions, runtimeOnlyKeyIn, scopeAcceptsTarget, scopeGrants, scopeGrantsAny, scopeTargets, scopesBeyond, scopesForRoles, serializeCollections, setQueueRuntime, setTopicRuntime, sortKeyToString, storageEnvSuffix, storageObjectPath, storageReference, storageReferencesForDownloadUrls, storageTargetOfDownloadUrl, storageTargetsInText, summarizeRoles, summarizeScopes, toCanonicalOp, toCollectionJson, topic, unsupportedMethod, usesLegacyRlsFunctions, validateScopes };
2478
3663
 
2479
3664
  //# sourceMappingURL=index.es.js.map