@rebasepro/server-postgres 0.12.1-canary.g4e7bcbf → 0.12.1-canary.g6861540

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 (47) hide show
  1. package/dist/auth/services.d.ts +6 -1
  2. package/dist/backup/backup-service.d.ts +10 -1
  3. package/dist/backup/pg-tools.d.ts +47 -0
  4. package/dist/{backup-service-DLb2drIH.js → backup-service-CD8o_1Sl.js} +136 -10
  5. package/dist/backup-service-CD8o_1Sl.js.map +1 -0
  6. package/dist/{ensure-collection-policies-Dv21KDMJ.js → ensure-collection-policies-BrUVgjz3.js} +2 -2
  7. package/dist/{ensure-collection-policies-Dv21KDMJ.js.map → ensure-collection-policies-BrUVgjz3.js.map} +1 -1
  8. package/dist/{ensure-collection-tables-CT6xHB1d.js → ensure-collection-tables-Da2oGkX2.js} +2 -2
  9. package/dist/{ensure-collection-tables-CT6xHB1d.js.map → ensure-collection-tables-Da2oGkX2.js.map} +1 -1
  10. package/dist/index.es.js +256 -88
  11. package/dist/index.es.js.map +1 -1
  12. package/dist/schema/introspect-db-constraints.d.ts +57 -0
  13. package/dist/schema/introspect-db-logic.d.ts +94 -5
  14. package/dist/schema/introspect-db-queries.d.ts +119 -0
  15. package/dist/schema/introspect-db-structure.d.ts +263 -0
  16. package/dist/schema/introspect-db-types.d.ts +11 -0
  17. package/dist/services/RelationService.d.ts +24 -1
  18. package/dist/services/channel-bus/index.d.ts +1 -7
  19. package/dist/services/collection-helpers.d.ts +24 -1
  20. package/dist/{src-BkdpiQdw.js → src-CzbghKwf.js} +43 -5
  21. package/dist/src-CzbghKwf.js.map +1 -0
  22. package/dist/utils/connection-string.d.ts +29 -0
  23. package/dist/utils/drizzle-conditions.d.ts +5 -4
  24. package/package.json +6 -6
  25. package/src/auth/services.ts +6 -3
  26. package/src/backup/backup-cli.ts +41 -2
  27. package/src/backup/backup-service.ts +38 -5
  28. package/src/backup/pg-tools.ts +96 -3
  29. package/src/cli.ts +11 -4
  30. package/src/collections/validate-relations.ts +15 -0
  31. package/src/data-transformer.ts +9 -3
  32. package/src/schema/generate-drizzle-schema-logic.ts +7 -1
  33. package/src/schema/introspect-db-constraints.ts +385 -0
  34. package/src/schema/introspect-db-inference.ts +18 -8
  35. package/src/schema/introspect-db-logic.ts +364 -68
  36. package/src/schema/introspect-db-queries.ts +326 -0
  37. package/src/schema/introspect-db-structure.ts +670 -0
  38. package/src/schema/introspect-db-types.ts +56 -0
  39. package/src/schema/introspect-db.ts +37 -80
  40. package/src/services/PersistService.ts +20 -6
  41. package/src/services/RelationService.ts +211 -45
  42. package/src/services/channel-bus/index.ts +0 -9
  43. package/src/services/collection-helpers.ts +40 -1
  44. package/src/utils/connection-string.ts +58 -0
  45. package/src/utils/drizzle-conditions.ts +31 -6
  46. package/dist/backup-service-DLb2drIH.js.map +0 -1
  47. package/dist/src-BkdpiQdw.js.map +0 -1
package/dist/index.es.js CHANGED
@@ -2,11 +2,11 @@ import { createRequire as __createRequire } from "module";
2
2
  import process from "process";
3
3
  __createRequire(import.meta.url);
4
4
  import { a as guardPoolAgainstDirtyRelease, i as createReadReplicaConnection, n as createDirectDatabaseConnection, o as pinSearchPath, r as createPostgresDatabaseConnection } from "./connection-BuZ97wsr.js";
5
- import { C as updateDateAutoValues, D as mergeDeep, E as getPolicyNamesForRule, M as hasForeignKeyOnTarget, N as isManyToMany, O as camelCase, P as Vector, S as normalizeToEntityRelation, _ as buildCompositeId, a as getJunctionSecurityRules, b as createRelationRef, c as policyToPostgres, d as findRelation, f as getColumnName, g as resolveCollectionRelations, h as getTableVarName, i as getJunctionCollectionConfig, k as toSnakeCase, l as securityRuleToConditions, m as getTableName$1, n as CollectionRegistry, o as resolveJunctionSpecs, p as getEnumVarName, r as resolveStringColumnLength, s as getEffectiveSecurityRules, t as buildSdkData, u as findAnonymousGrants, v as getDeclaredPrimaryKeys, w as generateForeignKeyName, x as createRelationRefWithData, y as parseIdValues } from "./src-BkdpiQdw.js";
5
+ import { C as updateDateAutoValues, D as mergeDeep, E as getPolicyNamesForRule, M as hasForeignKeyOnTarget, N as isManyToMany, O as camelCase, P as Vector, S as normalizeToEntityRelation, _ as buildCompositeId, a as getJunctionSecurityRules, b as createRelationRef, c as policyToPostgres, d as findRelation, f as getColumnName, g as resolveCollectionRelations, h as getTableVarName, i as getJunctionCollectionConfig, k as toSnakeCase, l as securityRuleToConditions, m as getTableName$1, n as CollectionRegistry, o as resolveJunctionSpecs, p as getEnumVarName, r as resolveStringColumnLength, s as getEffectiveSecurityRules, t as buildSdkData, u as findAnonymousGrants, v as getDeclaredPrimaryKeys, w as generateForeignKeyName, x as createRelationRefWithData, y as parseIdValues } from "./src-CzbghKwf.js";
6
6
  import { n as isPostgresCollectionConfig, r as isRelationalCollectionConfig } from "./src-DoU9yPqq.js";
7
7
  import { t as ANONYMOUS_USER_ID } from "./policy-CeA1JcxP.js";
8
8
  import { t as createPostgresWebSocket } from "./websocket-B2LsrINK.js";
