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

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 (36) hide show
  1. package/dist/PostgresBackendDriver.d.ts +1 -1
  2. package/dist/{backup-service-vAKWJkYL.js → backup-service-DLb2drIH.js} +7 -1
  3. package/dist/backup-service-DLb2drIH.js.map +1 -0
  4. package/dist/cli-helpers.d.ts +15 -0
  5. package/dist/{ensure-collection-policies-BjSwj0FM.js → ensure-collection-policies-Dv21KDMJ.js} +2 -2
  6. package/dist/{ensure-collection-policies-BjSwj0FM.js.map → ensure-collection-policies-Dv21KDMJ.js.map} +1 -1
  7. package/dist/{ensure-collection-tables-D6-XhqnQ.js → ensure-collection-tables-CT6xHB1d.js} +72 -12
  8. package/dist/ensure-collection-tables-CT6xHB1d.js.map +1 -0
  9. package/dist/index.es.js +74 -21
  10. package/dist/index.es.js.map +1 -1
  11. package/dist/schema/ensure-collection-tables.d.ts +21 -1
  12. package/dist/schema/generate-postgres-ddl-logic.d.ts +19 -0
  13. package/dist/services/FetchService.d.ts +4 -1
  14. package/dist/services/dataService.d.ts +3 -1
  15. package/dist/services/row-pipeline.d.ts +1 -1
  16. package/dist/{src-C_NHNVW2.js → src-BkdpiQdw.js} +137 -18
  17. package/dist/src-BkdpiQdw.js.map +1 -0
  18. package/dist/{websocket-DB7TbFPT.js → websocket-B2LsrINK.js} +4 -3
  19. package/dist/websocket-B2LsrINK.js.map +1 -0
  20. package/package.json +6 -6
  21. package/src/PostgresAdapter.ts +7 -2
  22. package/src/PostgresBackendDriver.ts +4 -0
  23. package/src/cli-helpers.ts +26 -0
  24. package/src/cli.ts +2 -19
  25. package/src/schema/ensure-collection-tables.ts +81 -10
  26. package/src/schema/generate-postgres-ddl-logic.ts +50 -6
  27. package/src/schema/introspect-db-logic.ts +21 -3
  28. package/src/security/policy-drift.test.ts +11 -3
  29. package/src/services/FetchService.ts +76 -14
  30. package/src/services/dataService.ts +3 -1
  31. package/src/services/row-pipeline.ts +1 -1
  32. package/src/websocket.ts +15 -6
  33. package/dist/backup-service-vAKWJkYL.js.map +0 -1
  34. package/dist/ensure-collection-tables-D6-XhqnQ.js.map +0 -1
  35. package/dist/src-C_NHNVW2.js.map +0 -1
  36. package/dist/websocket-DB7TbFPT.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 camelCase, E as mergeDeep, M as isManyToMany, N as Vector, O as toSnakeCase, S as normalizeToEntityRelation, T as getPolicyNamesForRule, _ 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, j as hasForeignKeyOnTarget, 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-C_NHNVW2.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";
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
- import { t as createPostgresWebSocket } from "./websocket-DB7TbFPT.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-vAKWJkYL.js";
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";
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";
@@ -2512,7 +2512,7 @@ function relationTargetAddress(targetRow, targetCollection, registry) {
2512
2512
  * expects real types. The row's own address is *not* among the columns — it is
2513
2513
  * derived by the consumer from the collection's primary keys.
2514
2514
  */
2515
- function toCmsRow(row, collection, registry) {
2515
+ function toFlatRow(row, collection, registry) {
2516
2516
  const resolvedRelations = resolveCollectionRelations(collection);
2517
2517
  const normalized = normalizeDbValues(row, collection);
2518
2518
  for (const [key, relation] of Object.entries(resolvedRelations)) {
@@ -2709,17 +2709,35 @@ var FetchService = class {
2709
2709
  const columnAt = (key) => (key in table ? table[key] : void 0) || void 0;
2710
2710
  const direct = columnAt(orderBy);
2711
2711
  if (direct) return direct;
2712
- if (collection) {
2713
- const relation = resolveCollectionRelations(collection)[orderBy];
2714
- if (relation?.kind === "belongsTo") {
2715
- const foreignKey = columnAt(relation.localKey);
2716
- if (foreignKey) return foreignKey;
2717
- }
2712
+ const declaredRelation = collection ? resolveCollectionRelations(collection)[orderBy] : void 0;
2713
+ if (declaredRelation?.kind === "belongsTo") {
2714
+ const foreignKey = columnAt(declaredRelation.localKey);
2715
+ if (foreignKey) return foreignKey;
2718
2716
  }
2719
2717
  for (const guess of [`${orderBy}_id`, generateForeignKeyName(orderBy)]) {
2720
2718
  const foreignKey = columnAt(guess);
2721
2719
  if (foreignKey) return foreignKey;
2722
2720
  }
2721
+ const collectionName = collection?.slug ?? collection?.name;
2722
+ const onCollection = collectionName ? ` on collection '${collectionName}'` : "";
2723
+ if (declaredRelation && declaredRelation.kind !== "belongsTo") throw ApiError.badRequest(`Cannot sort by '${orderBy}'${onCollection}: it is a to-many relation (${declaredRelation.kind}), which has no single value per row to order by.`, "ORDER_BY_FIELD_NOT_SORTABLE", {
2724
+ field: orderBy,
2725
+ kind: declaredRelation.kind,
2726
+ ...collectionName && { collection: collectionName }
2727
+ });
2728
+ if (getUnknownFilterFieldsMode() === "warn") {
2729
+ logger.warn(`Sorting by field '${orderBy}'${onCollection}, but it does not exist in the table — the ORDER BY was dropped and these rows are unsorted.`);
2730
+ return;
2731
+ }
2732
+ let validFields = [];
2733
+ try {
2734
+ validFields = Object.keys(getTableColumns(table)).sort();
2735
+ } catch {}
2736
+ throw ApiError.badRequest(`Unknown orderBy field '${orderBy}'${onCollection}` + (validFields.length > 0 ? `. Valid fields: ${validFields.join(", ")}` : ""), "UNKNOWN_ORDER_BY_FIELD", {
2737
+ field: orderBy,
2738
+ ...collectionName && { collection: collectionName },
2739
+ ...validFields.length > 0 && { validFields }
2740
+ });
2723
2741
  }
2724
2742
  /**
2725
2743
  * Build the `with` config for Drizzle's relational query API.
@@ -2933,7 +2951,7 @@ var FetchService = class {
2933
2951
  with: withConfig
2934
2952
  });
2935
2953
  if (!row) return void 0;
2936
- const flatRow = toCmsRow(row, collection, this.registry);
2954
+ const flatRow = toFlatRow(row, collection, this.registry);
2937
2955
  await this.resolveJoinPathRelations(flatRow, collection, collectionPath, parsedId, databaseId);
2938
2956
  return flatRow;
2939
2957
  } catch (e) {
@@ -2988,7 +3006,7 @@ var FetchService = class {
2988
3006
  const hasRelations = withConfig && Object.keys(withConfig).length > 0;
2989
3007
  if (qb && !options.searchString && !hasRelations && !options.vectorSearch) try {
2990
3008
  const queryOpts = this.buildDrizzleQueryOptions(table, idField, idInfo, options, collectionPath, void 0, scopeCondition);
2991
- return (await qb.findMany(queryOpts)).map((row) => toCmsRow(row, collection, this.registry));
3009
+ return (await qb.findMany(queryOpts)).map((row) => toFlatRow(row, collection, this.registry));
2992
3010
  } catch (e) {
2993
3011
  if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
2994
3012
  logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);
@@ -3051,7 +3069,7 @@ var FetchService = class {
3051
3069
  /**
3052
3070
  * Fallback path used when db.query is unavailable.
3053
3071
  *
3054
- * The primary path runs the results through `toCmsRow`, which maps
3072
+ * The primary path runs the results through `toFlatRow`, which maps
3055
3073
  * relations from what drizzle already nested — no query per row. This one
3056
3074
  * has no nesting to read, so it resolves relations itself, in batches.
3057
3075
  *
@@ -3144,6 +3162,10 @@ var FetchService = class {
3144
3162
  const filterConditions = this.buildFilterConditions(options.filter, table, effectivePath);
3145
3163
  if (filterConditions.length > 0) allConditions.push(...filterConditions);
3146
3164
  }
3165
+ if (options.logical) {
3166
+ const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, effectivePath, this.filterContext(effectivePath, table));
3167
+ if (logicalCondition) allConditions.push(logicalCondition);
3168
+ }
3147
3169
  if (allConditions.length > 0) {
3148
3170
  const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(allConditions);
3149
3171
  if (finalCondition) query = query.where(finalCondition);
@@ -5056,9 +5078,10 @@ var PostgresBackendDriver = class PostgresBackendDriver {
5056
5078
  async checkUniqueField(path, name, value, id, collection) {
5057
5079
  return this.dataService.checkUniqueField(path, name, value, id, collection?.databaseId);
5058
5080
  }
5059
- async count({ path, collection, filter, searchString }) {
5081
+ async count({ path, collection, filter, logical, searchString }) {
5060
5082
  return this.dataService.count(path, {
5061
5083
  filter,
5084
+ logical,
5062
5085
  searchString
5063
5086
  });
5064
5087
  }
@@ -8906,7 +8929,7 @@ function createBackupCron(config) {
8906
8929
  enabled: config.enabled ?? true,
8907
8930
  timeoutSeconds: 3600,
8908
8931
  async handler({ log }) {
8909
- const { createDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-service-vAKWJkYL.js").then((n) => n.r);
8932
+ const { createDump, pruneBackups, uploadBackup, validateDump } = await import("./backup-service-DLb2drIH.js").then((n) => n.r);
8910
8933
  const { destination } = config;
8911
8934
  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 }).`);
8912
8935
  log(`Starting backup of "${dbName}"…`);
@@ -11175,6 +11198,36 @@ var IRREGULAR_SINGULARS = {
11175
11198
  criteria: "criterion",
11176
11199
  phenomena: "phenomenon"
11177
11200
  };
11201
+ /**
11202
+ * Plurals in "-ves" whose singular really ends in f/fe, and which of the two.
11203
+ *
11204
+ * A blanket "-ves" -> "-f" rule gets `knives` -> `knif`, and mangles every
11205
+ * ordinary "-ive" noun that happens to be plural along with it: `archives` ->
11206
+ * `archif`, `objectives` -> `objectif`. The set of English words that genuinely
11207
+ * swap f/fe for ves is small and closed, so it is listed rather than guessed —
11208
+ * anything else ending in "ves" drops the trailing 's' like any other plural.
11209
+ *
11210
+ * Matched on the whole word, not on the suffix: `olives` ends in `lives`.
11211
+ */
11212
+ var VES_SINGULAR_ENDINGS = {
11213
+ calves: "f",
11214
+ dwarves: "f",
11215
+ elves: "f",
11216
+ halves: "f",
11217
+ hooves: "f",
11218
+ leaves: "f",
11219
+ loaves: "f",
11220
+ scarves: "f",
11221
+ selves: "f",
11222
+ sheaves: "f",
11223
+ shelves: "f",
11224
+ thieves: "f",
11225
+ wharves: "f",
11226
+ wolves: "f",
11227
+ knives: "fe",
11228
+ lives: "fe",
11229
+ wives: "fe"
11230
+ };
11178
11231
  /** Words ending in 's' that are already singular. */
11179
11232
  var UNCOUNTABLE = /* @__PURE__ */ new Set([
11180
11233
  "status",
@@ -11213,7 +11266,7 @@ function singularize(word) {
11213
11266
  if (UNCOUNTABLE.has(lower)) return word;
11214
11267
  if (lower.endsWith("ices") && lower.length > 5) return word.slice(0, -4) + "ex";
11215
11268
  if (lower.endsWith("ies") && lower.length > 3) return word.slice(0, -3) + "y";
11216
- if (lower.endsWith("ves")) return word.slice(0, -3) + "f";
11269
+ if (VES_SINGULAR_ENDINGS[lower]) return word.slice(0, -3) + VES_SINGULAR_ENDINGS[lower];
11217
11270
  if (lower.endsWith("ches") || lower.endsWith("shes") || lower.endsWith("sses") || lower.endsWith("xes") || lower.endsWith("zes")) return word.slice(0, -2);
11218
11271
  if (lower.endsWith("ses") && !lower.endsWith("sses")) return word.slice(0, -1);
11219
11272
  if (lower.endsWith("s") && !lower.endsWith("ss") && !lower.endsWith("us") && !lower.endsWith("is")) return word.slice(0, -1);
@@ -12004,7 +12057,7 @@ function createPostgresBootstrapper(pgConfig) {
12004
12057
  */
12005
12058
  async ensureCollectionSchema(collections, driverResult, log) {
12006
12059
  const internals = driverResult.internals;
12007
- const { ensureCollectionTables } = await import("./ensure-collection-tables-D6-XhqnQ.js");
12060
+ const { ensureCollectionTables } = await import("./ensure-collection-tables-CT6xHB1d.js");
12008
12061
  const plan = await ensureCollectionTables({ async query(text) {
12009
12062
  const result = await internals.db.execute(sql.raw(text));
12010
12063
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
@@ -12029,7 +12082,7 @@ function createPostgresBootstrapper(pgConfig) {
12029
12082
  */
12030
12083
  async ensureCollectionPolicies(collections, driverResult, log) {
12031
12084
  const internals = driverResult.internals;
12032
- const { ensureCollectionPolicies } = await import("./ensure-collection-policies-BjSwj0FM.js");
12085
+ const { ensureCollectionPolicies } = await import("./ensure-collection-policies-Dv21KDMJ.js");
12033
12086
  const outcome = await ensureCollectionPolicies({ async query(text) {
12034
12087
  const result = await internals.db.execute(sql.raw(text));
12035
12088
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
@@ -12043,7 +12096,7 @@ function createPostgresBootstrapper(pgConfig) {
12043
12096
  },
12044
12097
  mountRoutes(app, basePath, driverResult) {},
12045
12098
  async initializeWebsockets(server, realtimeService, driver, config, adapter) {
12046
- const { createPostgresWebSocket } = await import("./websocket-DB7TbFPT.js").then((n) => n.n);
12099
+ const { createPostgresWebSocket } = await import("./websocket-B2LsrINK.js").then((n) => n.n);
12047
12100
  createPostgresWebSocket(server, realtimeService, driver, config, adapter);
12048
12101
  }
12049
12102
  };
@@ -12069,8 +12122,8 @@ function createPostgresAdapter(pgConfig) {
12069
12122
  async initializeHistory(config, driverResult) {
12070
12123
  if (bootstrapper.initializeHistory) return bootstrapper.initializeHistory(config, driverResult);
12071
12124
  },
12072
- initializeWebsockets(server, realtimeService, driver, config) {
12073
- if (bootstrapper.initializeWebsockets) return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
12125
+ initializeWebsockets(server, realtimeService, driver, config, adapter) {
12126
+ if (bootstrapper.initializeWebsockets) return bootstrapper.initializeWebsockets(server, realtimeService, driver, config, adapter);
12074
12127
  },
12075
12128
  ensureCollectionSchema: bootstrapper.ensureCollectionSchema ? (collections, driverResult, log) => bootstrapper.ensureCollectionSchema(collections, driverResult, log) : void 0,
12076
12129
  ensureCollectionPolicies: bootstrapper.ensureCollectionPolicies ? (collections, driverResult, log) => bootstrapper.ensureCollectionPolicies(collections, driverResult, log) : void 0,