@rebasepro/server-postgres 0.13.1-canary.gf57a27e → 0.14.0

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 (103) hide show
  1. package/dist/PostgresBootstrapper.d.ts +26 -0
  2. package/dist/auth/services.d.ts +21 -0
  3. package/dist/{auth-users-columns-Dt9g712t.js → auth-users-columns-BfQHf9JE.js} +525 -63
  4. package/dist/auth-users-columns-BfQHf9JE.js.map +1 -0
  5. package/dist/{backup-service-Bww-Lg0s.js → backup-service-BH0Dzo_h.js} +2 -3
  6. package/dist/{backup-service-Bww-Lg0s.js.map → backup-service-BH0Dzo_h.js.map} +1 -1
  7. package/dist/cli-output.d.ts +34 -0
  8. package/dist/data-transformer.d.ts +7 -2
  9. package/dist/data_driver-ULAyJEi9.js +193 -0
  10. package/dist/data_driver-ULAyJEi9.js.map +1 -0
  11. package/dist/ensure-collection-policies-8vuu-n4r.js +124 -0
  12. package/dist/ensure-collection-policies-8vuu-n4r.js.map +1 -0
  13. package/dist/{ensure-collection-tables-DRxaUG96.js → ensure-collection-tables-CbvaGuVn.js} +89 -10
  14. package/dist/ensure-collection-tables-CbvaGuVn.js.map +1 -0
  15. package/dist/index.es.js +1310 -1060
  16. package/dist/index.es.js.map +1 -1
  17. package/dist/{rls-bootstrap-sql-Bpv3nUZo.js → rls-bootstrap-sql-69hYT8nr.js} +2 -2
  18. package/dist/{rls-bootstrap-sql-Bpv3nUZo.js.map → rls-bootstrap-sql-69hYT8nr.js.map} +1 -1
  19. package/dist/rls-enforcement-BJ_3wxwg.js +425 -0
  20. package/dist/rls-enforcement-BJ_3wxwg.js.map +1 -0
  21. package/dist/schema/auth-schema.d.ts +102 -0
  22. package/dist/schema/doctor-policy-checks.d.ts +28 -0
  23. package/dist/schema/doctor.d.ts +41 -25
  24. package/dist/schema/ensure-collection-policies.d.ts +33 -9
  25. package/dist/schema/ensure-collection-tables.d.ts +60 -6
  26. package/dist/schema/generate-drizzle-schema-logic.d.ts +9 -1
  27. package/dist/schema/introspect-db-inference.d.ts +8 -1
  28. package/dist/schema/introspect-db-logic.d.ts +49 -0
  29. package/dist/schema/introspect-db-project.d.ts +21 -0
  30. package/dist/schema/search-column.d.ts +49 -0
  31. package/dist/security/policy-drift.d.ts +34 -0
  32. package/dist/security/rls-enforcement.d.ts +8 -3
  33. package/dist/services/FetchService.d.ts +9 -0
  34. package/dist/services/PersistService.d.ts +21 -17
  35. package/dist/services/RelationService.d.ts +9 -57
  36. package/dist/services/RelationWriteService.d.ts +82 -0
  37. package/dist/services/collection-helpers.d.ts +42 -0
  38. package/dist/services/dataService.d.ts +2 -0
  39. package/dist/services/junction-writes.d.ts +82 -0
  40. package/dist/services/realtimeService.d.ts +137 -2
  41. package/dist/services/write-denial.d.ts +36 -0
  42. package/dist/{src-C_wvdMnl.js → src-DCdn3Val.js} +35 -3
  43. package/dist/src-DCdn3Val.js.map +1 -0
  44. package/dist/utils/drizzle-conditions.d.ts +54 -1
  45. package/dist/{websocket-D0TBU3ia.js → websocket-C8ZqVBiV.js} +75 -18
  46. package/dist/websocket-C8ZqVBiV.js.map +1 -0
  47. package/package.json +6 -6
  48. package/src/PostgresBackendDriver.ts +7 -3
  49. package/src/PostgresBootstrapper.ts +95 -9
  50. package/src/auth/ensure-tables.ts +27 -5
  51. package/src/auth/services.ts +82 -5
  52. package/src/backup/backup-cli.ts +59 -57
  53. package/src/cli-errors.ts +6 -6
  54. package/src/cli-helpers.ts +4 -4
  55. package/src/cli-output.ts +43 -0
  56. package/src/cli.ts +155 -147
  57. package/src/collections/buildRegistry.ts +3 -1
  58. package/src/data-transformer.ts +111 -25
  59. package/src/history/ensure-history-table.ts +2 -2
  60. package/src/schema/auth-schema.ts +17 -1
  61. package/src/schema/doctor-cli.ts +14 -65
  62. package/src/schema/doctor-policy-checks.ts +105 -0
  63. package/src/schema/doctor.ts +149 -72
  64. package/src/schema/ensure-collection-policies.ts +99 -6
  65. package/src/schema/ensure-collection-tables.ts +214 -17
  66. package/src/schema/generate-drizzle-schema-logic.ts +121 -65
  67. package/src/schema/generate-drizzle-schema.ts +11 -10
  68. package/src/schema/generate-postgres-ddl-logic.ts +28 -1
  69. package/src/schema/generate-postgres-ddl.ts +14 -13
  70. package/src/schema/generated-schema-staleness.ts +7 -5
  71. package/src/schema/introspect-db-inference.ts +9 -2
  72. package/src/schema/introspect-db-logic.ts +251 -75
  73. package/src/schema/introspect-db-project.ts +78 -0
  74. package/src/schema/introspect-db.ts +42 -25
  75. package/src/schema/introspect-runtime.ts +14 -2
  76. package/src/schema/search-column.ts +85 -0
  77. package/src/security/policy-drift.test.ts +104 -3
  78. package/src/security/policy-drift.ts +129 -7
  79. package/src/security/rls-enforcement.ts +9 -4
  80. package/src/services/FetchService.ts +105 -7
  81. package/src/services/PersistService.ts +68 -42
  82. package/src/services/RelationService.ts +35 -695
  83. package/src/services/RelationWriteService.ts +653 -0
  84. package/src/services/cdc/trigger-cdc.ts +5 -1
  85. package/src/services/channel-history.ts +9 -3
  86. package/src/services/channel-presence.ts +10 -3
  87. package/src/services/collection-helpers.ts +89 -4
  88. package/src/services/dataService.ts +2 -0
  89. package/src/services/junction-writes.ts +295 -0
  90. package/src/services/pg-notify-listener.ts +1 -1
  91. package/src/services/realtimeService.ts +337 -82
  92. package/src/services/write-denial.ts +55 -0
  93. package/src/utils/drizzle-conditions.ts +211 -34
  94. package/src/utils/pg-error-utils.ts +8 -3
  95. package/src/websocket.ts +113 -16
  96. package/dist/auth-users-columns-Dt9g712t.js.map +0 -1
  97. package/dist/ensure-collection-policies-CwYUliAa.js +0 -57
  98. package/dist/ensure-collection-policies-CwYUliAa.js.map +0 -1
  99. package/dist/ensure-collection-tables-DRxaUG96.js.map +0 -1
  100. package/dist/policy-CPkCqVTz.js +0 -105
  101. package/dist/policy-CPkCqVTz.js.map +0 -1
  102. package/dist/src-C_wvdMnl.js.map +0 -1
  103. package/dist/websocket-D0TBU3ia.js.map +0 -1