9
- import { A as serverVersionNumToMajor, C as globalsFileForDump, D as parseDbNameFromUrl, E as parseBackupTimestamp, M as withDatabaseName, O as parsePgToolMajor, S as checkToolServerCompatibility, T as parseBackupDestination, _ as buildBackupFilename, a as detectToolMajor, b as buildPgRestoreArgs, c as listBackups, d as resolvePgBinary, f as restoreDump, g as selectBackupsToPrune, h as require_source, i as createDump, j as splitGlobalsStatements, k as resolveConnectionString, l as preflight, m as validateDump, n as applyGlobals, o as ensureDatabaseExists, p as uploadBackup, s as getServerVersionMajor, t as BackupToolError, u as pruneBackups, v as buildPgDumpArgs, w as joinStorageKey, x as buildPgRestoreListArgs, y as buildPgDumpallGlobalsArgs } from "./backup-service-DLb2drIH.js";
9
+ import { A as parsePgToolMajor, C as checkToolServerCompatibility, D as parseBackupDestination, E as joinStorageKey, M as serverVersionNumToMajor, N as splitGlobalsStatements, O as parseBackupTimestamp, P as withDatabaseName, S as buildRowSecurityPgOptions, T as globalsFileForDump, _ as buildBackupFilename, a as detectToolMajor, b as buildPgRestoreArgs, c as listBackups, d as resolvePgBinary, f as restoreDump, g as selectBackupsToPrune, h as require_source, i as createDump, j as resolveConnectionString, k as parseDbNameFromUrl, l as preflight, m as validateDump, n as applyGlobals, o as ensureDatabaseExists, p as uploadBackup, s as getServerVersionMajor, t as BackupToolError, u as pruneBackups, v as buildPgDumpArgs, w as diagnoseRowSecurityDumpFailure, x as buildPgRestoreListArgs, y as buildPgDumpallGlobalsArgs } from "./backup-service-CD8o_1Sl.js";
10
10
  import { Client, Pool } from "pg";
11
11
  import { drizzle } from "drizzle-orm/node-postgres";
12
12
  import { ApiError, createEmailService, loadCollectionsFromDirectory, logger } from "@rebasepro/server";
@@ -50,6 +50,39 @@ function resolveClientListLimit(rawLimit, opts = {}) {
50
50
  return opts.vectorSearch ? opts.vectorDefaultLimit ?? 10 : opts.defaultLimit ?? 50;
51
51
  }
52
52
  //#endregion
53
+ //#region ../common/src/util/email.ts
54
+ /**
55
+ * Email normalization — one implementation, because the database enforces it.
56
+ *
57
+ * `ensureAuthTablesExist` puts a `UNIQUE INDEX ON users (lower(email))` on the
58
+ * auth table. That index decides what "the same address" means, and it does not
59
+ * trim: to Postgres, `' foo@bar.com'` and `'foo@bar.com'` are two addresses and
60
+ * both may exist. So every write that reaches the column has to agree with
61
+ * every read, exactly, or the two disagree in the one direction that matters —
62
+ * a row that exists and cannot be found.
63
+ *
64
+ * That is not hypothetical. The lookup path trimmed and the admin create paths
65
+ * did not, so a user created through `POST /api/data/users` or
66
+ * `POST /api/auth/admin/users` with a stray space was stored untrimmed,
67
+ * survived the unique index alongside the real address, and was unreachable by
68
+ * login forever after. The HTTP auth routes were unaffected only because Zod's
69
+ * `.email()` happens to reject surrounding whitespace — a guard on a different
70
+ * layer, for a different reason, that the admin paths do not sit behind.
71
+ *
72
+ * It lives in `common` because `server`, `server-postgres` and `server-mongo`
73
+ * all write this column and must agree exactly, and `common` is the only
74
+ * package all three already depend on.
75
+ */
76
+ /**
77
+ * Canonical form of an email address: trimmed, lower-cased.
78
+ *
79
+ * Non-strings pass through untouched, so this is safe to apply to a value out
80
+ * of a partial update payload whose type is not known yet.
81
+ */
82
+ function normalizeEmail(email) {
83
+ return typeof email === "string" ? email.trim().toLowerCase() : email;
84
+ }
85
+ //#endregion
53
86
  //#region ../common/src/util/callbacks.ts
