@rebasepro/server-postgres 0.11.1-canary.gfd39654 → 0.12.1-canary.gdfba2a1

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 (44) hide show
  1. package/dist/PostgresBootstrapper.d.ts +8 -0
  2. package/dist/auth/services.d.ts +16 -0
  3. package/dist/backup-service-DH9kPg-E.js +8866 -0
  4. package/dist/backup-service-DH9kPg-E.js.map +1 -0
  5. package/dist/collections/buildRegistry.d.ts +1 -1
  6. package/dist/connection-B5Wndbr1.js +196 -0
  7. package/dist/connection-B5Wndbr1.js.map +1 -0
  8. package/dist/{ensure-collection-tables-DGMYK0fr.js → ensure-collection-tables-CIHSH962.js} +5 -5
  9. package/dist/ensure-collection-tables-CIHSH962.js.map +1 -0
  10. package/dist/history/HistoryService.d.ts +9 -29
  11. package/dist/index.es.js +698 -9604
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/schema/auth-schema.d.ts +83 -144
  14. package/dist/schema/dynamic-tables.d.ts +1 -1
  15. package/dist/schema/introspect-runtime.d.ts +1 -1
  16. package/dist/services/FetchService.d.ts +36 -1
  17. package/dist/services/row-pipeline.d.ts +3 -1
  18. package/dist/{src-3VmUJ8Xn.js → src-DihrDFuP.js} +354 -165
  19. package/dist/src-DihrDFuP.js.map +1 -0
  20. package/dist/{src-D5xBTl32.js → src-DoU9yPqq.js} +79 -189
  21. package/dist/src-DoU9yPqq.js.map +1 -0
  22. package/dist/utils/drizzle-conditions.d.ts +157 -3
  23. package/dist/utils/pg-error-utils.d.ts +6 -3
  24. package/dist/websocket-BKcGvILX.js +528 -0
  25. package/dist/websocket-BKcGvILX.js.map +1 -0
  26. package/package.json +14 -14
  27. package/src/PostgresBootstrapper.ts +23 -6
  28. package/src/auth/ensure-tables.ts +164 -9
  29. package/src/auth/services.ts +21 -2
  30. package/src/collections/buildRegistry.ts +1 -1
  31. package/src/history/HistoryService.ts +13 -31
  32. package/src/schema/auth-schema.ts +30 -19
  33. package/src/schema/dynamic-tables.ts +1 -1
  34. package/src/schema/generate-drizzle-schema-logic.ts +17 -5
  35. package/src/schema/generate-postgres-ddl-logic.ts +7 -3
  36. package/src/schema/introspect-runtime.ts +1 -1
  37. package/src/services/FetchService.ts +79 -11
  38. package/src/services/row-pipeline.ts +3 -1
  39. package/src/utils/drizzle-conditions.ts +509 -45
  40. package/src/utils/pg-error-utils.ts +52 -3
  41. package/dist/chunk-DSJWtz9O.js +0 -40
  42. package/dist/ensure-collection-tables-DGMYK0fr.js.map +0 -1
  43. package/dist/src-3VmUJ8Xn.js.map +0 -1
  44. package/dist/src-D5xBTl32.js.map +0 -1
@@ -1,8 +1,19 @@
1
- import { pgSchema, pgTable, varchar, uuid, timestamp, boolean, jsonb, text, unique, index } from "drizzle-orm/pg-core";
1
+ import { pgSchema, pgTable, uuid, timestamp, boolean, jsonb, text, unique, index } from "drizzle-orm/pg-core";
2
2
  import { relations } from "drizzle-orm";
3
3
 
4
4
  /**
5
5
  * Factory function to dynamically create the auth tables bound to the specified schema names.
6
+ *
7
+ * This module builds queries; it does not create tables. `ensureAuthTablesExist`
8
+ * owns the DDL, which makes everything here a *claim* about a database it cannot
9
+ * enforce — and the claims drifted. Every column below was declared
10
+ * `varchar(n)` while the DDL created it as `TEXT`: `user_agent` as varchar(500),
11
+ * `ip_address` as varchar(45), `secret_encrypted` as varchar(500), every
12
+ * `token_hash` as varchar(255). None of it was true of any database this
13
+ * framework ever provisioned. Harmless at runtime — drizzle does not enforce a
14
+ * length client-side, so the widths only ever misled the next reader — but a
15
+ * schema module that describes columns that do not exist is worse than no
16
+ * schema module. They are `text` here now because they are TEXT there.
6
17
  */