@@ -2,8 +2,9 @@ import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
4
  import { l as __require, s as __commonJSMin } from "./connection-BuZ97wsr.js";
5
- import { a as rewriteLegacyRlsFunctions, c as isPostgresCollectionConfig, d as getDataSourceCapabilities, f as NULL_OPS, i as RLS_UID_SQL, l as isRelationalCollectionConfig, m as toCanonicalOp, p as REST_TO_CANONICAL, r as RLS_ROLES_SQL, s as getDeclaredSubcollections } from "./src-C_wvdMnl.js";
6
- import { n as ANONYMOUS_USER_IDS, r as policy, t as ANONYMOUS_USER_ID } from "./policy-CPkCqVTz.js";
5
+ import { a as policy, i as ANONYMOUS_USER_IDS, r as ANONYMOUS_USER_ID } from "./data_driver-ULAyJEi9.js";
6
+ import { a as rewriteLegacyRlsFunctions, c as isPostgresCollectionConfig, d as getDataSourceCapabilities, f as ALL_WHERE_FILTER_OPS, g as toCanonicalOp, h as REST_TO_CANONICAL, i as RLS_UID_SQL, l as isRelationalCollectionConfig, m as NULL_OPS, p as CANONICAL_TO_REST, r as RLS_ROLES_SQL, s as getDeclaredSubcollections } from "./src-DCdn3Val.js";
7
+ import { createHash } from "node:crypto";
7
8
  //#region ../types/src/types/entities.ts
8
9
  /**
9
10
  * Class used to create a reference to a entity in a different path
@@ -55,25 +56,6 @@ function hasForeignKeyOnTarget(relation) {
55
56
  function isManyToMany(relation) {
56
57
  return relation.kind === "manyToMany";
57
58
  }
58
- /**
59
- * Resolve a client-supplied list `limit` into a safe, always-defined value.
60
- *
61
- * - A provided limit is coerced to an integer and clamped to `[1, maxLimit]`,
62
- * so `0`, negatives, and absurd values can never bypass the cap.
63
- * - An absent / blank / non-numeric limit falls back to the mode default:
64
- * `vectorDefaultLimit` for a vector search, otherwise `defaultLimit`.
65
- *
66
- * The return is never `undefined` — no ingress that routes its client limit
67
- * through this can produce an unbounded read.
68
- */
69
- function resolveClientListLimit(rawLimit, opts = {}) {
70
- const maxLimit = opts.maxLimit ?? 1e3;
71
- if (rawLimit != null && String(rawLimit).trim() !== "") {
72
- const parsed = typeof rawLimit === "number" ? rawLimit : parseInt(String(rawLimit), 10);
73
- if (Number.isFinite(parsed)) return Math.min(Math.max(1, Math.floor(parsed)), maxLimit);
74
- }
75
- return opts.vectorSearch ? opts.vectorDefaultLimit ?? 10 : opts.defaultLimit ?? 50;
76
- }
77
59
  //#endregion
78
60
  //#region ../common/src/util/common.ts
79
61
  var DEFAULT_ONE_OF_TYPE = "type";
@@ -1430,6 +1412,73 @@ function toPostgresIdentifier(name) {
1430
1412
  if (bytes.byteLength <= 63) return name;
1431
1413
  return new TextDecoder("utf-8").decode(bytes.subarray(0, 63)).replace(/�+$/, "");
1432
1414
  }