54
87
  /**
55
88
  * Helper function to recursively check if there are any callbacks in the properties.
@@ -282,6 +315,35 @@ function requirePrimaryKeys(collection, registry) {
282
315
  return keys;
283
316
  }
284
317
  /**
318
+ * The column on the *source* table that a `hasOne`/`hasMany` link points at.
319
+ *
320
+ * `sourceKey` is authored when the two sides join on a natural key — an
321
+ * external identity id, a SKU — and left off when they join on the row id,
322
+ * which is the overwhelming majority. That makes `undefined` the only optional
323
+ * field on a resolved relation, so it gets exactly one reader: this function.
324
+ * Every consumer that needs the column asks here, and none of them re-derives
325
+ * "or else the primary key" for itself. That is the whole point — the fallback
326
+ * chains this codebase removed from relation resolution were dangerous because
327
+ * they were *duplicated* and could disagree, not because they existed.
328
+ */
329
+ function sourceKeyField(relation, sourceCollection, registry) {
330
+ if (relation.sourceKey) return relation.sourceKey;
331
+ return requirePrimaryKeys(sourceCollection, registry)[0].fieldName;
332
+ }
333
+ /**
334
+ * Whether this link joins on something other than the source's primary key.
335
+ *
336
+ * Callers that hold a parent *id* — which is most of them, since an id is what
337
+ * a URL carries — must translate it to the source key's value before it can be
338
+ * compared with the target's foreign key. Those that hold the parent *row*, or
339
+ * that build a correlated subquery over the source table, can read the column
340
+ * directly and skip the lookup.
341
+ */
342
+ function joinsOnNaturalKey(relation, sourceCollection, registry) {
343
+ if (!relation.sourceKey) return false;
344
+ return relation.sourceKey !== requirePrimaryKeys(sourceCollection, registry)[0].fieldName;
345
+ }
346
+ /**
285
347
  * Collections whose key the *browser* cannot resolve, and what it will do
286
348
  * instead.
287
349
  *
@@ -452,7 +514,9 @@ var DrizzleConditionBuilder = class {
452
514
  case "hasMany": {
453
515
  const fkColumn = targetTable[relation.foreignKeyOnTarget];
454
516
  if (!fkColumn) throw new Error(`Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of relation '${relation.relationName}'.`);
455
- return eq(fkColumn, parentId);
517
+ if (!relation.sourceKey) return eq(fkColumn, parentId);
518
+ const { table, idColumn } = parent();
519
+ return sql`${fkColumn} = (SELECT ${sql.identifier(relation.sourceKey)} FROM ${table} WHERE ${idColumn} = ${parentId})`;
456
520
  }
457
521
  case "belongsTo": {
458
522
  const { table, idColumn } = parent();
@@ -542,12 +606,16 @@ var DrizzleConditionBuilder = class {
542
606
  column: foreignKey
543
607
  };
544
608
  }
545
- if (relation && (hasForeignKeyOnTarget(relation) || isManyToMany(relation)) && registry && sourceIdColumn) return {
546
- kind: "relation",
547
- relation,
548
- registry,
549
- sourceIdColumn
550
- };
609
+ if (relation && (hasForeignKeyOnTarget(relation) || isManyToMany(relation)) && registry && sourceIdColumn) {
610
+ const correlationColumn = hasForeignKeyOnTarget(relation) && relation.sourceKey ? columnAt(relation.sourceKey) : sourceIdColumn;
611
+ if (!correlationColumn) throw new Error(`\`sourceKey: "${relation.sourceKey}"\` on relation '${relation.relationName}' is not a column on '${collectionPath}', so a filter on that relation has nothing to correlate against.`);
612
+ return {
613
+ kind: "relation",
614
+ relation,
615
+ registry,
616
+ sourceIdColumn: correlationColumn
617
+ };
618
+ }
551
619
  }
552
620
  for (const guess of [`${field}_id`, generateForeignKeyName(field)]) {
553
621
  const foreignKey = columnAt(guess);
@@ -1346,8 +1414,9 @@ async function parseDataFromServer(data, collection, db, registry) {
1346
1414
  } else if (hasForeignKeyOnTarget(relation) && db && registry) try {
1347
1415
  const targetCollection = relation.target();
1348
1416
  const targetTable = registry.getTable(getTableName$1(targetCollection));
1349
- const currentId = buildCompositeId(data, getPrimaryKeys(collection, registry));
1350
- if (targetTable && currentId) {
1417
+ const pks = getPrimaryKeys(collection, registry);
1418
+ const currentId = relation.sourceKey ? data[relation.sourceKey] : buildCompositeId(data, pks);
1419
+ if (targetTable && currentId !== void 0 && currentId !== null && currentId !== "") {
1351
1420
  const foreignKeyColumn = targetTable[relation.foreignKeyOnTarget];
1352
1421
  if (foreignKeyColumn) {
1353
1422
  const relatedRows = await db.select().from(targetTable).where(eq(foreignKeyColumn, currentId)).limit(relation.cardinality === "one" ? 1 : 100);
@@ -1719,8 +1788,67 @@ var RelationService = class {
1719
1788
  * reading. Left alone it would silently match on the first key column and
1720
1789
  * hand a tenant's rows to its neighbour — say so instead.
1721
1790
  */
