@rebasepro/types 0.23.0 → 0.24.1-canary.g56af4a4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
  /**
@@ -1663,10 +1965,15 @@ amendResourceKind("bucket", {
1663
1965
  "S3_FORCE_PATH_STYLE",
1664
1966
  "GCS_BUCKET",
1665
1967
  "GCS_PROJECT_ID",
1666
- "GCS_KEY_FILENAME"
1968
+ "GCS_KEY_FILENAME",
1969
+ "STORAGE_MAX_FILE_SIZE"
1667
1970
  ],
1668
1971
  envBasesByEngine: {
1669
- local: ["STORAGE_TYPE", "STORAGE_PATH"],
1972
+ local: [
1973
+ "STORAGE_TYPE",
1974
+ "STORAGE_PATH",
1975
+ "STORAGE_MAX_FILE_SIZE"
1976
+ ],
1670
1977
  s3: [
1671
1978
  "STORAGE_TYPE",
1672
1979
  "S3_BUCKET",
@@ -1674,13 +1981,15 @@ amendResourceKind("bucket", {
1674
1981
  "S3_ACCESS_KEY_ID",
1675
1982
  "S3_SECRET_ACCESS_KEY",
1676
1983
  "S3_ENDPOINT",
1677
- "S3_FORCE_PATH_STYLE"
1984
+ "S3_FORCE_PATH_STYLE",
1985
+ "STORAGE_MAX_FILE_SIZE"
1678
1986
  ],
1679
1987
  gcs: [
1680
1988
  "STORAGE_TYPE",
1681
1989
  "GCS_BUCKET",
1682
1990
  "GCS_PROJECT_ID",
1683
- "GCS_KEY_FILENAME"
1991
+ "GCS_KEY_FILENAME",
1992
+ "STORAGE_MAX_FILE_SIZE"
1684
1993
  ],
1685
1994
  azure: [],
1686
1995
  firebase: []
@@ -1698,19 +2007,73 @@ registerResourceKind({
1698
2007
  implicitDefault: false
1699
2008
  });
1700
2009
  amendResourceKind("topic", { envBases: [] });
1701
- var runtimeHolder = { current: null };
2010
+ /**
2011
+ * Declared handlers and installed runtimes, for topics and queues — on
2012
+ * `globalThis` under a shared symbol, never in module locals.
2013
+ *
2014
+ * A module local is one per COPY of this package, and two copies in one
2015
+ * process is the normal layout rather than an accident: `@rebasepro/server`
2016
+ * inlines this package into its own dist, while `config/resources.ts` imports
2017
+ * it from `node_modules`. With locals, a project's `.handler()` and
2018
+ * `.subscription()` went into the project's copy and the worker read the
2019
+ * server's empty one (`Job queue started {"tasks":0}`), and the runtime the
2020
+ * server installed was invisible to the project's `enqueue`/`publish` — so
2021
+ * every queue and topic threw "no runtime is installed" inside a running
2022
+ * backend. The CLI deriving the graph had the same split. Same mechanism and
2023
+ * reasoning as `registry()` in resources.ts; `module-state-across-copies.test.ts`
2024
+ * loads two copies to hold it, and scans for any other module-level state that
2025
+ * would split the same way.
2026
+ *
2027
+ * The version is in the symbol: a change to this object's SHAPE takes a new
2028
+ * suffix, so no copy ever reads a shape it does not know.
2029
+ */
2030
+ var WORK_KEY = Symbol.for("@rebasepro/types.workRegistry.v1");
2031
+ function work() {
2032
+ const g = globalThis;
2033
+ let shared = g[WORK_KEY];
2034
+ if (!shared) {
2035
+ shared = {
2036
+ topicRuntime: null,
2037
+ subscriptions: [],
2038
+ queueRuntime: null,
2039
+ queueConsumers: /* @__PURE__ */ new Map()
2040
+ };
2041
+ g[WORK_KEY] = shared;
2042
+ }
2043
+ return shared;
2044
+ }
2045
+ /**
2046
+ * Where `@rebasepro/server` publishes its client once it has booted (its
2047
+ * `singleton.ts`). Read only to tell the two causes of a missing runtime apart.
2048
+ */
2049
+ var SERVER_INSTANCE_SLOT = Symbol.for("@rebasepro/server:singleton-instance");
2050
+ /**
2051
+ * Why there is no runtime to send through, as far as this process can tell.
2052
+ *
2053
+ * Outside a backend — the CLI evaluating config, a script, a unit test — the
2054
+ * answer is that there is nothing to deliver to. Inside one it is not: the
2055
+ * backend installs a runtime at boot for what `config/resources.ts` declared,
2056
+ * so a missing one there means boot never saw the declaration, or the backend
2057
+ * carries a copy of this package too old to share the slot it installs into.
2058
+ * Saying "outside a backend" to somebody whose backend is running sends them
2059
+ * looking for the wrong thing.
2060
+ */
2061
+ function missingRuntimeCause(kind) {
2062
+ 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).";
2063
+ 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.`;
2064
+ }
1702
2065
  /** Install the transport topics publish through. Called by the server at boot. */
1703
2066
  function setTopicRuntime(runtime) {
1704
- runtimeHolder.current = runtime;
2067
+ work().topicRuntime = runtime;
1705
2068
  }
1706
- var subscriptions = [];
1707
2069
  /** Every declared subscription, for the worker to wire and the graph to record. */
1708
2070
  function declaredSubscriptions(topic) {
2071
+ const { subscriptions } = work();
1709
2072
  return topic ? subscriptions.filter((s) => s.topic === topic) : subscriptions.slice();
1710
2073
  }
1711
2074
  /** Forget declared subscriptions. For tests, alongside `resetDeclaredResources`. */
1712
2075
  function resetDeclaredSubscriptions() {
1713
- subscriptions.length = 0;
2076
+ work().subscriptions.length = 0;
1714
2077
  }
1715
2078
  /**
1716
2079
  * Declare a topic.
@@ -1729,12 +2092,13 @@ function topic(key, options = {}) {
1729
2092
  return key;
1730
2093
  },
1731
2094
  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).`);
2095
+ const runtime = work().topicRuntime;
2096
+ if (!runtime) throw new Error(`Cannot publish to topic "${key}": no topic runtime is installed. ${missingRuntimeCause("topic")}`);
1734
2097
  await runtime.publish(key, event);