7
18
  export function createAuthSchema(usersSchemaName = "rebase") {
8
19
  const usersSchema = usersSchemaName === "public" ? null : pgSchema(usersSchemaName);
@@ -15,12 +26,12 @@ export function createAuthSchema(usersSchemaName = "rebase") {
15
26
  */
16
27
  const users = usersTableCreator("users", {
17
28
  id: uuid("id").defaultRandom().primaryKey(),
18
- email: varchar("email", { length: 255 }).notNull().unique(),
19
- passwordHash: varchar("password_hash", { length: 255 }), // NULL for OAuth-only users
20
- displayName: varchar("display_name", { length: 255 }),
21
- photoUrl: varchar("photo_url", { length: 500 }),
29
+ email: text("email").notNull().unique(),
30
+ passwordHash: text("password_hash"), // NULL for OAuth-only users
31
+ displayName: text("display_name"),
32
+ photoUrl: text("photo_url"),
22
33
  emailVerified: boolean("email_verified").default(false).notNull(),
23
- emailVerificationToken: varchar("email_verification_token", { length: 255 }),
34
+ emailVerificationToken: text("email_verification_token"),
24
35
  emailVerificationSentAt: timestamp("email_verification_sent_at"),
25
36
  isAnonymous: boolean("is_anonymous").default(false).notNull(),
26
37
  roles: text("roles").array().default([]).notNull(),
@@ -65,7 +76,7 @@ export function createAuthSchema(usersSchemaName = "rebase") {
65
76
  id: uuid("id").defaultRandom().primaryKey(),
66
77
  uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
67
78
  sessionId: uuid("session_id").defaultRandom().notNull(),
68
- tokenHash: varchar("token_hash", { length: 255 }).notNull().unique(),
79
+ tokenHash: text("token_hash").notNull().unique(),
69
80
  expiresAt: timestamp("expires_at").notNull(),
70
81
  revoked: boolean("revoked").default(false).notNull(),
71
82
  rotatedAt: timestamp("rotated_at"),
@@ -76,8 +87,8 @@ export function createAuthSchema(usersSchemaName = "rebase") {
76
87
  * that rotates immediately after it.
77
88
  */
78
89
  sessionStartedAt: timestamp("session_started_at").defaultNow().notNull(),
79
- userAgent: varchar("user_agent", { length: 500 }),
80
- ipAddress: varchar("ip_address", { length: 45 }),
90
+ userAgent: text("user_agent"),
91
+ ipAddress: text("ip_address"),
81
92
  createdAt: timestamp("created_at").defaultNow().notNull()
82
93
  }, (table) => ({
83
94
  sessionIdx: index("idx_refresh_tokens_session").on(table.sessionId)
@@ -89,7 +100,7 @@ export function createAuthSchema(usersSchemaName = "rebase") {
89
100
  const passwordResetTokens = tableCreator("password_reset_tokens", {
90
101
  id: uuid("id").defaultRandom().primaryKey(),
91
102
  uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
92
- tokenHash: varchar("token_hash", { length: 255 }).notNull().unique(),
103
+ tokenHash: text("token_hash").notNull().unique(),
93
104
  expiresAt: timestamp("expires_at").notNull(),
94
105
  usedAt: timestamp("used_at"),
95
106
  createdAt: timestamp("created_at").defaultNow().notNull()
@@ -99,7 +110,7 @@ export function createAuthSchema(usersSchemaName = "rebase") {
99
110
  * App config - key/value store for custom settings
100
111
  */
101
112
  const appConfig = tableCreator("app_config", {
102
- key: varchar("key", { length: 100 }).primaryKey(),
113
+ key: text("key").primaryKey(),
103
114
  value: jsonb("value").notNull(),
104
115
  updatedAt: timestamp("updated_at").defaultNow().notNull()
105
116
  });
@@ -110,8 +121,8 @@ export function createAuthSchema(usersSchemaName = "rebase") {
110
121
  const userIdentities = tableCreator("user_identities", {
111
122
  id: uuid("id").defaultRandom().primaryKey(),
112
123
  uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
113
- provider: varchar("provider", { length: 50 }).notNull(), // e.g. 'google', 'linkedin'
114
- providerId: varchar("provider_id", { length: 255 }).notNull(),
124
+ provider: text("provider").notNull(), // e.g. 'google', 'linkedin'
125
+ providerId: text("provider_id").notNull(),
115
126
  profileData: jsonb("profile_data"),
116
127
  createdAt: timestamp("created_at").defaultNow().notNull(),
117
128
  updatedAt: timestamp("updated_at").defaultNow().notNull()
@@ -125,9 +136,9 @@ export function createAuthSchema(usersSchemaName = "rebase") {
125
136
  const mfaFactors = tableCreator("mfa_factors", {
126
137
  id: uuid("id").defaultRandom().primaryKey(),
127
138
  uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
128
- factorType: varchar("factor_type", { length: 20 }).notNull(), // 'totp'
129
- secretEncrypted: varchar("secret_encrypted", { length: 500 }).notNull(),
130
- friendlyName: varchar("friendly_name", { length: 255 }),
139
+ factorType: text("factor_type").notNull(), // 'totp'
140
+ secretEncrypted: text("secret_encrypted").notNull(),
141
+ friendlyName: text("friendly_name"),
131
142
  verified: boolean("verified").default(false).notNull(),
132
143
  createdAt: timestamp("created_at").defaultNow().notNull(),
133
144
  updatedAt: timestamp("updated_at").defaultNow().notNull()
@@ -141,7 +152,7 @@ export function createAuthSchema(usersSchemaName = "rebase") {
141
152
  factorId: uuid("factor_id").notNull().references(() => mfaFactors.id, { onDelete: "cascade" }),
142
153
  createdAt: timestamp("created_at").defaultNow().notNull(),
143
154
  verifiedAt: timestamp("verified_at"),
144
- ipAddress: varchar("ip_address", { length: 45 }),
155
+ ipAddress: text("ip_address"),
145
156
  expiresAt: timestamp("expires_at").notNull()
146
157
  });
147
158
 
@@ -151,7 +162,7 @@ export function createAuthSchema(usersSchemaName = "rebase") {
151
162
  const recoveryCodes = tableCreator("recovery_codes", {
152
163
  id: uuid("id").defaultRandom().primaryKey(),
153
164
  uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
154
- codeHash: varchar("code_hash", { length: 255 }).notNull(),
165
+ codeHash: text("code_hash").notNull(),
155
166
  usedAt: timestamp("used_at"),
156
167
  createdAt: timestamp("created_at").defaultNow().notNull()
157
168
  });
@@ -162,7 +173,7 @@ export function createAuthSchema(usersSchemaName = "rebase") {
162
173
  const magicLinkTokens = tableCreator("magic_link_tokens", {
163
174
  id: uuid("id").defaultRandom().primaryKey(),
164
175
  uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
165
- tokenHash: varchar("token_hash", { length: 255 }).notNull().unique(),
176
+ tokenHash: text("token_hash").notNull().unique(),
166
177
  expiresAt: timestamp("expires_at").notNull(),
167
178
  usedAt: timestamp("used_at"),
168
179
  createdAt: timestamp("created_at").defaultNow().notNull()
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Build drizzle tables at runtime from an introspected schema.
3
3
  *
4
- * CMS mode gets its drizzle tables from a generated `schema.generated.ts` that
4
+ * A project with declared collections gets its drizzle tables from a generated `schema.generated.ts` that
5
5
  * the developer commits. BaaS mode has no such file — it points at a database
6
6
  * and serves it — so the equivalent table objects are constructed here from
7
7
  * `information_schema` metadata.
@@ -1,6 +1,6 @@
1
1
  import { CollectionConfig, NumberProperty, Property, ResolvedRelation, RelationProperty, SecurityOperation, SecurityRule, StringProperty, isPostgresCollectionConfig, DateProperty, ArrayProperty, MapProperty, ReferenceProperty, VectorProperty, BinaryProperty, isManyToMany, type ResolvedManyToMany, type ResolvedBelongsTo, type ResolvedForeignKeyOnTarget, hasForeignKeyOnTarget } from "@rebasepro/types";
2
2
  import { getPrimaryKeys } from "../services/collection-helpers";
3
- import { getEnumVarName, getTableName, getTableVarName, resolveCollectionRelations, findRelation, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig } from "@rebasepro/common";
3
+ import { getEnumVarName, getTableName, getTableVarName, resolveCollectionRelations, findRelation, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig, resolveStringColumnLength } from "@rebasepro/common";
4
4
  import { toSnakeCase, getPolicyNamesForRule } from "@rebasepro/utils";
5
5
  import { logger } from "@rebasepro/server";
6
6
  // --- Helper Functions ---
@@ -98,9 +98,13 @@ const getDrizzleColumn = (propName: string, prop: Property, collection: Collecti
98
98
  } else if (stringProp.columnType === "uuid") {
99
99
  columnDefinition = `uuid("${colName}")`;
100
100
  } else if (stringProp.columnType === "char") {
101
- columnDefinition = `char("${colName}")`;
101
+ columnDefinition = `char("${colName}", { length: ${resolveStringColumnLength(stringProp)} })`;
102
102
  } else if (stringProp.columnType === "varchar") {
103
- columnDefinition = `varchar("${colName}")`;
103
+ // The length is not optional decoration: `varchar("col")` with
104
+ // no length is an UNBOUNDED varchar in Postgres, which is what
105
+ // this emitted while the DDL generator emitted VARCHAR(255) for
106
+ // the very same property.
107
+ columnDefinition = `varchar("${colName}", { length: ${resolveStringColumnLength(stringProp)} })`;
104
108
  } else {
105
109
  // `text` is the default, and the only length-unbounded choice.
106
110
  // Ask for `varchar` explicitly if you want the length constraint.
@@ -708,9 +712,17 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
708
712
  emittedRelationNames.add(deduplicationKey);
709
713
 
710
714
  switch (rel.kind) {
711
- case "belongsTo":
712
- tableRelations.push(` "${relationKey}": one(${targetTableVar}, {\n fields: [${tableVarName}.${rel.localKey}],\n references: [${targetTableVar}.${getPrimaryKeyName(target)}],\n relationName: \"${drizzleRelationName}\"\n })`);
715
+ case "belongsTo": {
716
+ // `localKey` is a COLUMN name; the generated Drizzle
717
+ // object is keyed by PROPERTY. They differ whenever
718
+ // the property is camelCase — `user_id` is exposed
719
+ // as `userId` — and emitting the column produces a
720
+ // schema that does not compile. The three other
721
+ // emission sites normalise; this one did not.
722
+ const localFieldKey = resolvePropertyKeyForColumn(collection, rel.localKey);
723
+ tableRelations.push(` "${relationKey}": one(${targetTableVar}, {\n fields: [${tableVarName}.${localFieldKey}],\n references: [${targetTableVar}.${getPrimaryKeyName(target)}],\n relationName: \"${drizzleRelationName}\"\n })`);
713
724
  break;
725
+ }
714
726
 
715
727
  case "hasOne":
716
728
  // The foreign key lives on the TARGET table. Drizzle pairs
@@ -1,5 +1,5 @@
1
1
  import { CollectionConfig, NumberProperty, Property, ResolvedRelation, RelationProperty, SecurityOperation, SecurityRule, StringProperty, isPostgresCollectionConfig, DateProperty, ArrayProperty, MapProperty, ReferenceProperty, VectorProperty, BinaryProperty, isManyToMany, type ResolvedManyToMany, type ResolvedBelongsTo } from "@rebasepro/types";
2
- import { getEnumVarName, getTableName, resolveCollectionRelations, findRelation, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, getInjectedSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig } from "@rebasepro/common";
2
+ import { getEnumVarName, getTableName, resolveCollectionRelations, findRelation, securityRuleToConditions, policyToPostgres, getEffectiveSecurityRules, getInjectedSecurityRules, resolveJunctionSpecs, getJunctionSecurityRules, getJunctionCollectionConfig, resolveStringColumnLength } from "@rebasepro/common";
3
3
  import { toSnakeCase, getPolicyNamesForRule } from "@rebasepro/utils";
4
4
 
5
5
  // --- Helper Functions ---
@@ -103,11 +103,15 @@ export const getSqlColumnType = (propName: string, prop: Property, collection: C
103
103
  if (stringProp.isId === "uuid" || stringProp.columnType === "uuid") {
104
104
  return "UUID";
105
105
  }
106
+ // Width comes from `validation.max` when the property states one.
107
+ // It used to be a hardcoded 255 here and *absent* on the Drizzle
108
+ // path, so the same property produced a bounded column down one
109
+ // generator and an unbounded one down the other.
106
110
  if (stringProp.columnType === "char") {
107
- return "CHAR(255)";
111
+ return `CHAR(${resolveStringColumnLength(stringProp)})`;
108
112
  }
109
113
  if (stringProp.columnType === "varchar") {
110
- return "VARCHAR(255)";
114
+ return `VARCHAR(${resolveStringColumnLength(stringProp)})`;
111
115
  }
112
116
  // `text` is the default. The two generators disagreed here before:
113
117
  // this one emitted VARCHAR(255) while the drizzle path emitted a bare
@@ -7,7 +7,7 @@
7
7
  * single config file.
8
8
  *
9
9
  * Distinct from `introspect-db.ts`, which runs the same queries but emits
10
- * TypeScript *source* for a developer to edit and commit (CMS mode). The two
10
+ * TypeScript *source* for a developer to edit and commit (declared collections). The two
11
11
  * share the mapping helpers in `introspect-db-logic.ts` so a table is described
12
12
  * the same way whether it was generated or introspected.
13
13
  */
@@ -3,10 +3,12 @@ import { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
3
3
  import { CollectionConfig, FilterValues, ResolvedRelation, LogicalCondition, isManyToMany } from "@rebasepro/types";
4
4
  import type { VectorSearchParams } from "@rebasepro/types";
5
5
  import { resolveCollectionRelations, findRelation, createRelationRef, createRelationRefWithData } from "@rebasepro/common";
6
- import { DrizzleConditionBuilder } from "../utils/drizzle-conditions";
6
+ import { generateForeignKeyName } from "@rebasepro/utils";
7
+ import { DrizzleConditionBuilder, type FilterCompilationOptions } from "../utils/drizzle-conditions";
7
8
  import {
8
9
  getCollectionByPath,
9
10
  getTableForCollection,
11
+ getPrimaryKeys,
10
12
  requirePrimaryKeys,
11
13
  deriveRowAddress,
12
14
  parseIdValues,
@@ -45,6 +47,45 @@ export class FetchService {
45
47
  return query?.[tableName] as RelationalQueryBuilder<TablesRelationalConfig, TableRelationalConfig> | undefined;
46
48
  }
47
49
 
50
+ /**
51
+ * The context the condition builder needs to compile a filter key that is
52
+ * not a column name outright.
53
+ *
54
+ * Two such keys. An owning relation's key resolves through the collection's
55
+ * relations to its foreign-key column; a relation whose link lives on the
56
+ * target table or in a junction resolves to a correlated `EXISTS`, which
57
+ * needs the registry to reach that other table and this table's key column
58
+ * to correlate back.
59
+ *
60
+ * Looked up rather than passed: every read path already has the path, only
61
+ * some have the collection, and a path that names no registered collection
62
+ * (a nested/derived one) is not an error here — the builder simply falls
63
+ * back to guessing the default key shapes, and a relation filter it cannot
64
+ * compile stays unresolvable and so fails closed.
65
+ */
66
+ private filterContext(collectionPath: string, table: PgTable<any>): FilterCompilationOptions {
67
+ const collection = this.registry.getCollectionByPath(collectionPath) ?? undefined;
68
+ return {
69
+ collection,
70
+ registry: this.registry,
71
+ sourceIdColumn: collection ? this.resolveIdColumn(collection, table) : undefined
72
+ };
73
+ }
74
+
75
+ /**
76
+ * The table column this collection's rows are keyed by, or `undefined`.
77
+ *
78
+ * `getPrimaryKeys` rather than `requirePrimaryKeys`: a collection with no
79
+ * resolvable key is not an error on the filter path — it only means the
80
+ * relation filters that would correlate on it cannot be compiled, which
81
+ * the builder already handles by failing that field closed.
82
+ */
83
+ private resolveIdColumn(collection: CollectionConfig, table: PgTable<any>): AnyPgColumn | undefined {
84
+ const [idInfo] = getPrimaryKeys(collection, this.registry);
85
+ if (!idInfo) return undefined;
86
+ return table[idInfo.fieldName as keyof typeof table] as AnyPgColumn | undefined;
87
+ }
88
+
48
89
  /**
49
90
  * Build filter conditions from FilterValues
50
91
  * Delegates to DrizzleConditionBuilder.buildFilterConditions
@@ -54,7 +95,9 @@ export class FetchService {
54
95
  table: PgTable<any>,
55
96
  collectionPath: string
56
97
  ): SQL[] {
57
- return DrizzleConditionBuilder.buildFilterConditions(filter, table, collectionPath);
98
+ return DrizzleConditionBuilder.buildFilterConditions(
99
+ filter, table, collectionPath, this.filterContext(collectionPath, table)
100
+ );
58
101
  }
59
102
 
60
103
  // =============================================================
@@ -64,20 +107,45 @@ export class FetchService {
64
107
  /**
65
108
  * Resolves the correct Drizzle column for sorting.
66
109
  * Automatically maps owning relation property keys to their underlying foreign key column.
110
+ *
111
+ * The relation's own `localKey` is the authority for that foreign key, not
112
+ * `<field>_id`. The default local key comes from `generateForeignKeyName`,
113
+ * which snake-cases *and singularises* — `userProfile` → `user_profile_id`,
114
+ * `users` → `user_id` — and an author can override it outright. A wrong
115
+ * guess resolves to nothing, the caller drops the `ORDER BY`, and the rows
116
+ * come back in whatever order Postgres pleases: paging over that repeats
117
+ * and skips rows rather than erroring. The guesses stay, last, for a
118
+ * caller that hands over no collection to resolve against.
67
119
  */
68
120
  private resolveOrderByField(
69
121
  table: PgTable<any>,
70
122
  orderBy: string,
71
123
  collection?: CollectionConfig
72
124
  ): AnyPgColumn | undefined {
73
- let orderByField = table[orderBy as keyof typeof table] as AnyPgColumn;
74
- if (!orderByField && collection) {
75
- const property = collection.properties[orderBy];
76
- if (property && property.type === "relation" && "relation" in property && property.resolvedRelation?.kind === "belongsTo") {
77
- orderByField = table[`${orderBy}_id` as keyof typeof table] as AnyPgColumn;
125
+ const columnAt = (key: string): AnyPgColumn | undefined =>
126
+ (key in table ? table[key as keyof typeof table] as AnyPgColumn : undefined) || undefined;
127
+
128
+ const direct = columnAt(orderBy);
129
+ if (direct) return direct;
130
+
131
+ // Owning relation, resolved: the relation names its own local key.
132
+ if (collection) {
133
+ const relation = resolveCollectionRelations(collection)[orderBy];
134
+ if (relation?.kind === "belongsTo") {
135
+ const foreignKey = columnAt(relation.localKey);
136
+ if (foreignKey) return foreignKey;
78
137
  }
79
138
  }
80
- return orderByField;
139
+
140
+ // No collection in hand — the two shapes an owning relation's key takes
141
+ // by default (e.g. `project` → `project_id`, `userProfile` →
142
+ // `user_profile_id`).
143
+ for (const guess of [`${orderBy}_id`, generateForeignKeyName(orderBy)]) {
144
+ const foreignKey = columnAt(guess);
145
+ if (foreignKey) return foreignKey;
146
+ }
147
+
148
+ return undefined;
81
149
  }
82
150
 
83
151
  /**
@@ -85,7 +153,7 @@ export class FetchService {
85
153
  * Converts collection relations to a Drizzle-compatible `with` object.
86
154
  *
87
155
  * When `include` is provided, only those relations are loaded.
88
- * When `include` is absent, ALL relations are loaded (CMS path).
156
+ * When `include` is absent, ALL relations are loaded (the admin path).
89
157
  *
90
158
  * Automatically detects many-to-many junction tables and nests
91
159
  * the target relation so actual row data is returned.
@@ -311,7 +379,7 @@ export class FetchService {
311
379
  }
312
380
 
313
381
  if (options.logical) {
314
- const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath);
382
+ const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath, this.filterContext(collectionPath, table));
315
383
  if (logicalCondition) allConditions.push(logicalCondition);
316
384
  }
317
385
 
@@ -656,7 +724,7 @@ _distance: vectorMeta.distanceSelect }).from(table).$dynamic()
656
724
  }
657
725
 
658
726
  if (options.logical) {
659
- const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath);
727
+ const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath, this.filterContext(collectionPath, table));
660
728
  if (logicalCondition) allConditions.push(logicalCondition);
661
729
  }
662
730
 
@@ -14,7 +14,9 @@ import { PostgresCollectionRegistry } from "../collections/PostgresCollectionReg
14
14
  *
15
15
  * - `"ref"` — a `{ id, path, __type: "relation" }` reference carrying the
16
16
  * target's values. This is what the admin renders.
17
- * - `"inline"` — the target's own columns, flat. This is what REST serves.
17
+ * - `"inline"` — the target's own columns, flat. This is what REST serves, and
18
+ * — since the in-process SDK reads through the same pipeline — what
19
+ * `rebase.data` / `context.data` serve too. A developer never sees a ref.
18
20
  *
19
21
  * They used to be two functions that happened to agree, and the agreement was
20
22
  * not enforced by anything: the row-identity bug had to be fixed five times