1722
- assertSingleKeyAddressable(parentCollection, parentPks, via) {
1723
- if (parentPks.length > 1) throw new Error(`Relation on '${parentCollection.slug}' uses '${via}', a single foreign-key column, but '${parentCollection.slug}' is keyed on ${parentPks.map((k) => `'${k.fieldName}'`).join(" + ")}. One column cannot reference a composite key — express this relation with \`joinPath\`, whose \`on.from\`/\`on.to\` take every key column.`);
1791
+ assertSingleKeyAddressable(parentCollection, parentPks, via, relation) {
1792
+ if (relation && hasForeignKeyOnTarget(relation) && relation.sourceKey) return;
1793
+ if (parentPks.length > 1) throw new Error(`Relation on '${parentCollection.slug}' uses '${via}', a single foreign-key column, but '${parentCollection.slug}' is keyed on ${parentPks.map((k) => `'${k.fieldName}'`).join(" + ")}. One column cannot reference a composite key — give the relation a \`sourceKey\` naming a single unique column to point at, or express it with \`joinPath\`, whose \`on.from\`/\`on.to\` take every key column.`);
1794
+ }
1795
+ /**
1796
+ * What the target's foreign key holds, for each of these parent rows.
1797
+ *
1798
+ * Ordinarily the parent's id, and then this is free. When the relation
1799
+ * declares a `sourceKey` the two are different values, and the mapping
1800
+ * between them lives in the source table — so it costs one SELECT, issued
1801
+ * once for the whole batch rather than per parent.
1802
+ *
1803
+ * Both directions come back because both are needed and deriving one from
1804
+ * the other by hand is how a batch loader ends up attributing a child to the
1805
+ * wrong parent: reads translate id → key to build the WHERE, and then
1806
+ * translate key → id to attribute each row that comes back.
1807
+ */
1808
+ /**
1809
+ * The value a related row's foreign key must hold to belong to this parent.
1810
+ *
1811
+ * `undefined` when the parent's source key is null — which is not an error
1812
+ * here, only in the callers that were about to write it. Exposed for
1813
+ * {@link PersistService}, which stamps this onto a child created under a
1814
+ * nested path and would otherwise write the id and lose the row.
1815
+ */
1816
+ async parentKeyValue(parentCollection, relation, parentId, db = this.db) {
1817
+ const { keyByParentId } = await this.resolveSourceKeys(parentCollection, relation, [parentId], db);
1818
+ return keyByParentId.get(String(parentId));
1819
+ }
1820
+ async resolveSourceKeys(parentCollection, relation, parentIds, db = this.db) {
1821
+ const keyByParentId = /* @__PURE__ */ new Map();
1822
+ const parentIdByKey = /* @__PURE__ */ new Map();
1823
+ const parentPks = requirePrimaryKeys(parentCollection, this.registry);
1824
+ if (!joinsOnNaturalKey(relation, parentCollection, this.registry)) {
1825
+ for (const id of parentIds) {
1826
+ const value = parseIdValues(id, parentPks)[parentPks[0].fieldName];
1827
+ keyByParentId.set(String(id), value);
1828
+ parentIdByKey.set(String(value), id);
1829
+ }
1830
+ return {
1831
+ keyByParentId,
1832
+ parentIdByKey
1833
+ };
1834
+ }
1835
+ const field = sourceKeyField(relation, parentCollection, this.registry);
1836
+ const parentTable = getTableForCollection(parentCollection, this.registry);
1837
+ if (!parentTable[field]) throw new Error(`\`sourceKey: "${field}"\` on relation '${relation.relationName}' is not a column on '${parentCollection.slug}'. It names a column on the source table, not on the target.`);
1838
+ const rows = await db.select().from(parentTable).where(this.parentKeyCondition(parentTable, parentPks, parentIds));
1839
+ for (const row of rows) {
1840
+ const keyValue = row[field];
1841
+ if (keyValue === null || keyValue === void 0) continue;
1842
+ const parentId = buildCompositeId(row, parentPks);
1843
+ keyByParentId.set(String(parentId), keyValue);
1844
+ const existing = parentIdByKey.get(String(keyValue));
1845
+ if (existing !== void 0 && String(existing) !== String(parentId)) throw new Error(`\`sourceKey: "${field}"\` on relation '${relation.relationName}' is not unique on '${parentCollection.slug}': rows '${existing}' and '${parentId}' both hold '${keyValue}'. Add a unique constraint — a link that addresses more than one source row cannot say which one a related row belongs to.`);
1846
+ parentIdByKey.set(String(keyValue), parentId);
1847
+ }
1848
+ return {
1849
+ keyByParentId,
1850
+ parentIdByKey
1851
+ };
1724
1852
  }
1725
1853
  /**
1726
1854
  * Fetch rows related to a parent row through a specific relation
@@ -1779,6 +1907,8 @@ var RelationService = class {
1779
1907
  }
1780
1908
  return rows;
1781
1909
  }
1910
+ const matchValue = hasForeignKeyOnTarget(relation) ? (await this.resolveSourceKeys(parentCollection, relation, [parentId])).keyByParentId.get(String(parentId)) : parsedParentId;
1911
+ if (matchValue === void 0) return [];
1782
1912
  let query = this.db.select().from(targetTable).$dynamic();
1783
1913
  const additionalFilters = [];
1784
1914
  if (options.searchString) {
@@ -1787,7 +1917,7 @@ var RelationService = class {
1787
1917
  const searchCombined = DrizzleConditionBuilder.combineConditionsWithOr(searchConditions);
1788
1918
  if (searchCombined) additionalFilters.push(searchCombined);
1789
1919
  }
1790
- query = applyDynamicRelationQuery(query, query, relation, parsedParentId, targetTable, parentTable, parentIdCol, idField, this.registry, additionalFilters);
1920
+ query = applyDynamicRelationQuery(query, query, relation, matchValue, targetTable, parentTable, parentIdCol, idField, this.registry, additionalFilters);
1791
1921
  if (options.limit) query = query.limit(options.limit);
1792
1922
  const results = await query;
1793
1923
  if (!results || !Array.isArray(results)) return [];
@@ -1830,8 +1960,10 @@ var RelationService = class {
1830
1960
  const parentTable = this.registry.getTable(getTableName$1(parentCollection));
1831
1961
  if (!parentTable) throw new Error("Parent table not found");
1832
1962
  const parentIdCol = parentTable[parentIdInfo.fieldName];
1963
+ const matchValue = hasForeignKeyOnTarget(relation) ? (await this.resolveSourceKeys(parentCollection, relation, [parentId])).keyByParentId.get(String(parentId)) : parsedParentId;
1964
+ if (matchValue === void 0) return 0;
1833
1965
  let query = this.db.select({ count: sql`count(distinct ${targetIdField})` }).from(targetTable).$dynamic();
1834
- query = DrizzleConditionBuilder.buildRelationCountQuery(query, relation, parsedParentId, targetTable, parentTable, parentIdCol, targetIdField, this.registry, additionalFilters);
1966
+ query = DrizzleConditionBuilder.buildRelationCountQuery(query, relation, matchValue, targetTable, parentTable, parentIdCol, targetIdField, this.registry, additionalFilters);
1835
1967
  const result = await query;
1836
1968
  return Number(result[0]?.count || 0);
1837
1969
  }
@@ -1958,17 +2090,24 @@ var RelationService = class {
1958
2090
  }
1959
2091
  return resultMap;
1960
2092
  }
1961
- this.assertSingleKeyAddressable(parentCollection, parentPks, hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName);
2093
+ this.assertSingleKeyAddressable(parentCollection, parentPks, hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName, relation);
2094
+ const { keyByParentId, parentIdByKey } = hasForeignKeyOnTarget(relation) ? await this.resolveSourceKeys(parentCollection, relation, parentIds) : {
2095
+ keyByParentId: /* @__PURE__ */ new Map(),
2096
+ parentIdByKey: /* @__PURE__ */ new Map()
2097
+ };
2098
+ const matchValues = hasForeignKeyOnTarget(relation) ? [...keyByParentId.values()] : parsedParentIds;
2099
+ if (matchValues.length === 0) return /* @__PURE__ */ new Map();
1962
2100
  let query = this.db.select().from(targetTable).$dynamic();
1963
- query = applyDynamicRelationQuery(query, query, relation, parsedParentIds, targetTable, parentTable, parentIdCol, targetIdField, this.registry, []);
2101
+ query = applyDynamicRelationQuery(query, query, relation, matchValues, targetTable, parentTable, parentIdCol, targetIdField, this.registry, []);
1964
2102
  const results = await query;
1965
2103
  const resultMap = /* @__PURE__ */ new Map();
1966
- const parentIdSet = new Set(parsedParentIds.map(String));
1967
2104
  for (const row of results) {
1968
2105
  const targetRow = row[getTableName$1(targetCollection)] || row;
1969
- let parentId;
1970
- if (hasForeignKeyOnTarget(relation)) parentId = targetRow[relation.foreignKeyOnTarget];
1971
- if (parentId !== void 0 && parentIdSet.has(String(parentId))) resultMap.set(String(parentId), await this.toRelatedRow(targetRow, targetCollection, targetPks));
2106
+ if (!hasForeignKeyOnTarget(relation)) continue;
2107
+ const foreignKeyValue = targetRow[relation.foreignKeyOnTarget];
2108
+ if (foreignKeyValue === void 0 || foreignKeyValue === null) continue;
2109
+ const parentId = parentIdByKey.get(String(foreignKeyValue));
2110
+ if (parentId !== void 0) resultMap.set(String(parentId), await this.toRelatedRow(targetRow, targetCollection, targetPks));
1972
2111
  }