1415
+ /**
1416
+ * The API name a database column is served under.
1417
+ *
1418
+ * The wire name of a field is its property key, and Rebase's property keys are
1419
+ * camelCase — `displayName`, `createdAt`, `photoURL`. Columns are snake_case,
1420
+ * because an unquoted Postgres identifier folds to lower case and a camelCase
1421
+ * column is therefore reachable only as `"authorId"` forever: in hand-written
1422
+ * SQL, in psql, in an RLS policy body, in a dump, and in every third-party tool
1423
+ * that ever touches the database. So the two conventions are both right, and
1424
+ * this is the function that crosses between them.
1425
+ *
1426
+ * It exists because two sources of field names never crossed: a foreign key
1427
+ * derived from a relation (`author_id`) and a column read back by introspection
1428
+ * (`user_id`) both landed on the wire under their column name, while every
1429
+ * hand-authored collection next to them used camelCase. One API, two
1430
+ * conventions, and no rule a caller could infer from outside — those names are
1431
+ * also the `where` and `orderBy` keys, so it was not a matter of taste.
1432
+ *
1433
+ * Rules, in the order they matter:
1434
+ *
1435
+ * - **A name with no separator is returned unchanged.** `photoURL` stays
1436
+ * `photoURL` and `id` stays `id`. Lower-casing a single token is what makes
1437
+ * a "camelCase" helper destructive — `camelCase("photoURL")` is `photourl` —
1438
+ * and this function is applied to names that are *already* keys.
1439
+ * - **Each following segment keeps its own casing** apart from an upper-cased
1440
+ * first letter, so `photo_URL` → `photoURL` rather than `photoUrl`.
1441
+ * - **The result may still not be a JavaScript identifier.** `2fa_enabled`
1442
+ * becomes `2faEnabled`, which is a perfectly good object key and still needs
1443
+ * quoting where one is written into generated source.
1444
+ *
1445
+ * Not the inverse of {@link toSnakeCase}: `toSnakeCase` tokenises on case
1446
+ * boundaries and would turn `photoURL` into `photo_url`. Round-tripping is not
1447
+ * a property either function promises, which is why a column name that a
1448
+ * property maps explicitly is always read off `columnName` rather than derived.
1449
+ */
1450
+ function toWireKey(columnName) {
1451
+ if (!columnName) return columnName;
1452
+ const segments = columnName.split(/[-_ ]+/).filter(Boolean);
1453
+ if (segments.length <= 1) return columnName;
1454
+ return segments.map((segment, index) => index === 0 ? segment.charAt(0).toLowerCase() + segment.slice(1) : segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
1455
+ }
1456
+ /**
1457
+ * The first candidate key not already used, or a numbered fallback.
1458
+ *
1459
+ * Introspection turns a set of column names into a set of object keys, and the
1460
+ * mapping is not injective: `user_id` and `userId` are two columns and one
1461
+ * {@link toWireKey}, and two foreign keys can strip to the same relation name.
1462
+ * A duplicate key in a generated object literal is a TypeScript error, so the
1463
+ * whole collection stops compiling — and a duplicate key in a `Record` built at
1464
+ * runtime is worse, because it silently drops a column instead.
1465
+ *
1466
+ * The numbered tail is what makes this total: a function that returns a key it
1467
+ * cannot guarantee is free has only moved the duplicate one line down.
1468
+ *
1469
+ * Structurally typed on `has` so a `Map` of emitted blocks and a `Set` of taken
1470
+ * names both satisfy it. Lives here, in the package both introspection
1471
+ * producers and the admin's table import can reach, because they must resolve a
1472
+ * collision the same way or one database describes itself three ways.
1473
+ */
1474
+ function firstFreeKey(candidates, taken) {
1475
+ for (const candidate of candidates) if (!taken.has(candidate)) return candidate;
1476
+ const base = candidates[candidates.length - 1];
1477
+ for (let suffix = 2;; suffix++) {
1478
+ const candidate = `${base}_${suffix}`;
1479
+ if (!taken.has(candidate)) return candidate;
1480
+ }
1481
+ }
1433
1482
  //#endregion
1434
1483
  //#region ../common/src/util/entities.ts
1435
1484
  /**
@@ -1539,6 +1588,25 @@ function createRelationRefWithData(id, path, data) {
1539
1588
  data
1540
1589
  };
1541
1590
  }
1591
+ //#endregion
1592
+ //#region ../common/src/util/collections.ts
1593
+ /**
1594
+ * A copy of `collections` ordered by slug.
1595
+ *
1596
+ * Every generator that turns collections into a file is order-dependent, and
1597
+ * every one of them is compared against its own output — `rebase doctor`
1598
+ * regenerates in memory and diffs, `generate-sdk && git diff --exit-code` gates
1599
+ * CI. While only the *writers* sorted, a project whose `readdirSync` order
1600
+ * differed from its slug order was reported permanently out of date, and the
1601
+ * fix the message printed rewrote the file in the order it was already in. The
1602
+ * generators sort themselves now, so no caller can get this wrong.
1603
+ *
1604
+ * A slug-less collection is left to the generator's own validation, which names
1605
+ * the offending collection; sorting must not throw first.
1606
+ */
1607
+ function sortCollectionsBySlug(collections) {
1608
+ return [...collections].sort((a, b) => (a.slug ?? "").localeCompare(b.slug ?? ""));
1609
+ }
1542
1610
  /** The eight-four-four-four-twelve shape of a UUID, any version. */
1543
1611
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1544
1612
  /** Whether one address part can be a value of the column it addresses. */
@@ -1875,6 +1943,48 @@ function getColumnName(fullColumn) {
1875
1943
  return fullColumn.includes(".") ? fullColumn.split(".").pop() : fullColumn;
1876
1944
  }
1877
1945
  /**
1946
+ * The field key a database column is served and addressed under.
1947
+ *
1948
+ * A column has two names and they are not the same name. `author_id` is what
1949
+ * Postgres stores; `authorId` is the key on the JSON row, the key in the
1950
+ * generated Drizzle table, and the key a caller writes in `where` and
1951
+ * `orderBy`. Every place that starts from a column and has to reach a row, a
1952
+ * Drizzle table or a payload goes through here, so there is one answer rather
1953
+ * than one per call site — the two that disagreed put `displayName` and
1954
+ * `author_id` on the same API.
1955
+ *
1956
+ * A declared property is the authority when there is one, because its key *is*
1957
+ * the wire name and `columnName` is the only thing that ever renamed the
1958
+ * column:
1959
+ *
1960
+ * 1. an explicit `columnName` equal to this column;
1961
+ * 2. a property whose key is literally the column (an author who wrote
1962
+ * `author_id:` meant `author_id` on the wire, and gets it);
1963
+ * 3. a property whose key snake-cases to the column, which is the default
1964
+ * mapping — `authorId` → `author_id`.
1965
+ *
1966
+ * With no property in the way — a foreign key derived from a relation, which
1967
+ * usually has none — the name is derived: {@link toWireKey}.
1968
+ *
1969
+ * Note the fallback is *not* the column verbatim. That was the old behaviour
1970
+ * and it is precisely the defect: a derived foreign key reached the wire under
1971
+ * its column name while every hand-authored field beside it was camelCase.
1972
+ */
1973
+ function fieldKeyForColumn(collection, column) {
1974
+ const properties = collection?.properties;
1975
+ if (properties) {
1976
+ for (const [key, prop] of Object.entries(properties)) {
1977
+ const columnName = prop?.columnName;
1978
+ if (typeof columnName === "string" && columnName === column) return key;
1979
+ }
1980
+ for (const key of Object.keys(properties)) {
1981
+ if (key === column) return key;
1982
+ if (toSnakeCase(key) === column) return key;
1983
+ }
1984
+ }
1985
+ return toWireKey(column);
1986
+ }
1987
+ /**
1878
1988
  * Look up a relation by key with forgiving normalization.
1879
1989
  *
1880
1990
  * `resolveCollectionRelations` stores each relation under a single canonical
@@ -2114,11 +2224,32 @@ function sqlToPolicy(sql) {
2114
2224
  * is the more dangerous spelling, because it inverts a rule instead of
2115
2225
  * emptying a table.
2116
2226
  */
2117
- var FOREIGN_CONVENTION_UIDS = {
2118
- anon: "Supabase",
2119
- authenticated: "Supabase",
2120
- service_role: "Supabase"
2121
- };
2227
+ /**
2228
+ * A `Map`, not an object literal.
2229
+ *
2230
+ * As `Record<string, string>` this was indexed with a literal taken straight
2231
+ * out of a policy, so every key on `Object.prototype` answered: a rule
2232
+ * comparing `rebase.uid()` to `"valueOf"`, `"toString"`, `"constructor"` or
2233
+ * `"hasOwnProperty"` found a truthy "platform" and reported an anonymous-grant
2234
+ * risk that does not exist — with the matched function interpolated into the
2235
+ * explanation as the platform's name. A security warning that fires on
2236
+ * innocent input is worse than none: it is what teaches people to skip the
2237
+ * warnings that are real.
2238
+ *
2239
+ * Same shape as the prototype-pollution class swept out of `setIn`, `getIn`,
2240
+ * `mergeDeep` and `unflattenObject` — a data-derived key reaching a plain
2241
+ * object. Found by a property test, on the input `"valueOf"`.
2242
+ */
2243
+ var FOREIGN_CONVENTION_UIDS = /* @__PURE__ */ new Map([
2244
+ ["anon", "Supabase"],
2245
+ ["authenticated", "Supabase"],
2246
+ ["service_role", "Supabase"]
2247
+ ]);
2248
+ /**
2249
+ * The same foreign literals, as a pattern for SQL that could not be parsed
2250
+ * back into structure.
2251
+ */
2252
+ var FOREIGN_UID_LITERAL_SQL = new RegExp(String.raw`rebase\.uid\(\)\s*=\s*'(${[...FOREIGN_CONVENTION_UIDS.keys()].join("|")})'`, "i");
2122
2253
  /**
2123
2254
  * `rebase.uid() IS NOT NULL` in raw SQL, the clause that is always true.
2124
2255
  *
@@ -2165,17 +2296,27 @@ function findAnonymousGrants(expr) {
2165
2296
  case "existsIn":
2166
2297
  visit(e.where);
2167
2298
  return;
2168
- case "raw":
2299
+ case "raw": {
2169
2300
  if (UID_NOT_NULL.test(e.sql)) found.push({
2170
2301
  pattern: "uid-not-null",
2171
2302
  detail: e.sql,
2172
2303
  explanation: `\`rebase.uid() IS NOT NULL\` is true for every request that came from a client, including anonymous ones — they carry '${ANONYMOUS_USER_ID}', not NULL. Use \`condition: policy.authenticated()\` to mean "signed in".`
2173
2304
  });
2305
+ const foreign = FOREIGN_UID_LITERAL_SQL.exec(e.sql);
2306
+ if (foreign) {
2307
+ const literal = foreign[1];
2308
+ found.push({
2309
+ pattern: "foreign-uid-literal",
2310
+ detail: literal,
2311
+ explanation: `'${literal}' is a ${FOREIGN_CONVENTION_UIDS.get(literal)} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in".`
2312
+ });
2313
+ }
2174
2314
  return;
2315
+ }
2175
2316
  case "compare": {
2176
2317
  const literal = [e.left, e.right].find((o) => o.kind === "literal");
2177
2318
  if (!(e.left.kind === "authUid" || e.right.kind === "authUid") || typeof literal?.value !== "string") return;
2178
- const platform = FOREIGN_CONVENTION_UIDS[literal.value];
2319
+ const platform = FOREIGN_CONVENTION_UIDS.get(literal.value);
2179
2320
  if (!platform) return;
2180
2321
  found.push({
2181
2322
  pattern: "foreign-uid-literal",
@@ -2191,12 +2332,43 @@ function findAnonymousGrants(expr) {
2191
2332
  return found;
2192
2333
  }
2193
2334
  function parseOperand(str) {
2194
- if (/current_setting\s*\(\s*'app\.(uid|user_id)'\s*\)/i.test(str) || /rebase\.uid\(\)/i.test(str)) return policy.authUid();
2195
- const stringMatch = str.match(/^'(.+)'$/);
2196
- if (stringMatch) return policy.literal(stringMatch[1]);
2197
- if (/^\w+$/.test(str)) return policy.field(str);
2335
+ if (/^current_setting\s*\(\s*'app\.(uid|user_id)'\s*\)$/i.test(str) || /^rebase\.uid\(\)$/i.test(str)) return policy.authUid();
2336
+ const literal = parseSingleQuoted(str);
2337
+ if (literal !== null) return policy.literal(literal);
2338
+ if (/^-?\d+$/.test(str)) return policy.literal(Number(str));
2339
+ if (/^-?\d*\.\d+$/.test(str)) return policy.literal(Number(str));
2340
+ if (/^true$/i.test(str)) return policy.literal(true);
2341
+ if (/^false$/i.test(str)) return policy.literal(false);
2342
+ if (/^null$/i.test(str)) return policy.literal(null);
2343
+ if (/^\w+$/.test(str) && toSnakeCase(str) !== "") return policy.field(str);
2198
2344
  return null;
2199
2345
  }
2346
+ /**
2347
+ * Decode a single-quoted SQL literal, or null when `str` is not exactly one.
2348
+ *
2349
+ * Rejecting is as important as decoding: `'a' = 'b'` is two literals and an
2350
+ * operator, not one literal whose body contains a quote, and a regex anchored
2351
+ * on the outer quotes would happily read it as the latter. Every interior quote
2352
+ * must therefore be part of a `''` pair.
2353
+ */
2354
+ function parseSingleQuoted(str) {
2355
+ if (str.length < 2 || !str.startsWith("'") || !str.endsWith("'")) return null;
2356
+ const body = str.slice(1, -1);
2357
+ let out = "";
2358
+ for (let i = 0; i < body.length; i++) {
2359
+ if (body[i] !== "'") {
2360
+ out += body[i];
2361
+ continue;
2362
+ }
2363
+ if (body[i + 1] === "'") {
2364
+ out += "'";
2365
+ i++;
2366
+ continue;
2367
+ }
2368
+ return null;
2369
+ }
2370
+ return out;
2371
+ }
2200
2372
  //#endregion
2201
2373
  //#region ../common/src/util/policy/securityRuleToConditions.ts
2202
2374
  /**
@@ -2354,8 +2526,14 @@ function rolesArraySql(roles) {
2354
2526
  * Rebase's enforcement model is unified: authenticated (user-context) requests
2355
2527
  * run under the restricted `rebase_user` role, so Postgres RLS binds *every*
2356
2528
  * statement — reads and writes. A collection's `securityRules` are the whole
2357
- * authorization model. The server context (auth flows, migrations,
2358
- * `dataAsAdmin`) runs as the owner and bypasses RLS.
2529
+ * authorization model. The server context (auth flows, migrations, raw
2530
+ * `rebase.sql`) runs as the owner and bypasses RLS.
2531
+ *
2532
+ * `rebase.dataAsAdmin` is **not** in that set, despite the name: it is scoped as
2533
+ * `{ uid: "service", roles: ["admin"] }`, so it runs as `rebase_user` like any
2534
+ * other caller and clears the baseline below through the *admin* arm, not the
2535
+ * server arm. Which is why `disableDefaultPolicies` plus a lone
2536
+ * `policy.serverContext()` rule locks it out too.
2359
2537
  *
2360
2538
  * Because RLS default-denies, every collection is **locked by default**: with
2361
2539
  * no rules, only the server context and admins can touch it. The generator
@@ -2413,11 +2591,28 @@ function getIdPropertyName$1(collection) {
2413
2591
  *
2414
2592
  * Collections that opt out via `disableDefaultPolicies` are returned unchanged.
2415
2593
  */
2594
+ /**
2595
+ * The restrictive write gate for an auth collection.
2596
+ *
2597
+ * Restrictive, so it is ANDed with everything else: whatever an author's
2598
+ * permissive rules allow, a write to this table still has to satisfy this too.
2599
+ * It is the only thing standing between "users may edit their own row" and
2600
+ * "users may grant themselves any role".
2601
+ */
2602
+ function adminWriteGate(tableName) {
2603
+ return {
2604
+ name: `${tableName}_require_admin_write`,
2605
+ mode: "restrictive",
2606
+ operations: [...DEFAULT_GUARDED_OPS],
2607
+ condition: SERVER_OR_ADMIN_EXPR$1,
2608
+ check: SERVER_OR_ADMIN_EXPR$1
2609
+ };
2610
+ }
2416
2611
  function getEffectiveSecurityRules(collection) {
2417
2612
  const explicit = [...collection.securityRules ?? []];
2418
- if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return explicit;
2419
2613
  const tableName = getTableName(collection);
2420
2614
  const injected = [];
2615
+ if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return isAuthCollection$1(collection) ? [...explicit, adminWriteGate(tableName)] : explicit;
2421
2616
  injected.push({
2422
2617
  name: `${tableName}_default_admin_read`,
2423
2618
  operations: ["select"],
@@ -2435,13 +2630,7 @@ function getEffectiveSecurityRules(collection) {
2435
2630
  operations: ["select"],
2436
2631
  condition: policy.compare(policy.field(getIdPropertyName$1(collection)), "eq", policy.authUid())
2437
2632
  });
2438
- injected.push({
2439
- name: `${tableName}_require_admin_write`,
2440
- mode: "restrictive",
2441
- operations: [...DEFAULT_GUARDED_OPS],
2442
- condition: SERVER_OR_ADMIN_EXPR$1,
2443
- check: SERVER_OR_ADMIN_EXPR$1
2444
- });
2633
+ injected.push(adminWriteGate(tableName));
2445
2634
  }
2446
2635
  return [...explicit, ...injected];
2447
2636
  }
@@ -3947,21 +4136,50 @@ async function collectAllPages(find, params, label = "collection") {
3947
4136
  * metadata, so type coercion is the responsibility of the server-side data
3948
4137
  * driver which has access to the collection schema.
3949
4138
  *
3950
- * Commas inside list values are backslash-escaped (`\,`), and literal
3951
- * backslashes are escaped as `\\`.
4139
+ * Structural characters inside a value are backslash-escaped: `,` `\,`,
4140
+ * `(` `\(`, `)` → `\)`, and a literal backslash as `\\`. Decoding is
4141
+ * deliberately conservative — only those four sequences are decoded, so a
4142
+ * backslash that arrives unescaped from an older client survives intact.
3952
4143
  *
3953
4144
  * @module
3954
4145
  */
3955
4146
  /**
3956
- * Unescape a single list item from the wire format.
3957
- * `\\``\`, `\,` → `,`
4147
+ * Escape a value for the wire format: `\` → `\\`, `,` → `\,`, `(` → `\(`,
4148
+ * `)``\)`.
4149
+ */
4150
+ /**
4151
+ * The wire spelling of an empty list.
4152
+ *
4153
+ * A lone backslash: unproducible by {@link escapeWireValue}, which doubles
4154
+ * every backslash it emits, so it cannot collide with any real item.
3958
4155
  */
3959
- function unescapeListItem(value) {
4156
+ var EMPTY_LIST_TOKEN = "\\";
4157
+ /**
4158
+ * Unescape a wire-format value.
4159
+ *
4160
+ * **Conservative**, and deliberately so: only the four sequences
4161
+ * {@link escapeWireValue} actually produces are decoded. A backslash followed
4162
+ * by anything else is left exactly as it is.
4163
+ *
4164
+ * This used to consume the backslash before *any* character, which is
4165
+ * indistinguishable for anything this codec emitted — it only ever emits those
4166
+ * four — but not for input arriving from elsewhere. A client on an older
4167
+ * release sends a Windows path or a LIKE pattern with a literal `C:\x`
4168
+ * unescaped, and greedy unescaping silently turned it into `C:x`, changing
4169
+ * which rows matched. Decoding only what the encoder can produce makes the two
4170
+ * directions agree across versions.
4171
+ */
4172
+ function unescapeWireValue(value) {
3960
4173
  let result = "";
3961
- for (let i = 0; i < value.length; i++) if (value[i] === "\\" && i + 1 < value.length) {
3962
- result += value[i + 1];
3963
- i++;
3964
- } else result += value[i];
4174
+ for (let i = 0; i < value.length; i++) {
4175
+ const next = value[i + 1];
4176
+ if (value[i] === "\\" && (next === "\\" || next === "," || next === "(" || next === ")")) {
4177
+ result += next;
4178
+ i++;
4179
+ continue;
4180
+ }
4181
+ result += value[i];
4182
+ }
3965
4183
  return result;
3966
4184
  }
3967
4185
  /**
@@ -3979,13 +4197,205 @@ function splitListItems(inner) {
3979
4197
  current += inner[i] + inner[i + 1];
3980
4198
  i++;
3981
4199
  } else if (inner[i] === ",") {
3982
- items.push(unescapeListItem(current));
4200
+ items.push(unescapeWireValue(current));
3983
4201
  current = "";
3984
4202
  } else current += inner[i];
3985
- items.push(unescapeListItem(current));
4203
+ items.push(unescapeWireValue(current));
3986
4204
  return items;
3987
4205
  }
3988
- var REST_OP_LOOKUP = REST_TO_CANONICAL;
4206
+ /**
4207
+ * Operator tables as `Map`s, because the key comes off the wire.
4208
+ *
4209
+ * Indexed as plain objects, every `Object.prototype` member answered: a query
4210
+ * string of `?f=valueOf.x` found a truthy "operator" — the inherited function —
4211
+ * and `deserializeTuple` returned it *as the operator*, so a function object
4212
+ * travelled on into the compilers in place of a `WhereFilterOp`. The guard one
4213
+ * line below (`if (!canonicalOp)`) reads as though it rejects anything unknown,
4214
+ * and does not: `Object.prototype` is not unknown to a plain object.
4215
+ *
4216
+ * Same shape as the prototype-key defects swept out of `setIn`, `getIn`,
4217
+ * `mergeDeep`, `unflattenObject` and `FOREIGN_CONVENTION_UIDS`.
4218
+ */
4219
+ var REST_OP_LOOKUP = new Map(Object.entries(REST_TO_CANONICAL));
4220
+ new Map(Object.entries(CANONICAL_TO_REST));
4221
+ /** The operator spellings a rejection lists back to the caller. */
4222
+ var VALID_OPERATOR_LIST = ALL_WHERE_FILTER_OPS.join(", ");
4223
+ /**
4224
+ * A filter condition named an operator this dialect does not have.
4225
+ *
4226
+ * ## Why this throws, rather than returning a typed rejection
4227
+ *
4228
+ * `deserializeFilter` is the *shared* codec: the REST ingress
4229
+ * (`packages/server/src/api/rest/query-parser.ts`), the browser SDK and the
4230
+ * admin panel (`buildRebaseData.ts`) all decode through it. Two constraints
4231
+ * follow.
4232
+ *
4233
+ * - It cannot throw the server's `ApiError`. `@rebasepro/common` does not
4234
+ * depend on `@rebasepro/server` (the dependency runs the other way), and a
4235
+ * browser client has no error handler to render an `ApiError` with. So the
4236
+ * rejection is this plain `Error` subclass, whose `message` reads correctly
4237
+ * wherever it surfaces — a rejected promise in an app, a 400 body over HTTP.
4238
+ * - It cannot be a returned rejection *value*. Every caller assigns the result
4239
+ * straight into a query it is about to run; a sentinel that none of them
4240
+ * check would be ignored, which is exactly the silently-wrong-filter failure
4241
+ * this exists to stop. Throwing is also what this file already does for the
4242
+ * sibling cases — `serializeTuple` on an unknown canonical operator,
4243
+ * `deserializeLogicalCondition` past the nesting bound — and the REST parser
4244
+ * already converts the latter into a 400.
4245
+ *
4246
+ * `statusCode`, `code` and `details` are carried as fields because the server's
4247
+ * Hono error handler duck-types those off any thrown error: a decode path that
4248
+ * forgets to convert still answers 400 with the canonical envelope instead of a
4249
+ * 500 that says "An unexpected error occurred". `query-parser.ts` converts
4250
+ * explicitly all the same — that is the path the contract is stated on, and an
4251
+ * incidental 400 is not a contract.
4252
+ */
4253
+ var UnknownFilterOperatorError = class extends Error {
4254
+ /** The field the condition was written against. */
4255
+ field;
4256
+ /** The operator string as it arrived, verbatim. */
4257
+ operator;
4258
+ /** Every operator this dialect accepts, in canonical spelling. */
4259
+ validOperators = ALL_WHERE_FILTER_OPS;
4260
+ /** See the class docblock: read by the server's error handler. */
4261
+ statusCode = 400;
4262
+ code = "UNKNOWN_FILTER_OPERATOR";
4263
+ details;
4264
+ constructor(field, operator) {
4265
+ super(`Unknown filter operator '${operator}' on field '${field}'. Valid operators: ${VALID_OPERATOR_LIST}`);
4266
+ this.name = "UnknownFilterOperatorError";
4267
+ this.field = field;
4268
+ this.operator = operator;
4269
+ this.details = {
4270
+ field,
4271
+ operator,
4272
+ validOperators: ALL_WHERE_FILTER_OPS
4273
+ };
4274
+ }
4275
+ };
4276
+ /**
4277
+ * Two to three characters of ASCII punctuation and nothing else — the shape
4278
+ * every symbolic operator has (`==`, `>=`, `<>`, `~~`, `!!`, `>>`, `===`), and
4279
+ * one a column value effectively never has.
4280
+ *
4281
+ * Two characters minimum on purpose. A *single* punctuation character is a
4282
+ * perfectly ordinary value — `{ grade: ["-", "+"] }` is a two-item list, not a
4283
+ * condition — and the only single-character operator anyone actually mistypes
4284
+ * is `=`, which is named separately below. `<` and `>` need no special case:
4285
+ * they are real operators and resolve.
4286
+ */
4287
+ var SYMBOLIC_OPERATOR = /^[^\p{L}\p{N}\s]{2,3}$/u;
4288
+ /** Lowercase, strip everything that is not a letter or digit. */
4289
+ function normalizeOperatorName(op) {
4290
+ return op.toLowerCase().replace(/[^a-z0-9]/g, "");
4291
+ }
4292
+ /**
4293
+ * Every real operator name with its case and separators removed, so a
4294
+ * respelling of one — `arrayContains`, `not_in`, `NOT-LIKE`, `isNull` — is
4295
+ * recognised as an attempt at an operator rather than read as a value.
4296
+ *
4297
+ * These are rejected rather than accepted: admitting a second spelling of an
4298
+ * operator would leave two wire spellings of one thing, and the rejection
4299
+ * message names the one that works.
4300
+ */
4301
+ var RESPELLED_OPERATORS = new Set([...ALL_WHERE_FILTER_OPS, ...Object.keys(REST_TO_CANONICAL)].map(normalizeOperatorName));
4302
+ /**
4303
+ * Operator names *other* query dialects use, which this one does not have.
4304
+ *
4305
+ * This list is curated, and deliberately so. For a word-shaped string there is
4306
+ * no rule that separates "an operator the caller guessed" from "a value that
4307
+ * happens to be a word": `{ tags: ["a", "b"] }` has to keep meaning a two-item
4308
+ * `in` list, so the codec cannot simply refuse every unrecognised word in
4309
+ * position 0. The line is therefore drawn by name, and only around names whose
4310
+ * use as an operator is far more likely than their use as one of two sibling
4311
+ * values. `contains` is the motivating case — the first thing a developer
4312
+ * reaches for, and until now it compiled to `title IN ('contains', 'Hell')`.
4313
+ *
4314
+ * Genuinely ambiguous single words (`any`, `all`, `exists`, `search`, `not`)
4315
+ * are left off: as operators they are rare, and as enum values they are common.
4316
+ * Everywhere else the tie goes to *rejecting*, because a 400 naming the
4317
+ * supported set costs the caller one round trip, and the alternative — which is
4318
+ * what every name on this list used to produce — is a query that runs, returns
4319
+ * rows, and is wrong.
4320
+ */
4321
+ var NEAR_MISS_OPERATORS = /* @__PURE__ */ new Set([
4322
+ "contains",
4323
+ "notcontains",
4324
+ "doesnotcontain",
4325
+ "doesnotcontains",
4326
+ "includes",
4327
+ "notincludes",
4328
+ "startswith",
4329
+ "notstartswith",
4330
+ "beginswith",
4331
+ "startingwith",
4332
+ "endswith",
4333
+ "notendswith",
4334
+ "matches",
4335
+ "notmatches",
4336
+ "regex",
4337
+ "regexp",
4338
+ "between",
4339
+ "notbetween",
4340
+ "equals",
4341
+ "notequals",
4342
+ "equalto",
4343
+ "isequalto",
4344
+ "isnotequalto",
4345
+ "greaterthan",
4346
+ "greaterthanorequal",
4347
+ "greaterthanorequalto",
4348
+ "lessthan",
4349
+ "lessthanorequal",
4350
+ "lessthanorequalto",
4351
+ "isempty",
4352
+ "isnotempty",
4353
+ "oneof",
4354
+ "noneof",
4355
+ "anyof",
4356
+ "allof",
4357
+ "null",
4358
+ "isnullorempty"
4359
+ ]);
4360
+ /**
4361
+ * Was this string *meant* as an operator?
4362
+ *
4363
+ * Only consulted after {@link toCanonicalOp} has already failed to resolve it,
4364
+ * so a `true` here is always a rejection.
4365
+ */
4366
+ function isOperatorShaped(op) {
4367
+ if (op === "=") return true;
4368
+ if (SYMBOLIC_OPERATOR.test(op)) return true;
4369
+ const normalized = normalizeOperatorName(op);
4370
+ if (!normalized) return false;
4371
+ return RESPELLED_OPERATORS.has(normalized) || NEAR_MISS_OPERATORS.has(normalized);
4372
+ }
4373
+ /**
4374
+ * Read a `[op, value]` tuple, if that is what this is.
4375
+ *
4376
+ * Three outcomes, and the middle one is the defect this function exists for:
4377
+ *
4378
+ * - the operator resolves (canonical *or* REST spelling) → the canonical tuple;
4379
+ * - the operator does not resolve but was plainly meant as one → throw;
4380
+ * - it does not look like an operator at all → `undefined`, and the caller
4381
+ * falls back to reading the array as a list of values.
4382
+ *
4383
+ * The old test was `toCanonicalOp(raw[0]) === raw[0]`, i.e. canonical spelling
4384
+ * only, with *everything else* — including every REST short-code — dropping
4385
+ * through to `["in", raw]`. So the operator string itself became a value in a
4386
+ * membership test: `["!!", "Hello"]` compiled to `title IN ('!!','Hello')`,
4387
+ * which matches, and the caller got back rows their filter was written to
4388
+ * exclude. `["eq", "active"]` had the same shape of failure.
4389
+ */
4390
+ function readTuple(field, raw) {
4391
+ if (!Array.isArray(raw) || raw.length !== 2) return void 0;
4392
+ const [op, value] = raw;
4393
+ if (typeof op !== "string") return void 0;
4394
+ const canonical = toCanonicalOp(op);
4395
+ if (canonical) return [canonical, value];
4396
+ if (op.includes(".")) return void 0;
4397
+ if (isOperatorShaped(op)) throw new UnknownFilterOperatorError(field, op);
4398
+ }
3989
4399
  /**
3990
4400
  * Parse a single PostgREST dot-string into a `[WhereFilterOp, unknown]` tuple.
3991
4401
  *
@@ -4002,10 +4412,13 @@ function deserializeSingle(raw) {
4002
4412
  if (dotIndex === -1) return ["==", raw];
4003
4413
  const prefix = raw.substring(0, dotIndex);
4004
4414
  const rest = raw.substring(dotIndex + 1);
4005
- const canonicalOp = REST_OP_LOOKUP[prefix];
4415
+ const canonicalOp = REST_OP_LOOKUP.get(prefix);
4006
4416
  if (!canonicalOp) return ["==", raw];
4007
4417
  if (NULL_OPS.has(canonicalOp)) return [canonicalOp, null];
4008
- if (rest.startsWith("(") && rest.endsWith(")")) return [canonicalOp, splitListItems(rest.slice(1, -1))];
4418
+ if (rest.startsWith("(") && rest.endsWith(")")) {
4419
+ const inner = rest.slice(1, -1);
4420
+ return [canonicalOp, inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner)];
4421
+ }
4009
4422
  return [canonicalOp, rest];
4010
4423
  }
4011
4424
  /**
@@ -4020,20 +4433,27 @@ function deserializeSingle(raw) {
4020
4433
  *
4021
4434
  * deserializeFilter({ age: ["gte.18", "lt.65"] })
4022
4435
  * // → { age: [[">=", "18"], ["<", "65"]] }
4436
+ *
4437
+ * @throws {UnknownFilterOperatorError} when a condition names an operator this
4438
+ * dialect does not have. See that class for why a rejection here is a throw.
4023
4439
  */
4024
4440
  function deserializeFilter(query) {
4025
4441
  const result = {};
4026
4442
  for (const [field, raw] of Object.entries(query)) {
4027
4443
  if (raw === void 0) continue;
4028
- if (Array.isArray(raw) && raw.length === 2 && typeof raw[0] === "string" && toCanonicalOp(raw[0]) === raw[0]) {
4029
- result[field] = raw;
4444
+ const tuple = readTuple(field, raw);
4445
+ if (tuple) {
4446
+ result[field] = tuple;
4030
4447
  continue;
4031
4448
  }
4032
4449
  if (Array.isArray(raw)) {
4033
4450
  if (raw.length === 0) continue;
4034
- if (Array.isArray(raw[0]) && raw[0].length === 2 && typeof raw[0][0] === "string" && toCanonicalOp(raw[0][0]) === raw[0][0]) {
4035
- result[field] = raw;
4036
- continue;
4451
+ if (Array.isArray(raw[0])) {
4452
+ const tuples = raw.map((item) => readTuple(field, item));
4453
+ if (tuples.every((t) => t !== void 0)) {
4454
+ result[field] = tuples;
4455
+ continue;
4456
+ }
4037
4457
  }
4038
4458
  if (raw.length === 1) result[field] = typeof raw[0] === "string" ? deserializeSingle(raw[0]) : ["==", raw[0]];
4039
4459
  else if (typeof raw[0] === "string" && raw[0].includes(".")) result[field] = raw.map((r) => typeof r === "string" ? deserializeSingle(r) : ["==", r]);
@@ -4792,6 +5212,48 @@ var searchIndexStatements = (spec) => {
4792
5212
  return statements;
4793
5213
  };
4794
5214
  /**
5215
+ * Marker on the comment of every generated search column this module creates.
5216
+ *
5217
+ * Versioned because the fingerprint below is only comparable against itself: a
5218
+ * future change to how it is computed has to read as "not stamped by this
5219
+ * version" rather than as drift on every existing column.
5220
+ */
5221
+ var SEARCH_STAMP_PREFIX = "rebase:search:v1:";
5222
+ /**
5223
+ * A stable fingerprint of one generated column's expression.
5224
+ *
5225
+ * Why a stamp rather than reading the expression back: Postgres stores a
5226
+ * generated column's expression *parsed*, and hands it back deparsed — casts
5227
+ * made explicit, identifiers requoted, schema qualifications added or dropped
5228
+ * according to `search_path`. Comparing that text to the text we generated
5229
+ * would report drift on wording, and this comparison decides whether a boot
5230
+ * refuses, so a false positive is an outage. The stamp is written by the same
5231
+ * code that writes the column, so equality means what it says.
5232
+ */
5233
+ var searchExpressionFingerprint = (expression) => `${SEARCH_STAMP_PREFIX}${createHash("sha256").update(expression).digest("hex").slice(0, 16)}`;
5234
+ /**
5235
+ * The stamps for a spec's generated columns — one per column, never shared.
5236
+ *
5237
+ * Per column on purpose: turning `fuzzy` on adds a second column and changes
5238
+ * nothing about the first, and a spec-wide fingerprint would report the
5239
+ * untouched `tsvector` column as drifted and refuse a boot over a change that
5240
+ * is purely additive.
5241
+ */
5242
+ var searchColumnStamps = (spec) => {
5243
+ const stamp = (column, expression) => {
5244
+ const fingerprint = searchExpressionFingerprint(expression);
5245
+ return {
5246
+ column,
5247
+ expression,
5248
+ fingerprint,
5249
+ sql: `COMMENT ON COLUMN "${spec.schema}"."${spec.table}"."${column}" IS ${quote(fingerprint)};`
5250
+ };
5251
+ };
5252
+ const stamps = [stamp(spec.column, spec.expression)];
5253
+ if (spec.fuzzy) stamps.push(stamp(spec.fuzzy.column, spec.fuzzy.expression));
5254
+ return stamps;
5255
+ };
5256
+ /**
4795
5257
  * The generated column names a collection's search block adds, if any.
4796
5258
  *
4797
5259
  * These are physical columns on the table, so `SELECT *` returns them. They are
@@ -4968,6 +5430,6 @@ function isAuthCollection(collection) {
4968
5430
  return typeof auth === "object" && auth !== null && auth.enabled === true;
4969
5431
  }
4970
5432
  //#endregion
4971
- export { resolveCollectionRelations as A, legacyForeignKeyName as B, securityRuleToConditions as C, getEnumVarName as D, getColumnName as E, createRelationRef as F, camelCase as G, getPolicyNamesForRule as H, createRelationRefWithData as I, DEFAULT_ONE_OF_VALUE as J, toSnakeCase as K, normalizeToEntityRelation as L, getDeclaredPrimaryKeys as M, isAddressableId as N, getTableName as O, parseIdValues as P, Vector as Q, updateDateAutoValues as R, policyToPostgres as S, findRelation as T, isPrototypePollutingKey as U, toPostgresIdentifier as V, mergeDeep as W, hasForeignKeyOnTarget as X, resolveClientListLimit as Y, isManyToMany as Z, resolveStringColumnLength as _, SEARCH_TEXT_FN as a, resolveJunctionSpecs as b, buildSearchColumnSpec as c, searchHelperFunctions as d, searchIndexStatements as f, relationalCollections as g, CollectionRegistry as h, isAuthCollection as i, buildCompositeId as j, getTableVarName as k, hiddenColumnsOption as l, buildSdkData as m, authUsersColumnDefinition as n, SEARCH_UNACCENT_FN as o, visibleColumnProjection as p, DEFAULT_ONE_OF_TYPE as q, authUsersColumnSql as r, assertSearchIsPostgresOnly as s, AUTH_USERS_COLUMNS as t, searchExtensionStatements as u, getJunctionCollectionConfig as v, findAnonymousGrants as w, getEffectiveSecurityRules as x, getJunctionSecurityRules as y, generateForeignKeyName as z };
5433
+ export { DEFAULT_ONE_OF_TYPE as $, getEnumVarName as A, createRelationRefWithData as B, getEffectiveSecurityRules as C, fieldKeyForColumn as D, findAnonymousGrants as E, getDeclaredPrimaryKeys as F, legacyForeignKeyName as G, updateDateAutoValues as H, isAddressableId as I, getPolicyNamesForRule as J, toPostgresIdentifier as K, parseIdValues as L, getTableVarName as M, resolveCollectionRelations as N, findRelation as O, buildCompositeId as P, toSnakeCase as Q, sortCollectionsBySlug as R, resolveJunctionSpecs as S, securityRuleToConditions as T, firstFreeKey as U, normalizeToEntityRelation as V, generateForeignKeyName as W, mergeDeep as X, isPrototypePollutingKey as Y, camelCase as Z, CollectionRegistry as _, SEARCH_STAMP_PREFIX as a, getJunctionCollectionConfig as b, assertSearchIsPostgresOnly as c, searchColumnStamps as d, DEFAULT_ONE_OF_VALUE as et, searchExtensionStatements as f, buildSdkData as g, visibleColumnProjection as h, isAuthCollection as i, getTableName as j, getColumnName as k, buildSearchColumnSpec as l, searchIndexStatements as m, authUsersColumnDefinition as n, isManyToMany as nt, SEARCH_TEXT_FN as o, searchHelperFunctions as p, toWireKey as q, authUsersColumnSql as r, Vector as rt, SEARCH_UNACCENT_FN as s, AUTH_USERS_COLUMNS as t, hasForeignKeyOnTarget as tt, hiddenColumnsOption as u, relationalCollections as v, policyToPostgres as w, getJunctionSecurityRules as x, resolveStringColumnLength as y, createRelationRef as z };
4972
5434
 
4973
- //# sourceMappingURL=auth-users-columns-Dt9g712t.js.map
5435
+ //# sourceMappingURL=auth-users-columns-BfQHf9JE.js.map