1735
2098
  },
1736
2099
  subscription(name, handler, subOptions = {}) {
1737
2100
  if (!name || name.trim() === "") throw new Error(`A subscription on topic "${key}" needs a non-empty name.`);
2101
+ const { subscriptions } = work();
1738
2102
  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
2103
  subscriptions.push({
1740
2104
  topic: key,
@@ -1797,19 +2161,17 @@ registerResourceKind({
1797
2161
  optionKeys: ["maxAttempts"],
1798
2162
  implicitDefault: false
1799
2163
  });
1800
- var queueRuntimeHolder = { current: null };
1801
2164
  /** Install the transport queues enqueue through. Called by the server at boot. */
1802
2165
  function setQueueRuntime(runtime) {
1803
- queueRuntimeHolder.current = runtime;
2166
+ work().queueRuntime = runtime;
1804
2167
  }
1805
- var queueConsumers = /* @__PURE__ */ new Map();
1806
2168
  /** Every declared queue handler, for the worker to wire. */
1807
2169
  function declaredQueueConsumers() {
1808
- return [...queueConsumers.values()];
2170
+ return [...work().queueConsumers.values()];
1809
2171
  }
1810
2172
  /** Forget declared queue handlers. For tests, alongside `resetDeclaredResources`. */
1811
2173
  function resetDeclaredQueueConsumers() {
1812
- queueConsumers.clear();
2174
+ work().queueConsumers.clear();
1813
2175
  }
1814
2176
  /**
1815
2177
  * Declare a queue.
@@ -1831,11 +2193,12 @@ function queue(key, options = {}) {
1831
2193
  return key;
1832
2194
  },
1833
2195
  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).`);
2196
+ const runtime = work().queueRuntime;
2197
+ if (!runtime) throw new Error(`Cannot enqueue on queue "${key}": no queue runtime is installed. ${missingRuntimeCause("queue")}`);
1836
2198
  return runtime.enqueue(key, payload, enqueueOptions);
1837
2199
  },
1838
2200
  handler(fn) {
2201
+ const { queueConsumers } = work();
1839
2202
  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
2203
  queueConsumers.set(key, {
1841
2204
  queue: key,
@@ -1971,6 +2334,426 @@ function isLazyComponentRef(ref) {
1971
2334
  return typeof ref === "object" && ref !== null && "__rebaseLazy" in ref && ref.__rebaseLazy === true;
1972
2335
  }
1973
2336
  //#endregion
2337
+ //#region src/types/auth_adapter.ts
2338
+ /**
2339
+ * The request header that asks for a data-plane request to run as another
2340
+ * user, named by uid.
2341
+ *
2342
+ * The server honours it only on a signed-in administrator's own session —
2343
+ * never on an API key or the service key — and only through an adapter that
2344
+ * implements {@link AuthAdapter.resolveUser}. Everywhere else a request that
2345
+ * carries it is refused, never run as its caller.
2346
+ *
2347
+ * @group Auth
2348
+ */
2349
+ var IMPERSONATE_HEADER = "x-rebase-impersonate";
2350
+ //#endregion
2351
+ //#region src/types/scopes.ts
2352
+ /**
2353
+ * Scopes — what a credential may do, named `resource:action`.
2354
+ *
2355
+ * One vocabulary for every way of reaching a Rebase backend: a person's
2356
+ * session, a service key, a personal key and an OAuth token for `/mcp` all
2357
+ * answer "may this caller do X?" with the same strings, so a grant reads the
2358
+ * same on a key, on a role and on a consent screen.
2359
+ *
2360
+ * A scope may carry a target — `data:read:posts` is `data:read` on the `posts`
2361
+ * collection only. The unqualified scope covers every target. Only the data
2362
+ * plane and app scopes take targets; see {@link SCOPE_TARGETS}.
2363
+ *
2364
+ * Two planes, held differently:
2365
+ *
2366
+ * - **The data plane** (`data:*`, `storage:*`, `functions:invoke`). Every
2367
+ * signed-in person holds all of it, because what a person may do with data
2368
+ * is decided row by row by the collection's `securityRules` and by storage
2369
+ * policies, never by a scope. On a key or a token these scopes narrow: a key
2370
+ * holding `data:read:posts` reaches nothing else, whatever RLS would allow.
2371
+ * - **The admin plane** (everything else). Nobody holds it implicitly. The
2372
+ * `admin` role holds all of it; any other role holds what the app declares
2373
+ * for it under `auth.roles` on the users collection.
2374
+ *
2375
+ * Apps declare their own scopes too (`auth.scopes`), named the same way —
2376
+ * `project:deploy`. Every signed-in person holds every app scope: like the data
2377
+ * plane, the function behind it decides whether the person may act, and the
2378
+ * scope exists so a key can be narrowed to that one action.
2379
+ *
2380
+ * @module
2381
+ */
2382
+ /** @group Auth */
2383
+ var DATA_PLANE_SCOPES = [
2384
+ "data:read",
2385
+ "data:write",
2386
+ "data:delete",
2387
+ "storage:read",
2388
+ "storage:write",
2389
+ "storage:delete",
2390
+ "functions:invoke"
2391
+ ];
2392
+ /** @group Auth */
2393
+ var ADMIN_SCOPES = [
2394
+ "users:read",
2395
+ "users:write",
2396
+ "schema:read",
2397
+ "schema:write",
2398
+ "database:read",
2399
+ "database:write",
2400
+ "backups:read",
2401
+ "cron:read",
2402
+ "cron:write",
2403
+ "logs:read",
2404
+ "keys:read",
2405
+ "keys:write"
2406
+ ];
2407
+ /** Every scope Rebase itself defines, in display order. @group Auth */
2408
+ var BUILT_IN_SCOPES = [...DATA_PLANE_SCOPES, ...ADMIN_SCOPES];
2409
+ /**
2410
+ * Scopes no key may hold.
2411
+ *
2412
+ * A key that could manage keys could mint its own successor, widen itself, or
2413
+ * revoke the keys other integrations run on — and revoking it would undo none
2414
+ * of that. Managing keys is for a person (or the service key).
2415
+ *
2416
+ * @group Auth
2417
+ */
2418
+ var KEY_MANAGEMENT_SCOPES = ["keys:read", "keys:write"];
2419
+ /** The one built-in role. It holds every scope and is the RLS `admin` role. @group Auth */
2420
+ var ADMIN_ROLE = "admin";
2421
+ /**
2422
+ * The built-in scopes that accept a target, and what the target names.
2423
+ *
2424
+ * - `data:*` — a collection slug
2425
+ * - `storage:*` — a storage source id (`(default)` for the default source)
2426
+ * - `functions:invoke` — a custom function's name
2427
+ *
2428
+ * @group Auth
2429
+ */
2430
+ var SCOPE_TARGETS = {
2431
+ "data:read": "collection",
2432
+ "data:write": "collection",
2433
+ "data:delete": "collection",
2434
+ "storage:read": "bucket",
2435
+ "storage:write": "bucket",
2436
+ "storage:delete": "bucket",
2437
+ "functions:invoke": "function"
2438
+ };
2439
+ /** @group Auth */
2440
+ var BUILT_IN_SCOPE_DESCRIPTIONS = {
2441
+ "data:read": {
2442
+ label: "Read data",
2443
+ description: "Read rows from collections, through the caller's row-level security."
2444
+ },
2445
+ "data:write": {
2446
+ label: "Create and update data",
2447
+ description: "Create and update rows in collections, through the caller's row-level security."
2448
+ },
2449
+ "data:delete": {
2450
+ label: "Delete data",
2451
+ description: "Delete rows from collections, through the caller's row-level security."
2452
+ },
2453
+ "storage:read": {
2454
+ label: "Read files",
2455
+ description: "List and download stored files."
2456
+ },
2457
+ "storage:write": {
2458
+ label: "Upload files",
2459
+ description: "Upload files and create folders."
2460
+ },
2461
+ "storage:delete": {
2462
+ label: "Delete files",
2463
+ description: "Delete stored files."
2464
+ },
2465
+ "functions:invoke": {
2466
+ label: "Call functions",
2467
+ description: "Call the backend's custom functions. A function can do anything its code does."
2468
+ },
2469
+ "users:read": {
2470
+ label: "See users",
2471
+ description: "List user accounts and their roles."
2472
+ },
2473
+ "users:write": {
2474
+ label: "Manage users",
2475
+ description: "Create, edit and delete accounts, reset passwords and second factors, and assign roles up to the holder's own."
2476
+ },
2477
+ "schema:read": {
2478
+ label: "See the schema",
2479
+ description: "Read the collection schema, plan schema changes and run the RLS audit."
2480
+ },
2481
+ "schema:write": {
2482
+ label: "Change the schema",
2483
+ description: "Apply schema changes: edits collection files and alters the database."
2484
+ },
2485
+ "database:read": {
2486
+ label: "Inspect the database",
2487
+ description: "List databases, tables, Postgres roles and branches."
2488
+ },
2489
+ "database:write": {
2490
+ label: "Run SQL",
2491
+ description: "Run SQL as the database owner, outside row-level security, and create or delete branches."
2492
+ },
2493
+ "backups:read": {
2494
+ label: "Download backups",
2495
+ description: "List and download database backups — every row, outside row-level security."
2496
+ },
2497
+ "cron:read": {
2498
+ label: "See cron jobs",
2499
+ description: "List cron jobs and read their run history."
2500
+ },
2501
+ "cron:write": {
2502
+ label: "Run cron jobs",
2503
+ description: "Trigger cron jobs and switch them on or off."
2504
+ },
2505
+ "logs:read": {
2506
+ label: "Read server logs",
2507
+ description: "Read the server's request and application logs."
2508
+ },
2509
+ "keys:read": {
2510
+ label: "See service keys",
2511
+ description: "List the project's service keys. Never grantable to a key."
2512
+ },
2513
+ "keys:write": {
2514
+ label: "Manage service keys",
2515
+ description: "Create, change and revoke the project's service keys. Never grantable to a key."
2516
+ }
2517
+ };
2518
+ /** The access model of an app that declares nothing: built-ins only. @group Auth */
2519
+ var EMPTY_ACCESS_MODEL = {
2520
+ roles: {},
2521
+ scopes: {}
2522
+ };
2523
+ var SCOPE_PART = /^[a-z][a-z0-9-]*$/;
2524
+ /**
2525
+ * Split `resource:action[:target]`. Returns null for anything else.
2526
+ *
2527
+ * The target is everything after the second colon, so a target may itself
2528
+ * contain colons. Resource and action are lower-case words.
2529
+ *
2530
+ * @group Auth
2531
+ */
2532
+ function parseScope(value) {
2533
+ if (typeof value !== "string") return null;
2534
+ const first = value.indexOf(":");
2535
+ if (first < 0) return null;
2536
+ const second = value.indexOf(":", first + 1);
2537
+ const resource = value.slice(0, first);
2538
+ const action = second < 0 ? value.slice(first + 1) : value.slice(first + 1, second);
2539
+ if (!SCOPE_PART.test(resource) || !SCOPE_PART.test(action)) return null;
2540
+ if (second < 0) return { scope: `${resource}:${action}` };
2541
+ const target = value.slice(second + 1);
2542
+ if (target.length === 0 || /\s/.test(target)) return null;
2543
+ return {
2544
+ scope: `${resource}:${action}`,
2545
+ target
2546
+ };
2547
+ }
2548
+ /** @group Auth */
2549
+ function isBuiltInScope(scope) {
2550
+ return BUILT_IN_SCOPES.includes(scope);
2551
+ }
2552
+ /** @group Auth */
2553
+ function isDataPlaneScope(scope) {
2554
+ return DATA_PLANE_SCOPES.includes(scope);
2555
+ }
2556
+ /** @group Auth */
2557
+ function isAdminScope(scope) {
2558
+ return ADMIN_SCOPES.includes(scope);
2559
+ }
2560
+ /** Does this list of roles include the built-in `admin` role? @group Auth */
2561
+ function hasAdminRole(roles) {
2562
+ return !!roles?.includes(ADMIN_ROLE);
2563
+ }
2564
+ /**
2565
+ * Does a set of held scopes grant `scope`, optionally on one `target`?
2566
+ *
2567
+ * The unqualified grant covers every target. A qualified grant covers its own
2568
+ * target only, and never answers an unqualified question: a key holding
2569
+ * `data:read:posts` is not asked "may you read data?" and told yes.
2570
+ *
2571
+ * @group Auth
2572
+ */
2573
+ function scopeGrants(held, scope, target) {
2574
+ for (const entry of held) {
2575
+ if (entry === scope) return true;
2576
+ if (target !== void 0 && entry === `${scope}:${target}`) return true;
2577
+ }
2578
+ return false;
2579
+ }
2580
+ /**
2581
+ * Does a set of held scopes grant `scope` on at least one target?
2582
+ *
2583
+ * For a surface that lists what is reachable — the collections an MCP client
2584
+ * may query — and filters per item afterwards.
2585
+ *
2586
+ * @group Auth
2587
+ */
2588
+ function scopeGrantsAny(held, scope) {
2589
+ const prefix = `${scope}:`;
2590
+ return held.some((entry) => entry === scope || entry.startsWith(prefix));
2591
+ }
2592
+ /**
2593
+ * The targets a held set narrows `scope` to: `"all"` when it holds the
2594
+ * unqualified scope, otherwise the listed targets (possibly none).
2595
+ *
2596
+ * @group Auth
2597
+ */
2598
+ function scopeTargets(held, scope) {
2599
+ const prefix = `${scope}:`;
2600
+ const targets = [];
2601
+ for (const entry of held) {
2602
+ if (entry === scope) return "all";
2603
+ if (entry.startsWith(prefix)) targets.push(entry.slice(prefix.length));
2604
+ }
2605
+ return targets;
2606
+ }
2607
+ /**
2608
+ * Every scope a person holding `roles` has.
2609
+ *
2610
+ * The data plane and every app scope always; the whole admin plane with the
2611
+ * `admin` role; otherwise each declared role's scopes. A role nobody declared
2612
+ * adds nothing — it may still mean something to an RLS policy.
2613
+ *
2614
+ * @group Auth
2615
+ */
2616
+ function scopesForRoles(roles, model = EMPTY_ACCESS_MODEL) {
2617
+ const held = /* @__PURE__ */ new Set([...DATA_PLANE_SCOPES, ...Object.keys(model.scopes)]);
2618
+ if (hasAdminRole(roles)) {
2619
+ for (const scope of ADMIN_SCOPES) held.add(scope);
2620
+ return [...held];
2621
+ }
2622
+ for (const role of roles ?? []) {
2623
+ const definition = Object.prototype.hasOwnProperty.call(model.roles, role) ? model.roles[role] : void 0;
2624
+ for (const scope of definition?.scopes ?? []) held.add(scope);
2625
+ }
2626
+ return [...held];
2627
+ }
2628
+ /**
2629
+ * Every scope this access model knows, unqualified: the built-ins, then the
2630
+ * app's own.
2631
+ *
2632
+ * @group Auth
2633
+ */
2634
+ function knownScopes(model = EMPTY_ACCESS_MODEL) {
2635
+ return [...BUILT_IN_SCOPES, ...Object.keys(model.scopes)];
2636
+ }
2637
+ /** Does this scope accept a target? @group Auth */
2638
+ function scopeAcceptsTarget(scope, model = EMPTY_ACCESS_MODEL) {
2639
+ if (isBuiltInScope(scope)) return SCOPE_TARGETS[scope] !== void 0;
2640
+ return (Object.prototype.hasOwnProperty.call(model.scopes, scope) ? model.scopes[scope] : void 0)?.target !== void 0;
2641
+ }
2642
+ /**
2643
+ * Check a list of scope strings against an access model. Returns the
2644
+ * problems, empty when every entry is a known scope with an acceptable target.
2645
+ *
2646
+ * @group Auth
2647
+ */
2648
+ function validateScopes(values, model = EMPTY_ACCESS_MODEL) {
2649
+ const known = new Set(knownScopes(model));
2650
+ const problems = [];
2651
+ for (const value of values) {
2652
+ const text = typeof value === "string" ? value : String(value);
2653
+ const parsed = typeof value === "string" ? parseScope(value) : null;
2654
+ if (!parsed) problems.push({
2655
+ scope: text,
2656
+ reason: "malformed"
2657
+ });
2658
+ else if (!known.has(parsed.scope)) problems.push({
2659
+ scope: text,
2660
+ reason: "unknown"
2661
+ });
2662
+ else if (parsed.target !== void 0 && !scopeAcceptsTarget(parsed.scope, model)) problems.push({
2663
+ scope: text,
2664
+ reason: "target-not-accepted"
2665
+ });
2666
+ }
2667
+ return problems;
2668
+ }
2669
+ /**
2670
+ * The entries of `requested` that `held` does not cover — what a credential
2671
+ * would hold beyond its minter. Empty means the request is within reach.
2672
+ *
2673
+ * `data:read:posts` is covered by `data:read`; `data:read` is not covered by
2674
+ * `data:read:posts`.
2675
+ *
2676
+ * @group Auth
2677
+ */
2678
+ function scopesBeyond(requested, held) {
2679
+ return requested.filter((entry) => {
2680
+ const parsed = parseScope(entry);
2681
+ if (!parsed) return true;
2682
+ return !scopeGrants(held, parsed.scope, parsed.target);
2683
+ });
2684
+ }
2685
+ /**
2686
+ * Narrow `requested` to what `held` covers, entry by entry. What a personal
2687
+ * key holds today when its owner has lost a role since it was minted.
2688
+ *
2689
+ * @group Auth
2690
+ */
2691
+ function intersectScopes(requested, held) {
2692
+ const beyond = new Set(scopesBeyond(requested, held));
2693
+ return requested.filter((entry) => !beyond.has(entry));
2694
+ }
2695
+ /**
2696
+ * How a scope is worded for a person: the built-in description, the app's
2697
+ * declared label, or the raw string when nothing describes it. A target is
2698
+ * named after the label.
2699
+ *
2700
+ * @group Auth
2701
+ */
2702
+ function describeScope(value, model = EMPTY_ACCESS_MODEL) {
2703
+ const parsed = parseScope(value);
2704
+ if (!parsed) return {
2705
+ label: value,
2706
+ description: ""
2707
+ };
2708
+ let base;
2709
+ if (isBuiltInScope(parsed.scope)) base = BUILT_IN_SCOPE_DESCRIPTIONS[parsed.scope];
2710
+ else if (Object.prototype.hasOwnProperty.call(model.scopes, parsed.scope)) {
2711
+ const definition = model.scopes[parsed.scope];
2712
+ base = {
2713
+ label: definition.label,
2714
+ description: definition.description ?? ""
2715
+ };
2716
+ }
2717
+ const label = base?.label ?? parsed.scope;
2718
+ return {
2719
+ label: parsed.target !== void 0 ? `${label}: ${parsed.target}` : label,
2720
+ description: base?.description ?? ""
2721
+ };
2722
+ }
2723
+ /** The roles of an access model, `admin` first. @group Auth */
2724
+ function summarizeRoles(model = EMPTY_ACCESS_MODEL) {
2725
+ return [{
2726
+ id: ADMIN_ROLE,
2727
+ name: "Admin",
2728
+ description: "Holds every scope. Row-level security does not limit it: it reads, changes and deletes every row.",
2729
+ scopes: [...ADMIN_SCOPES],
2730
+ builtIn: true
2731
+ }, ...Object.entries(model.roles).map(([id, definition]) => ({
2732
+ id,
2733
+ name: definition.name ?? id,
2734
+ ...definition.description ? { description: definition.description } : {},
2735
+ scopes: [...definition.scopes],
2736
+ builtIn: false
2737
+ }))];
2738
+ }
2739
+ /** Every scope of an access model, described, in display order. @group Auth */
2740
+ function summarizeScopes(model = EMPTY_ACCESS_MODEL) {
2741
+ const builtIn = BUILT_IN_SCOPES.map((scope) => ({
2742
+ scope,
2743
+ ...BUILT_IN_SCOPE_DESCRIPTIONS[scope],
2744
+ ...SCOPE_TARGETS[scope] ? { target: SCOPE_TARGETS[scope] } : {},
2745
+ plane: isDataPlaneScope(scope) ? "data" : "admin"
2746
+ }));
2747
+ const app = Object.entries(model.scopes).map(([scope, definition]) => ({
2748
+ scope,
2749
+ label: definition.label,
2750
+ description: definition.description ?? "",
2751
+ ...definition.target ? { target: definition.target } : {},
2752
+ plane: "app"
2753
+ }));
2754
+ return [...builtIn, ...app];
2755
+ }
2756
+ //#endregion
1974
2757
  //#region src/types/project_manifest.ts
1975
2758
  /**
1976
2759
  * Path prefixes the backend owns, which no static app may claim.
@@ -2008,6 +2791,91 @@ function reservedPrefixFor(path) {
2008
2791
  return RESERVED_BACKEND_PREFIXES.find((reserved) => normalized === reserved || normalized.startsWith(`${reserved}/`));
2009
2792
  }
2010
2793
  /**
2794
+ * Split a static app's declared `path` into the hostname it claims and the path
2795
+ * under it.
2796
+ *
2797
+ * `"/admin"` is `{ path: "/admin" }`, returned as written: the path rules
2798
+ * (leading slash, no `..`, reserved prefixes) belong to the caller, which
2799
+ * applies them to the path part of either form. `"https://admin.example.com/cms"`
2800
+ * is `{ host: "admin.example.com", path: "/cms" }`.
2801
+ *
2802
+ * Shared because three parties must read the same value the same way: the CLI
2803
+ * while `rebase.json` is edited, the control plane at deploy intake (a bundle can
2804
+ * come from any CLI, or none), and the runtime choosing which app answers.
2805
+ */
2806
+ function parseAppAddress(value) {
2807
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
2808
+ if (!value.startsWith("/") && isValidAppHost(value.toLowerCase().replace(/\/.*$/, ""))) return {
2809
+ ok: false,
2810
+ reason: `looks like a hostname — write it as a URL: "https://${value}"`
2811
+ };
2812
+ return {
2813
+ ok: true,
2814
+ address: { path: value }
2815
+ };
2816
+ }
2817
+ if (/[\s\\\u0000-\u001f\u007f]/.test(value)) return {
2818
+ ok: false,
2819
+ reason: "must not contain spaces, control characters or backslashes"
2820
+ };
2821
+ const authority = value.slice(value.indexOf("//") + 2).split(/[/?#]/)[0];
2822
+ if (/:[^@]*$/.test(authority) && !authority.startsWith("[")) return {
2823
+ ok: false,
2824
+ reason: "must not name a port — a hostname is served on 443"
2825
+ };
2826
+ let url;
2827
+ try {
2828
+ url = new URL(value);
2829
+ } catch {
2830
+ return {
2831
+ ok: false,
2832
+ reason: "is not a valid URL"
2833
+ };
2834
+ }
2835
+ if (url.protocol !== "https:") return {
2836
+ ok: false,
2837
+ reason: `must use https — the platform serves every hostname over TLS: "https://${url.host}${url.pathname === "/" ? "" : url.pathname}"`
2838
+ };
2839
+ if (url.username || url.password) return {
2840
+ ok: false,
2841
+ reason: "must not contain credentials"
2842
+ };
2843
+ if (url.port) return {
2844
+ ok: false,
2845
+ reason: "must not name a port — a hostname is served on 443"
2846
+ };
2847
+ if (url.search || url.hash || /[?#]/.test(value)) return {
2848
+ ok: false,
2849
+ reason: "must not have a query or fragment"
2850
+ };
2851
+ if (!isValidAppHost(url.hostname)) return {
2852
+ ok: false,
2853
+ reason: `names "${url.hostname}", which is not a public hostname`
2854
+ };
2855
+ const authorityAndPath = value.slice(value.indexOf("//") + 2);
2856
+ const slash = authorityAndPath.indexOf("/");
2857
+ return {
2858
+ ok: true,
2859
+ address: {
2860
+ host: url.hostname,
2861
+ path: slash === -1 ? "/" : authorityAndPath.slice(slash)
2862
+ }
2863
+ };
2864
+ }
2865
+ /**
2866
+ * Whether `host` is a public DNS name an app can be given: lowercase labels of
2867
+ * letters, digits and hyphens, at least two of them, not an IP address and not
2868
+ * `localhost`. Internationalised names arrive here already in punycode.
2869
+ */
2870
+ function isValidAppHost(host) {
2871
+ if (host.length === 0 || host.length > 253 || host !== host.toLowerCase()) return false;
2872
+ const labels = host.split(".");
2873
+ if (labels.length < 2) return false;
2874
+ const label = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
2875
+ if (!labels.every((part) => label.test(part))) return false;
2876
+ return !/^[0-9]+$/.test(labels[labels.length - 1]);
2877
+ }
2878
+ /**
2011
2879
  * Version of the bundle *format* itself.
2012
2880
  *
2013
2881
  * Bumped only when the on-disk layout changes in a way an older runtime could
@@ -2370,10 +3238,13 @@ var BATCH_REF_KEY = "$ref";
2370
3238
  * one, which is the case worth catching.
2371
3239
  *
2372
3240
  * 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.
3241
+ * column as a JSON document is the failure this exists to prevent.
3242
+ *
3243
+ * Except on a `map` (pass `propertyType`): a map's value is a JSON document,
3244
+ * and one may legitimately carry `$` keys — a JSON Schema's `$schema`, a
3245
+ * MongoDB-style export. There, only a single-key object whose key is one of
3246
+ * {@link FIELD_OPERATORS} is an operation, and anything else is data. A caller
3247
+ * that does not know the property type gets the strict reading.
2377
3248
  *
2378
3249
  * The one exception is `{ $ref: … }`, the batch's backward reference. It stands
2379
3250
  * where a *value* goes and is resolved to one before the row is written, so it
@@ -2382,15 +3253,16 @@ var BATCH_REF_KEY = "$ref";
2382
3253
  *
2383
3254
  * @group Data
2384
3255
  */
2385
- function isFieldOperation(value) {
3256
+ function isFieldOperation(value, propertyType) {
2386
3257
  if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) return false;
2387
3258
  const keys = Object.keys(value);
2388
3259
  if (keys.length === 1 && keys[0] === "$ref") return false;
3260
+ if (propertyType === "map") return keys.length === 1 && FIELD_OPERATORS.includes(keys[0]);
2389
3261
  return keys.some((key) => key.startsWith("$"));
2390
3262
  }
2391
3263
  /** True when any value in a write payload is (or is attempting to be) one. @group Data */
2392
3264
  function hasFieldOperation(values) {
2393
- return !!values && Object.values(values).some(isFieldOperation);
3265
+ return !!values && Object.values(values).some((value) => isFieldOperation(value));
2394
3266
  }
2395
3267
  //#endregion
2396
3268
  //#region src/controllers/data_driver.ts
@@ -2458,9 +3330,33 @@ function resolveClientListLimit(rawLimit, opts = {}) {
2458
3330
  */
2459
3331
  var PUBLIC_STORAGE_PREFIX = "public/";
2460
3332
  /**
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.
3333
+ * True when a storage **key** is public: it lies under
3334
+ * {@link PUBLIC_STORAGE_PREFIX}.
3335
+ *
3336
+ * The key is read exactly as it is — the canonical key within its bucket, the
3337
+ * one the server stores, serves, and hands a `storageAuthorize` hook as
3338
+ * `key` — and nothing is parsed out of it. That is the difference from
3339
+ * {@link isPublicStoragePath}, which reads a client-side *path* and so strips
3340
+ * a `scheme://` and a leading `default/` bucket segment first. On a key both are
3341
+ * wrong: `default/public/x` is a key in a folder called `default`, and a
3342
+ * request path `notes://public/x` names the key `notes:/public/x`. Neither is
3343
+ * public, and the server decides on this function so that it never says they
3344
+ * are.
3345
+ *
3346
+ * @group Models
3347
+ */
3348
+ function isPublicStorageKey(key) {
3349
+ if (!key || !key.startsWith("public/")) return false;
3350
+ return !key.split(/[\\/]/).includes("..");
3351
+ }
3352
+ /**
3353
+ * True when a storage key or client-side path points at a public object
3354
+ * (lives under {@link PUBLIC_STORAGE_PREFIX}).
3355
+ *
3356
+ * For a path as a client holds it: a `scheme://` prefix and a single leading
3357
+ * `default/` bucket segment are stripped first. The server and a
3358
+ * `storageAuthorize` hook, which hold the canonical key, use
3359
+ * {@link isPublicStorageKey}.
2464
3360
  *
2465
3361
  * @group Models
2466
3362
  */
@@ -2473,7 +3369,295 @@ function isPublicStoragePath(path) {
2473
3369
  if (p.split("/").some((seg) => seg === "..")) return false;
2474
3370
  return p.startsWith("public/") || p.startsWith(`default/public/`);
2475
3371
  }
3372
+ /**
3373
+ * The path the storage routes address an object by, from a key or from the
3374
+ * `storageUrl` an upload returned (`<scheme>://<bucket>/<key>`, which is what a
3375
+ * property with `includeBucketUrl: true` keeps in its column).
3376
+ *
3377
+ * The routes address a source's objects by key. On S3 and GCS the bucket in a
3378
+ * `storageUrl` is always the source's own — the server refuses any other — so
3379
+ * it is dropped: sent along, `s3://acme-media/products/a.png` reached the
3380
+ * server as the key `acme-media/products/a.png`, the preview found nothing and
3381
+ * a delete reported success while the object stayed. It worked on local disk
3382
+ * only, because `local://default/<key>` happens to carry the one bucket
3383
+ * segment the routes recognise — which is kept, as is any other local bucket.
3384
+ *
3385
+ * A bare key whose first folder is `default` is sent behind that bucket
3386
+ * segment. The routes read one leading `default/` (in any case) as the bucket,
3387
+ * so the key `default/photo.png` sent as it is names the root `photo.png`:
3388
+ * {@link defaultBucketPath} is the spelling that names the key itself.
3389
+ *
3390
+ * Shared by every storage source that talks to those routes, so they address
3391
+ * an object alike.
3392
+ *
3393
+ * @group Models
3394
+ */
3395
+ function storageObjectPath(keyOrUrl, bucket) {
3396
+ let filePath = keyOrUrl;
3397
+ let bucketQualified = false;
3398
+ const scheme = /^(local|s3|gs):\/\//.exec(filePath);
3399
+ if (scheme) {
3400
+ filePath = filePath.substring(scheme[0].length);
3401
+ if (scheme[1] !== "local") {
3402
+ const slash = filePath.indexOf("/");
3403
+ if (slash > 0) filePath = filePath.substring(slash + 1);
3404
+ } else bucketQualified = true;
3405
+ }
3406
+ if (bucket && filePath) {
3407
+ if (!filePath.startsWith(`${bucket}/`)) filePath = `${bucket}/${filePath}`;
3408
+ bucketQualified = true;
3409
+ }
3410
+ return bucketQualified ? filePath : defaultBucketPath(filePath);
3411
+ }
3412
+ /**
3413
+ * The request path of a key in the default bucket: the key itself, unless the
3414
+ * routes would read its first folder as the bucket — a first folder named
3415
+ * `default`, matched as the routes match it (case-insensitively, and only when
3416
+ * something follows it). Then the bucket segment goes in front, so exactly the
3417
+ * one segment the routes strip is the one added.
3418
+ */
3419
+ function defaultBucketPath(key) {
3420
+ const segments = key.split("/");
3421
+ return segments.length > 1 && segments[0].toLowerCase() === "default" ? `default/${key}` : key;
3422
+ }
3423
+ //#endregion
3424
+ //#region src/controllers/storage-references.ts
3425
+ /**
3426
+ * The scheme of a **storage reference**: a stored object named inside text.
3427
+ *
3428
+ * Text that embeds a file — a markdown body above all — cannot hold a private
3429
+ * object's URL: that URL carries a download token which expires in minutes,
3430
+ * after which every reader of the text gets a 401 for an image that is still in
3431
+ * the bucket. It holds a reference instead,
3432
+ * `rebase-storage:posts/cover.png` (`?storageId=media` for a named source),
3433
+ * and whoever renders the text exchanges each reference for a fresh URL with
3434
+ * {@link resolveStorageReferences} — `resolveStorageReferences(text, client)`
3435
+ * from `@rebasepro/client` on a site.
3436
+ *
3437
+ * @group Models
3438
+ */
3439
+ var STORAGE_REFERENCE_SCHEME = "rebase-storage:";
3440
+ /**
3441
+ * `encodeURIComponent`, plus the five characters it leaves alone that end or
3442
+ * break a markdown link destination: `(`, `)`, `'`, `!` and `*`.
3443
+ */
3444
+ var encodeReferencePart = (part) => encodeURIComponent(part).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
3445
+ /**
3446
+ * A reference as it appears in text: the scheme, then the characters
3447
+ * {@link storageReference} writes. Kept beside {@link STORAGE_REFERENCE_SCHEME};
3448
+ * the test round-trips names chosen to break it.
3449
+ */
3450
+ var STORAGE_REFERENCE_PATTERN = /rebase-storage:[A-Za-z0-9\-._~%/?=&]+/g;
3451
+ /**
3452
+ * The reference for an object — what text stores in place of its URL.
3453
+ *
3454
+ * Every part is percent-encoded, so the reference contains no space,
3455
+ * parenthesis or quote and survives as one markdown link destination whatever
3456
+ * the file was called.
3457
+ *
3458
+ * @group Models
3459
+ */
3460
+ function storageReference(key, storageId) {
3461
+ return `${STORAGE_REFERENCE_SCHEME}${key.replace(/^\/+/, "").split("/").map(encodeReferencePart).join("/")}${storageId && storageId !== "(default)" ? `?storageId=${encodeReferencePart(storageId)}` : ""}`;
3462
+ }
3463
+ /**
3464
+ * Whether a string — an image's `src`, a link's `href` — is a storage reference.
3465
+ *
3466
+ * @group Models
3467
+ */
3468
+ function isStorageReference(value) {
3469
+ return typeof value === "string" && value.startsWith("rebase-storage:");
3470
+ }
3471
+ /**
3472
+ * The object a storage reference names, or `null` for anything else — a URL, a
3473
+ * bare key, or a reference whose key would climb out of its prefix.
3474
+ *
3475
+ * @group Models
3476
+ */
3477
+ function parseStorageReference(value) {
3478
+ if (!isStorageReference(value)) return null;
3479
+ const rest = value.slice(15);
3480
+ const queryAt = rest.indexOf("?");
3481
+ const rawPath = queryAt === -1 ? rest : rest.slice(0, queryAt);
3482
+ const query = queryAt === -1 ? "" : rest.slice(queryAt + 1);
3483
+ let key;
3484
+ let storageId;
3485
+ try {
3486
+ key = rawPath.split("/").map(decodeURIComponent).join("/");
3487
+ for (const pair of query.split("&")) {
3488
+ const [name, raw] = pair.split("=");
3489
+ if (name === "storageId" && raw) storageId = decodeURIComponent(raw);
3490
+ }
3491
+ } catch {
3492
+ return null;
3493
+ }
3494
+ if (!key || key.split("/").some((segment) => segment === "..")) return null;
3495
+ return storageId && storageId !== "(default)" ? {
3496
+ key,
3497
+ storageId
3498
+ } : { key };
3499
+ }
3500
+ /** The JSON in a JWT's payload segment, or `undefined` for anything else. */
3501
+ function jwtPayload(token) {
3502
+ const segments = token.split(".");
3503
+ if (segments.length !== 3) return void 0;
3504
+ try {
3505
+ const base64 = segments[1].replace(/-/g, "+").replace(/_/g, "/");
3506
+ const binary = atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
3507
+ const json = new TextDecoder().decode(Uint8Array.from(binary, (c) => c.charCodeAt(0)));
3508
+ const payload = JSON.parse(json);
3509
+ return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : void 0;
3510
+ } catch {
3511
+ return;
3512
+ }
3513
+ }
3514
+ /**
3515
+ * The object a private file's download URL was minted for, or `null` for any
3516
+ * other URL.
3517
+ *
3518
+ * Text written before storage references stored such URLs —
3519
+ * `…/storage/file/posts/cover.png?token=…` — and their tokens expired minutes
3520
+ * later. The token still names the object it was minted for, so the URL can be
3521
+ * traded for a fresh one, or for a reference. Read without verifying the
3522
+ * token: it grants nothing here, it only says which object is meant, and the
3523
+ * key in the URL must agree with it. Host-agnostic, since a URL written
3524
+ * through one origin may be read through another.
3525
+ *
3526
+ * @group Models
3527
+ */
3528
+ function storageTargetOfDownloadUrl(url) {
3529
+ let parsed;
3530
+ try {
3531
+ parsed = new URL(url, "http://relative.invalid");
3532
+ } catch {
3533
+ return null;
3534
+ }
3535
+ const at = parsed.pathname.indexOf("/storage/file/");
3536
+ const token = parsed.searchParams.get("token");
3537
+ if (at === -1 || !token) return null;
3538
+ let key;
3539
+ try {
3540
+ key = parsed.pathname.slice(at + 14).split("/").map(decodeURIComponent).join("/");
3541
+ } catch {
3542
+ return null;
3543
+ }
3544
+ if (!key || key.split("/").some((segment) => segment === "..")) return null;
3545
+ const payload = jwtPayload(token);
3546
+ if (payload?.purpose !== "file-read" || typeof payload.path !== "string") return null;
3547
+ const slash = payload.path.indexOf("/");
3548
+ if (payload.path !== key && payload.path.slice(slash + 1) !== key) return null;
3549
+ const named = parsed.searchParams.get("storageId") ?? (typeof payload.storageId === "string" ? payload.storageId : void 0);
3550
+ return named && named !== "(default)" ? {
3551
+ key,
3552
+ storageId: named
3553
+ } : { key };
3554
+ }
3555
+ /**
3556
+ * A private file's download URL as it may appear in text. Parentheses are
3557
+ * allowed in the path, where an unencoded file name puts them; the query, a
3558
+ * token, has none, so the match ends where a markdown destination does.
3559
+ */
3560
+ var DOWNLOAD_URL_PATTERN = /(?:https?:\/\/[^\s()<>"'\]]+)?\/storage\/file\/[^\s<>"'?#\]]+\?[^\s()<>"'#\]]*/g;
3561
+ /** Each download URL in `text` that names an object, with its reference. */
3562
+ function downloadUrlsIn(text) {
3563
+ const found = /* @__PURE__ */ new Map();
3564
+ if (!text.includes("/storage/file/")) return found;
3565
+ for (const url of text.match(DOWNLOAD_URL_PATTERN) ?? []) {
3566
+ const target = storageTargetOfDownloadUrl(url);
3567
+ if (target) found.set(url, storageReference(target.key, target.storageId));
3568
+ }
3569
+ return found;
3570
+ }
3571
+ /**
3572
+ * Every object `text` names: its storage references, and the private files'
3573
+ * download URLs saved in it before references existed. An object named more
3574
+ * than once may be listed more than once.
3575
+ *
3576
+ * @group Models
3577
+ */
3578
+ function storageTargetsInText(text) {
3579
+ return [...text.includes("rebase-storage:") ? text.match(STORAGE_REFERENCE_PATTERN) ?? [] : [], ...downloadUrlsIn(text).values()].map((reference) => parseStorageReference(reference)).filter((target) => target !== null);
3580
+ }
3581
+ /**
3582
+ * `text` with every private file's download URL replaced by the storage
3583
+ * reference for its object — for rewriting text saved before references, so
3584
+ * it stores what new text stores. Any other URL is left as it is.
3585
+ *
3586
+ * @group Models
3587
+ */
3588
+ function storageReferencesForDownloadUrls(text) {
3589
+ const urls = downloadUrlsIn(text);
3590
+ if (urls.size === 0) return text;
3591
+ return text.replace(DOWNLOAD_URL_PATTERN, (url) => urls.get(url) ?? url);
3592
+ }
3593
+ /**
3594
+ * Exchange every storage reference in `text` for a URL, at the moment the text
3595
+ * is rendered.
3596
+ *
3597
+ * ```ts
3598
+ * const html = markdown.render(await resolveStorageReferences(post.body, client));
3599
+ * ```
3600
+ *
3601
+ * Each distinct object is asked for once — `getSignedUrl(key)` on its source —
3602
+ * so a private object gets a freshly minted download URL every time the text is
3603
+ * rendered. A reference that cannot be resolved (no such object, an unknown
3604
+ * source) is left as it was, so a missing object reads as a missing image
3605
+ * rather than as a failed render.
3606
+ *
3607
+ * A private file's download URL saved in the text before references existed
3608
+ * is renewed the same way: its expired token still names the object (see
3609
+ * {@link storageTargetOfDownloadUrl}). One that cannot be resolved is left as
3610
+ * it was.
3611
+ *
3612
+ * @group Models
3613
+ */
3614
+ async function resolveStorageReferences(text, from) {
3615
+ if (!text) return text;
3616
+ const downloadUrls = downloadUrlsIn(text);
3617
+ if (!text.includes("rebase-storage:") && downloadUrls.size === 0) return text;
3618
+ const resolve = typeof from === "function" ? from : resolverFromSources(from);
3619
+ const references = /* @__PURE__ */ new Set([...text.match(STORAGE_REFERENCE_PATTERN) ?? [], ...downloadUrls.values()]);
3620
+ const urls = /* @__PURE__ */ new Map();
3621
+ await Promise.all([...references].map(async (reference) => {
3622
+ const target = parseStorageReference(reference);
3623
+ if (!target) return;
3624
+ try {
3625
+ const url = await resolve(target);
3626
+ if (url) urls.set(reference, url);
3627
+ } catch {}
3628
+ }));
3629
+ if (urls.size === 0) return text;
3630
+ return (downloadUrls.size === 0 ? text : text.replace(DOWNLOAD_URL_PATTERN, (saved) => {
3631
+ const reference = downloadUrls.get(saved);
3632
+ const url = reference === void 0 ? void 0 : urls.get(reference);
3633
+ return url === void 0 ? saved : asLinkDestination(url);
3634
+ })).replace(STORAGE_REFERENCE_PATTERN, (reference) => {
3635
+ const url = urls.get(reference);
3636
+ return url === void 0 ? reference : asLinkDestination(url);
3637
+ });
3638
+ }
3639
+ /**
3640
+ * A URL as one markdown link destination: whitespace, parentheses, angle
3641
+ * brackets and quotes percent-encoded, since any of them ends the destination
3642
+ * and turns the image into literal text. The URL means the same thing either
3643
+ * way.
3644
+ */
3645
+ var asLinkDestination = (url) => url.replace(/[\s()<>"']/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
3646
+ /** A resolver over a client's storage sources, one source per named key. */
3647
+ function resolverFromSources(client) {
3648
+ const named = /* @__PURE__ */ new Map();
3649
+ return async ({ key, storageId }) => {
3650
+ let source = client.storage;
3651
+ if (storageId) {
3652
+ if (!named.has(storageId)) named.set(storageId, client.createStorageSource?.(storageId));
3653
+ source = named.get(storageId);
3654
+ }
3655
+ if (!source) return null;
3656
+ const config = await source.getSignedUrl(key);
3657
+ return config.fileNotFound ? null : config.url;
3658
+ };
3659
+ }
2476
3660
  //#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 };
3661
+ 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
3662
 
2479
3663
  //# sourceMappingURL=index.es.js.map