1973
2112
  return resultMap;
1974
2113
  }
@@ -2046,17 +2185,24 @@ var RelationService = class {
2046
2185
  }
2047
2186
  return resultMap;
2048
2187
  }
2049
- this.assertSingleKeyAddressable(parentCollection, parentPks, hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName);
2188
+ this.assertSingleKeyAddressable(parentCollection, parentPks, hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName, relation);
2189
+ const { keyByParentId, parentIdByKey } = hasForeignKeyOnTarget(relation) ? await this.resolveSourceKeys(parentCollection, relation, parentIds) : {
2190
+ keyByParentId: /* @__PURE__ */ new Map(),
2191
+ parentIdByKey: /* @__PURE__ */ new Map()
2192
+ };
2193
+ const matchValues = hasForeignKeyOnTarget(relation) ? [...keyByParentId.values()] : parsedParentIds;
2194
+ if (matchValues.length === 0) return /* @__PURE__ */ new Map();
2050
2195
  let query = this.db.select().from(targetTable).$dynamic();
2051
- query = applyDynamicRelationQuery(query, query, relation, parsedParentIds, targetTable, parentTable, parentIdCol, targetIdField, this.registry, []);
2196
+ query = applyDynamicRelationQuery(query, query, relation, matchValues, targetTable, parentTable, parentIdCol, targetIdField, this.registry, []);
2052
2197
  const results = await query;
2053
2198
  const resultMap = /* @__PURE__ */ new Map();
2054
- const parentIdSet = new Set(parsedParentIds.map(String));
2055
2199
  for (const row of results) {
2056
2200
  const targetRow = row[getTableName$1(targetCollection)] || row;
2057
- let parentId;
2058
- if (hasForeignKeyOnTarget(relation)) parentId = targetRow[relation.foreignKeyOnTarget];
2059
- if (parentId !== void 0 && parentIdSet.has(String(parentId))) {
2201
+ if (!hasForeignKeyOnTarget(relation)) continue;
2202
+ const foreignKeyValue = targetRow[relation.foreignKeyOnTarget];
2203
+ if (foreignKeyValue === void 0 || foreignKeyValue === null) continue;
2204
+ const parentId = parentIdByKey.get(String(foreignKeyValue));
2205
+ if (parentId !== void 0) {
2060
2206
  const key = String(parentId);
2061
2207
  const arr = resultMap.get(key) || [];
2062
2208
  arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));
@@ -2155,14 +2301,13 @@ var RelationService = class {
2155
2301
  logger.warn(`Invalid inverse-many config for relation '${key}' in collection '${collection.slug}'`);
2156
2302
  continue;
2157
2303
  }
2158
- const parentPks = requirePrimaryKeys(collection, this.registry);
2159
- const parentIdInfo = parentPks[0];
2160
- const parsedParentId = parseIdValues(id, parentPks)[parentIdInfo.fieldName];
2304
+ const parentKeyValue = (await this.resolveSourceKeys(collection, relation, [id], tx)).keyByParentId.get(String(id));
2305
+ if (parentKeyValue === void 0) throw new Error(`Cannot write relation '${key}' on '${collection.slug}': row '${id}' has no value in \`sourceKey: "${sourceKeyField(relation, collection, this.registry)}"\`, so there is nothing for the related rows to point at.`);
2161
2306
  if (targetEntityIds.length > 0) {
2162
2307
  const parsedTargetIds = targetEntityIds.map((id) => parseIdValues(id, targetPks)[targetIdInfo.fieldName]);
2163
- await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(and(eq(fkCol, parsedParentId), sql`${targetIdCol} NOT IN (${sql.join(parsedTargetIds)})`));
2164
- await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: parsedParentId }).where(inArray(targetIdCol, parsedTargetIds));
2165
- } else await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(eq(fkCol, parsedParentId));
2308
+ await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(and(eq(fkCol, parentKeyValue), sql`${targetIdCol} NOT IN (${sql.join(parsedTargetIds)})`));
2309
+ await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: parentKeyValue }).where(inArray(targetIdCol, parsedTargetIds));
2310
+ } else await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(eq(fkCol, parentKeyValue));
2166
2311
  } else logger.warn(`Many relation '${key}' in collection '${collection.slug}' lacks write configuration and will be skipped during save.`);
2167
2312
  }
2168
2313
  }
@@ -2177,8 +2322,7 @@ var RelationService = class {
2177
2322
  const targetTable = getTableForCollection(targetCollection, this.registry);
2178
2323
  const targetPks = requirePrimaryKeys(targetCollection, this.registry);
2179
2324
  const targetIdInfo = targetPks[0];
2180
- const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);
2181
- const sourceIdInfo = sourcePks[0];
2325
+ requirePrimaryKeys(sourceCollection, this.registry)[0];
2182
2326
  if (relation.kind === "via") {
2183
2327
  await this.updateInverseJoinPathRelation(tx, sourceCollection, sourceEntityId, targetCollection, relation, newValue);
2184
2328
  continue;
@@ -2200,13 +2344,14 @@ var RelationService = class {
2200
2344
  logger.warn(`Foreign key column '${relation.foreignKeyOnTarget}' not found in target table for relation '${relation.relationName}'`);
2201
2345
  continue;
2202
2346
  }
2203
- const parsedSourceId = parseIdValues(sourceEntityId, sourcePks)[sourceIdInfo.fieldName];
2204
- if (newValue === null || newValue === void 0) await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(eq(foreignKeyColumn, parsedSourceId));
2347
+ const sourceKeyValue = (await this.resolveSourceKeys(sourceCollection, relation, [sourceEntityId], tx)).keyByParentId.get(String(sourceEntityId));
2348
+ if (sourceKeyValue === void 0) throw new Error(`Cannot write relation '${relation.relationName}' on '${sourceCollection.slug}': row '${sourceEntityId}' has no value in \`sourceKey: "${sourceKeyField(relation, sourceCollection, this.registry)}"\`, so there is nothing for the related row to point at.`);
2349
+ if (newValue === null || newValue === void 0) await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(eq(foreignKeyColumn, sourceKeyValue));
2205
2350
  else {
2206
2351
  const parsedNewTargetId = parseIdValues(newValue, targetPks)[targetIdInfo.fieldName];
2207
2352
  const targetIdField = targetTable[targetIdInfo.fieldName];
2208
- await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(eq(foreignKeyColumn, parsedSourceId));
2209
- await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: parsedSourceId }).where(eq(targetIdField, parsedNewTargetId));
2353
+ await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: null }).where(eq(foreignKeyColumn, sourceKeyValue));
2354
+ await tx.update(targetTable).set({ [relation.foreignKeyOnTarget]: sourceKeyValue }).where(eq(targetIdField, parsedNewTargetId));
2210
2355
  }
