@rebasepro/server-postgres 0.11.0 → 0.11.1-canary.g8caabf3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/server-postgres",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.11.1-canary.g8caabf3",
5
5
  "description": "PostgreSQL data source backend implementation for Rebase with Drizzle ORM",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -47,11 +47,11 @@
47
47
  "execa": "^9.6.1",
48
48
  "pg": "^8.21.0",
49
49
  "ws": "^8.21.0",
50
- "@rebasepro/codegen": "0.11.0",
51
- "@rebasepro/common": "0.11.0",
52
- "@rebasepro/utils": "0.11.0",
53
- "@rebasepro/types": "0.11.0",
54
- "@rebasepro/server": "0.11.0"
50
+ "@rebasepro/common": "0.11.1-canary.g8caabf3",
51
+ "@rebasepro/codegen": "0.11.1-canary.g8caabf3",
52
+ "@rebasepro/utils": "0.11.1-canary.g8caabf3",
53
+ "@rebasepro/server": "0.11.1-canary.g8caabf3",
54
+ "@rebasepro/types": "0.11.1-canary.g8caabf3"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@hono/node-server": "^2.0.11",
@@ -112,10 +112,10 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
112
112
  async initializeDriver(config: unknown): Promise<InitializedDriver> {
113
113
  // config is passed from coordinator, we merge it with our internal pgConfig if needed
114
114
  // Currently config from init.ts is `{ collections, collectionRegistry, mode }`
115
- const { collections, collectionRegistry, mode, baas } = config as {
115
+ const { collections, collectionRegistry, introspectCollections, baas } = config as {
116
116
  collections?: CollectionConfig[];
117
117
  collectionRegistry?: unknown;
118
- mode?: "cms" | "baas";
118
+ introspectCollections?: boolean;
119
119
  baas?: { unprotectedTables?: "exclude" | "serve" };
120
120
  };
121
121
  // Secure by default: a table with no RLS is not served.
@@ -126,13 +126,13 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
126
126
  ? (connection as Record<string, unknown>).$client
127
127
  : connection) as import("pg").Pool;
128
128
 
129
- // ── BaaS mode: derive the schema from the database ───────────────
129
+ // ── No declared collections: derive the schema from the database ──
130
130
  // No collection files and no generated drizzle schema exist, so read
131
131
  // the live database and build both from what is actually there.
132
132
  let introspectedCollections: CollectionConfig[] | undefined;
133
133
  let introspectedTables: Record<string, PgTable> | undefined;
134
134
  let introspectedRelations: Record<string, Relations> | undefined;
135
- if (mode === "baas" && (!collections || collections.length === 0)) {
135
+ if (introspectCollections && (!collections || collections.length === 0)) {
136
136
  const pgSchemaName = pgConfig.introspectionSchema ?? "public";
137
137
  const schema = await introspectSchema(rawClient, pgSchemaName);
138
138
 
@@ -9,7 +9,7 @@ import { assertRelationsResolve } from "./validate-relations";
9
9
  /**
10
10
  * Everything a registry is built from: the collections, and the drizzle schema
11
11
  * they are backed by. In BaaS mode all of it is introspected from the live
12
- * database; in CMS mode it comes from the config and the generated schema.
12
+ * database; when collections are declared it comes from the config and the generated schema.
13
13
  */
14
14
  export interface RegistrySchema {
15
15
  collections?: CollectionConfig[];
@@ -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.
@@ -708,9 +708,17 @@ export const generateSchema = async (collections: CollectionConfig[], stripPolic
708
708
  emittedRelationNames.add(deduplicationKey);
709
709
 
710
710
  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 })`);
711
+ case "belongsTo": {
712
+ // `localKey` is a COLUMN name; the generated Drizzle
713
+ // object is keyed by PROPERTY. They differ whenever
714
+ // the property is camelCase — `user_id` is exposed
715
+ // as `userId` — and emitting the column produces a
716
+ // schema that does not compile. The three other
717
+ // emission sites normalise; this one did not.
718
+ const localFieldKey = resolvePropertyKeyForColumn(collection, rel.localKey);
719
+ tableRelations.push(` "${relationKey}": one(${targetTableVar}, {\n fields: [${tableVarName}.${localFieldKey}],\n references: [${targetTableVar}.${getPrimaryKeyName(target)}],\n relationName: \"${drizzleRelationName}\"\n })`);
713
720
  break;
721
+ }
714
722
 
715
723
  case "hasOne":
716
724
  // The foreign key lives on the TARGET table. Drizzle pairs
@@ -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
  */
@@ -85,7 +85,7 @@ export class FetchService {
85
85
  * Converts collection relations to a Drizzle-compatible `with` object.
86
86
  *
87
87
  * When `include` is provided, only those relations are loaded.
88
- * When `include` is absent, ALL relations are loaded (CMS path).
88
+ * When `include` is absent, ALL relations are loaded (the admin path).
89
89
  *
90
90
  * Automatically detects many-to-many junction tables and nests
91
91
  * the target relation so actual row data is returned.
@@ -99,7 +99,24 @@ export class DrizzleConditionBuilder {
99
99
  // Correlated, not joined: a join through a junction multiplies
100
100
  // the target rows by the number of matching links and silently
101
101
  // breaks `limit`/`offset`.
102
- return sql`EXISTS (SELECT 1 FROM ${junctionTable} WHERE ${targetCol} = ${targetIdColumn} AND ${sourceCol} = ${parentId})`;
102
+ //
103
+ // The junction is aliased and referenced by identifier, never as a
104
+ // Drizzle column. A column object carries no table qualifier of its
105
+ // own — it is rendered against whatever the surrounding builder
106
+ // thinks the current table is — so inside `db.query.findMany`, which
107
+ // aliases the root table, `${sourceCol}` came out qualified with the
108
+ // *target's* alias: `podcast.podcast_id`, a column that does not
109
+ // exist. That aborts the transaction, and the fallback read then
110
+ // fails on the poisoned transaction rather than on anything to do
111
+ // with the relation. Only `targetIdColumn` stays a column object,
112
+ // because that one *must* bind to the outer row to correlate.
113
+ //
114
+ // Aliasing also disambiguates a self-referential many-to-many, where
115
+ // the junction and the target are the same table.
116
+ const junctionAlias = "__rel_m2m";
117
+ const junctionRef = (column: AnyPgColumn) =>
118
+ sql`${sql.identifier(junctionAlias)}.${sql.identifier(column.name)}`;
119
+ return sql`EXISTS (SELECT 1 FROM ${junctionTable} AS ${sql.identifier(junctionAlias)} WHERE ${junctionRef(targetCol)} = ${targetIdColumn} AND ${junctionRef(sourceCol)} = ${parentId})`;
103
120
  }
104
121
 
105
122
  case "hasOne":