2211
2356
  } catch (e) {
2212
2357
  logger.warn(`Failed to update inverse relation '${relation.relationName}'`, { error: e });
@@ -3797,10 +3942,11 @@ var PersistService = class {
3797
3942
  } else {
3798
3943
  const targetColumnName = this.resolveParentForeignKeyColumn(hop);
3799
3944
  if (targetColumnName) {
3800
- const parsedParentId = parentIdForWrite();
3945
+ const parentKeyValue = hasForeignKeyOnTarget(hop.relation) ? await this.relationService.parentKeyValue(hop.parentCollection, hop.relation, hop.parentId) : parentIdForWrite();
3946
+ if (parentKeyValue === void 0) throw ApiError.badRequest(`Cannot create under "${collectionPath}": the parent row has no value in \`sourceKey\`, so the new row has nothing to point at.`);
3801
3947
  const existingValue = effectiveValues[targetColumnName];
3802
- if (existingValue !== void 0 && existingValue !== null && existingValue !== parsedParentId) logger.warn(`Overriding provided value '${existingValue}' for FK '${targetColumnName}' with path parent id '${parsedParentId}'.`);
3803
- effectiveValues[targetColumnName] = parsedParentId;
3948
+ if (existingValue !== void 0 && existingValue !== null && existingValue !== parentKeyValue) logger.warn(`Overriding provided value '${existingValue}' for FK '${targetColumnName}' with path parent key '${parentKeyValue}'.`);
3949
+ effectiveValues[targetColumnName] = parentKeyValue;
3804
3950
  }
3805
3951
  }
3806
3952
  }
@@ -6152,8 +6298,9 @@ var generateSchema = async (collections, stripPolicies = false) => {
6152
6298
  if (!emittedRelationNames.has(deduplicationKey)) {
6153
6299
  const otherTableVar = getTableVarName(getTableName$1(otherCollection));
6154
6300
  const drizzleFieldKey = resolvePropertyKeyForColumn(collection, otherRel.foreignKeyOnTarget);
6301
+ const referencedKey = otherRel.sourceKey ? resolvePropertyKeyForColumn(otherCollection, otherRel.sourceKey) : getPrimaryKeyName(otherCollection);
6155
6302
  const synthKey = `_synth_${otherTableVar}_${drizzleFieldKey}`;
6156
- tableRelations.push(` "${synthKey}": one(${otherTableVar}, {\n fields: [${tableVarName}.${drizzleFieldKey}],\n references: [${otherTableVar}.${getPrimaryKeyName(otherCollection)}],\n relationName: \"${drizzleRelationName}\"\n })`);
6303
+ tableRelations.push(` "${synthKey}": one(${otherTableVar}, {\n fields: [${tableVarName}.${drizzleFieldKey}],\n references: [${otherTableVar}.${referencedKey}],\n relationName: \"${drizzleRelationName}\"\n })`);
6157
6304
  emittedRelationNames.add(deduplicationKey);
6158
6305
  }
6159
6306
  }
@@ -7311,14 +7458,6 @@ function resolveChannelBusSetting(configured) {
7311
7458
  return raw === "memory" ? { type: "memory" } : { type: "postgres" };
7312
7459
  }
7313
7460
  /**
7314
- * @deprecated Use {@link resolveChannelBusSetting}, which also accepts a
7315
- * supplied {@link ChannelBus} instance. Kept as a narrow alias so existing
7316
- * config-only callers keep their exact types.
7317
- */
7318
- function resolveChannelBusConfig(configured) {
7319
- return resolveChannelBusSetting(configured);
7320
- }
7321
- /**
7322
7461
  * Produce the bus a setting asks for.
7323
7462
  *
7324
7463
  * An instance is handed straight back — constructing it was the application's
@@ -8929,7 +9068,7 @@ function createBackupCron(config) {
8929
9068
  enabled: config.enabled ?? true,
8930
9069
  timeoutSeconds: 3600,
8931
9070
  async handler({ log }) {
8932
- const { createDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-service-DLb2drIH.js").then((n) => n.r);
9071
+ const { createDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-service-CD8o_1Sl.js").then((n) => n.r);
8933
9072
  const { destination } = config;
8934
9073
  if (destination.kind !== "local" && !config.storage) throw new Error(`Backup destination is ${destination.kind} but no storage controller was provided. Pass the backend's configured StorageController to createBackupCron({ storage }).`);
8935
9074
  log(`Starting backup of "${dbName}"…`);
@@ -9060,6 +9199,11 @@ function findRelationDefects(collections, registry) {
9060
9199
  problem: `\`foreignKeyOnTarget: "${relation.foreignKeyOnTarget}"\` is not a column on the target table \`${targetTableName}\``,
9061
9200
  fix: `add the column, or set \`foreignKeyOnTarget\` to one of: ${quote(targetColumns)}`
9062
9201
  });
9202
+ if (relation.sourceKey && !sourceColumns.has(relation.sourceKey)) defects.push({
9203
+ ...at,
9204
+ problem: `\`sourceKey: "${relation.sourceKey}"\` is not a column on \`${sourceTableName}\``,
9205
+ fix: targetColumns.has(relation.sourceKey) ? `it is a column on the *target* table \`${targetTableName}\` — \`sourceKey\` names the column on this collection that the target's foreign key points at, so it must be one of: ${quote(sourceColumns)}` : `add the column, or set \`sourceKey\` to one of: ${quote(sourceColumns)}`
9206
+ });
9063
9207
  break;
9064
9208
  case "manyToMany": {
9065
9209
  const { table, sourceColumn, targetColumn } = relation.through;
@@ -9811,21 +9955,6 @@ async function ensureAuthTablesExist(db, collection) {
9811
9955
  }
9812
9956
  //#endregion
9813
9957
  //#region src/auth/services.ts
9814
- function getColumnKey(table, ...keys) {
9815
- if (!table) return void 0;
9816
- for (const key of keys) {
9817
- if (key in table) return key;
9818
- const snake = toSnakeCase(key);
9819
- if (snake in table) return snake;
9820
- const camel = camelCase(key);
9821
- if (camel in table) return camel;
9822
- }
9823
- }
9824
- function getColumn(table, ...keys) {
9825
- if (!table) return void 0;
9826
- const key = getColumnKey(table, ...keys);
9827
- return key ? table[key] : void 0;
9828
- }
9829
9958
  /**
9830
9959
  * The single definition of what an email address looks like in storage.
9831
9960
  *
@@ -9840,9 +9969,25 @@ function getColumn(table, ...keys) {
9840
9969
  *
9841
9970
  * Whitespace goes too: a trailing space survives the fold and reproduces the
9842
9971
  * problem exactly.
9972
+ *
9973
+ * Re-exported rather than defined here: `@rebasepro/server` and
9974
+ * `@rebasepro/server-mongo` write this column too, and a second copy of this
9975
+ * rule is the defect it exists to prevent.
9843
9976
  */
9844
- function normalizeEmail(email) {
9845
- return typeof email === "string" ? email.trim().toLowerCase() : email;
9977
+ function getColumnKey(table, ...keys) {
9978
+ if (!table) return void 0;
9979
+ for (const key of keys) {
9980
+ if (key in table) return key;
9981
+ const snake = toSnakeCase(key);
9982
+ if (snake in table) return snake;
9983
+ const camel = camelCase(key);
9984
+ if (camel in table) return camel;
9985
+ }
9986
+ }
9987
+ function getColumn(table, ...keys) {
9988
+ if (!table) return void 0;
9989
+ const key = getColumnKey(table, ...keys);
9990
+ return key ? table[key] : void 0;
9846
9991
  }
9847
9992
  /**
9848
9993
  * PostgreSQL implementation of UserRepository.
@@ -11183,6 +11328,31 @@ function humanize(snakeName) {
11183
11328
  return snakeName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
11184
11329
  }
11185
11330
  //#endregion
11331
+ //#region src/schema/introspect-db-types.ts
11332
+ /**
11333
+ * The PostgreSQL type → Rebase property type mapping.
11334
+ *
11335
+ * Split out of `introspect-db-logic` so that the structural analysis can use it
11336
+ * without importing the generator, which imports the analysis. Re-exported from
11337
+ * `introspect-db-logic` so existing callers keep their import path.
11338
+ */
11339
+ /**
11340
+ * Map a PostgreSQL data type to a Rebase property type.
11341
+ */
11342
+ function mapPgType(dataType) {
11343
+ const dt = dataType.toLowerCase();
11344
+ if (dt === "interval") return "string";
11345
+ if (dt === "array" || dt.startsWith("_")) return "array";
11346
+ if (dt.includes("int") || dt.includes("numeric") || dt.includes("decimal") || dt.includes("serial") || dt === "real" || dt === "float4" || dt === "float8" || dt === "double precision" || dt === "money") return "number";
11347
+ if (dt.includes("bool")) return "boolean";
11348
+ if (dt.includes("time") || dt.includes("date")) return "date";
11349
+ if (dt === "json" || dt === "jsonb") return "map";
11350
+ if (dt === "bytea") return "binary";
11351
+ if (dt === "inet" || dt === "cidr" || dt === "macaddr" || dt === "macaddr8") return "string";
11352
+ if (dt === "uuid") return "string";
11353
+ return "string";
11354
+ }
11355
+ //#endregion
11186
11356
  //#region src/schema/introspect-db-logic.ts
11187
11357
  var IRREGULAR_SINGULARS = {
11188
11358
  people: "person",
@@ -11287,22 +11457,6 @@ function getIconForTable(tableName) {
11287
11457
  if (table.includes("comment") || table.includes("review") || table.includes("feedback")) return "MessageCircle";
11288
11458
  return "Database";
11289
11459
  }
11290
- /**
11291
- * Map a PostgreSQL data type to a Rebase property type.
11292
- */
11293
- function mapPgType(dataType) {
11294
- const dt = dataType.toLowerCase();
11295
- if (dt === "interval") return "string";
11296
- if (dt === "array" || dt.startsWith("_")) return "array";
11297
- if (dt.includes("int") || dt.includes("numeric") || dt.includes("decimal") || dt.includes("serial") || dt === "real" || dt === "float4" || dt === "float8" || dt === "double precision" || dt === "money") return "number";
11298
- if (dt.includes("bool")) return "boolean";
11299
- if (dt.includes("time") || dt.includes("date")) return "date";
11300
- if (dt === "json" || dt === "jsonb") return "map";
11301
- if (dt === "bytea") return "binary";
11302
- if (dt === "inet" || dt === "cidr" || dt === "macaddr" || dt === "macaddr8") return "string";
11303
- if (dt === "uuid") return "string";
11304
- return "string";
11305
- }
11306
11460
  function buildEnumMap(enumValues) {
11307
11461
  const enumMap = /* @__PURE__ */ new Map();
11308
11462
  for (const ev of enumValues) {
@@ -11322,6 +11476,20 @@ function buildTablesMap(tables, columns, pks, fks) {
11322
11476
  });
11323
11477
  return tablesMap;
11324
11478
  }
11479
+ /**
11480
+ * Join tables, identified by column name.
11481
+ *
11482
+ * Superseded for the CLI by `classifyTables` in `./introspect-db-structure`,
11483
+ * which asks the database instead: two single-column keys, unique together, no
11484
+ * payload column, nothing referencing the table. This rule folds away
11485
+ * `northwind.order_details` — which has the key shape and carries unit price,
11486
+ * quantity and discount — because it recognises `id`, `created_at` and
11487
+ * `updated_at` by name and calls everything else a foreign key.
11488
+ *
11489
+ * Still used by `./introspect-runtime`, which builds collections in memory from
11490
+ * a narrower set of catalog queries and has no unique-constraint or row-count
11491
+ * data to reason with.
11492
+ */
11325
11493
  function identifyJoinTables(tablesMap) {
11326
11494
  const joinTables = /* @__PURE__ */ new Set();
11327
11495
  for (const [tableName, meta] of tablesMap.entries()) if (meta.fks.length === 2) {
@@ -12057,7 +12225,7 @@ function createPostgresBootstrapper(pgConfig) {
12057
12225
  */
12058
12226
  async ensureCollectionSchema(collections, driverResult, log) {
12059
12227
  const internals = driverResult.internals;
12060
- const { ensureCollectionTables } = await import("./ensure-collection-tables-CT6xHB1d.js");
12228
+ const { ensureCollectionTables } = await import("./ensure-collection-tables-Da2oGkX2.js");
12061
12229
  const plan = await ensureCollectionTables({ async query(text) {
12062
12230
  const result = await internals.db.execute(sql.raw(text));
12063
12231
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
@@ -12082,7 +12250,7 @@ function createPostgresBootstrapper(pgConfig) {
12082
12250
  */
12083
12251
  async ensureCollectionPolicies(collections, driverResult, log) {
12084
12252
  const internals = driverResult.internals;
12085
- const { ensureCollectionPolicies } = await import("./ensure-collection-policies-Dv21KDMJ.js");
12253
+ const { ensureCollectionPolicies } = await import("./ensure-collection-policies-BrUVgjz3.js");
12086
12254
  const outcome = await ensureCollectionPolicies({ async query(text) {
12087
12255
  const result = await internals.db.execute(sql.raw(text));
12088
12256
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
@@ -12136,6 +12304,6 @@ function createPostgresAdapter(pgConfig) {
12136
12304
  };
12137
12305
  }
12138
12306
  //#endregion
12139
- export { AuthenticatedPostgresBackendDriver, BackupToolError, BranchService, CHANNEL_BUS_NOTIFY_CHANNEL, DEFAULT_BATCH_WINDOW_MS, DatabasePoolManager, DrizzleConditionBuilder, MemoryChannelBus, PG_NOTIFY_MAX_PAYLOAD_BYTES, PostgresBackendDriver, PostgresChannelBus, PostgresCollectionRegistry, PostgresConditionBuilder, PostgresRealtimeProvider, RealtimeService, appConfig, applyGlobals, backupCronConfigFromEnv, buildBackupFilename, buildPgDumpArgs, buildPgDumpallGlobalsArgs, buildPgRestoreArgs, buildPgRestoreListArgs, checkToolServerCompatibility, configureUnknownFilterFields, createAuthSchema, createBackupCron, createChannelBus, createDirectDatabaseConnection, createDump, createPostgresAdapter, createPostgresBootstrapper, createPostgresDatabaseConnection, createPostgresWebSocket, createReadReplicaConnection, detectToolMajor, ensureDatabaseExists, frameByteLength, generateSchema, getServerVersionMajor, getUnknownFilterFieldsMode, globalsFileForDump, guardPoolAgainstDirtyRelease, isChannelBusInstance, joinStorageKey, listBackups, magicLinkTokens, magicLinkTokensRelations, mfaChallenges, mfaChallengesRelations, mfaFactors, mfaFactorsRelations, parseBackupDestination, parseBackupTimestamp, parseChannelBusFrame, parseChannelBusPayload, parseDbNameFromUrl, parsePgToolMajor, passwordResetTokens, passwordResetTokensRelations, pinSearchPath, preflight, pruneBackups, recoveryCodes, recoveryCodesRelations, refreshTokens, refreshTokensRelations, resolveChannelBusConfig, resolveChannelBusSetting, resolveConnectionString, resolveDriftCheckName, resolvePgBinary, restoreDump, selectBackupsToPrune, serverVersionNumToMajor, splitGlobalsStatements, uploadBackup, userIdentities, userIdentitiesRelations, users, usersRelations, usersSchema, validateDump, withDatabaseName };
12307
+ export { AuthenticatedPostgresBackendDriver, BackupToolError, BranchService, CHANNEL_BUS_NOTIFY_CHANNEL, DEFAULT_BATCH_WINDOW_MS, DatabasePoolManager, DrizzleConditionBuilder, MemoryChannelBus, PG_NOTIFY_MAX_PAYLOAD_BYTES, PostgresBackendDriver, PostgresChannelBus, PostgresCollectionRegistry, PostgresConditionBuilder, PostgresRealtimeProvider, RealtimeService, appConfig, applyGlobals, backupCronConfigFromEnv, buildBackupFilename, buildPgDumpArgs, buildPgDumpallGlobalsArgs, buildPgRestoreArgs, buildPgRestoreListArgs, buildRowSecurityPgOptions, checkToolServerCompatibility, configureUnknownFilterFields, createAuthSchema, createBackupCron, createChannelBus, createDirectDatabaseConnection, createDump, createPostgresAdapter, createPostgresBootstrapper, createPostgresDatabaseConnection, createPostgresWebSocket, createReadReplicaConnection, detectToolMajor, diagnoseRowSecurityDumpFailure, ensureDatabaseExists, frameByteLength, generateSchema, getServerVersionMajor, getUnknownFilterFieldsMode, globalsFileForDump, guardPoolAgainstDirtyRelease, isChannelBusInstance, joinStorageKey, listBackups, magicLinkTokens, magicLinkTokensRelations, mfaChallenges, mfaChallengesRelations, mfaFactors, mfaFactorsRelations, parseBackupDestination, parseBackupTimestamp, parseChannelBusFrame, parseChannelBusPayload, parseDbNameFromUrl, parsePgToolMajor, passwordResetTokens, passwordResetTokensRelations, pinSearchPath, preflight, pruneBackups, recoveryCodes, recoveryCodesRelations, refreshTokens, refreshTokensRelations, resolveChannelBusSetting, resolveConnectionString, resolveDriftCheckName, resolvePgBinary, restoreDump, selectBackupsToPrune, serverVersionNumToMajor, splitGlobalsStatements, uploadBackup, userIdentities, userIdentitiesRelations, users, usersRelations, usersSchema, validateDump, withDatabaseName };
12140
12308
 
12141
12309
  //# sourceMappingURL=index.es.js.map