@rebasepro/server-postgresql 0.1.2 → 0.2.1

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 (68) hide show
  1. package/LICENSE +22 -6
  2. package/dist/common/src/util/entities.d.ts +2 -2
  3. package/dist/common/src/util/relations.d.ts +1 -1
  4. package/dist/index.es.js +1160 -612
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +1158 -610
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/server-postgresql/src/PostgresAdapter.d.ts +6 -0
  9. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
  10. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +0 -5
  11. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +2 -1
  12. package/dist/server-postgresql/src/auth/services.d.ts +37 -15
  13. package/dist/server-postgresql/src/index.d.ts +1 -0
  14. package/dist/server-postgresql/src/schema/auth-schema.d.ts +43 -856
  15. package/dist/server-postgresql/src/schema/default-collections.d.ts +2 -0
  16. package/dist/server-postgresql/src/schema/doctor.d.ts +10 -1
  17. package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +1 -0
  18. package/dist/server-postgresql/src/services/entity-helpers.d.ts +1 -1
  19. package/dist/server-postgresql/src/services/realtimeService.d.ts +12 -0
  20. package/dist/server-postgresql/src/websocket.d.ts +2 -1
  21. package/dist/types/src/controllers/auth.d.ts +9 -8
  22. package/dist/types/src/controllers/client.d.ts +3 -0
  23. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  24. package/dist/types/src/types/collections.d.ts +67 -2
  25. package/dist/types/src/types/database_adapter.d.ts +94 -0
  26. package/dist/types/src/types/entity_actions.d.ts +7 -1
  27. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  28. package/dist/types/src/types/entity_views.d.ts +36 -1
  29. package/dist/types/src/types/index.d.ts +2 -0
  30. package/dist/types/src/types/plugins.d.ts +1 -1
  31. package/dist/types/src/types/properties.d.ts +24 -5
  32. package/dist/types/src/types/property_config.d.ts +6 -2
  33. package/dist/types/src/types/relations.d.ts +1 -1
  34. package/dist/types/src/types/translations.d.ts +8 -0
  35. package/dist/types/src/users/user.d.ts +5 -0
  36. package/package.json +21 -15
  37. package/src/PostgresAdapter.ts +59 -0
  38. package/src/PostgresBackendDriver.ts +57 -8
  39. package/src/PostgresBootstrapper.ts +35 -15
  40. package/src/auth/ensure-tables.ts +82 -189
  41. package/src/auth/services.ts +421 -170
  42. package/src/cli.ts +44 -13
  43. package/src/data-transformer.ts +78 -8
  44. package/src/history/HistoryService.ts +25 -2
  45. package/src/index.ts +1 -0
  46. package/src/schema/auth-schema.ts +130 -98
  47. package/src/schema/default-collections.ts +68 -0
  48. package/src/schema/doctor-cli.ts +5 -1
  49. package/src/schema/doctor.ts +85 -8
  50. package/src/schema/generate-drizzle-schema-logic.ts +74 -27
  51. package/src/schema/generate-drizzle-schema.ts +13 -3
  52. package/src/schema/introspect-db-inference.ts +5 -5
  53. package/src/schema/introspect-db-logic.ts +9 -2
  54. package/src/schema/introspect-db.ts +14 -3
  55. package/src/services/EntityFetchService.ts +5 -5
  56. package/src/services/RelationService.ts +2 -2
  57. package/src/services/entity-helpers.ts +1 -1
  58. package/src/services/realtimeService.ts +145 -136
  59. package/src/utils/drizzle-conditions.ts +16 -2
  60. package/src/websocket.ts +113 -37
  61. package/test/auth-services.test.ts +163 -74
  62. package/test/data-transformer-hardening.test.ts +57 -0
  63. package/test/data-transformer.test.ts +43 -0
  64. package/test/generate-drizzle-schema.test.ts +7 -5
  65. package/test/introspect-db-utils.test.ts +4 -1
  66. package/test/postgresDataDriver.test.ts +17 -0
  67. package/test/realtimeService.test.ts +7 -7
  68. package/test/websocket.test.ts +139 -0
package/dist/index.es.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Pool, Client } from "pg";
2
2
  import { drizzle } from "drizzle-orm/node-postgres";
3
3
  import { sql, inArray, eq, and, or, ilike, asc, desc, gt, lt, getTableName as getTableName$1, count, relations, isTable } from "drizzle-orm";
4
- import { pgSchema, timestamp, varchar, boolean, uuid, jsonb, primaryKey, unique } from "drizzle-orm/pg-core";
4
+ import { PgVarchar, PgText, PgChar, pgSchema, pgTable, timestamp, jsonb, varchar, boolean, uuid, primaryKey, unique, getTableConfig } from "drizzle-orm/pg-core";
5
5
  import { createHash, randomUUID } from "crypto";
6
6
  import * as fs from "fs";
7
7
  import { promises } from "fs";
@@ -51,6 +51,12 @@ function createPostgresDatabaseConnection(connectionString, schema, poolConfig)
51
51
  connectionString
52
52
  };
53
53
  }
54
+ class Vector {
55
+ value;
56
+ constructor(value) {
57
+ this.value = value;
58
+ }
59
+ }
54
60
  function isPostgresCollection(collection) {
55
61
  return !collection.driver || collection.driver === "postgres";
56
62
  }
@@ -127,16 +133,25 @@ const DEFAULT_ONE_OF_VALUE = "value";
127
133
  const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
128
134
  const snakeCaseRegex = tokenizeRegex;
129
135
  const toSnakeCase = (str) => {
136
+ if (!str || typeof str !== "string") return "";
130
137
  const regExpMatchArray = str.match(snakeCaseRegex);
131
138
  if (!regExpMatchArray) return "";
132
139
  return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
133
140
  };
141
+ function camelCase(str) {
142
+ if (!str) return "";
143
+ if (str.length === 1) return str.toLowerCase();
144
+ const parts = str.split(/[-_ ]+/).filter(Boolean);
145
+ if (parts.length === 0) return "";
146
+ return parts[0].toLowerCase() + // Transform remaining parts to have first letter uppercase
147
+ parts.slice(1).map((part) => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase()).join("");
148
+ }
134
149
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
135
150
  function commonjsRequire(path2) {
136
151
  throw new Error('Could not dynamically require "' + path2 + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
137
152
  }
138
153
  var object_hash = { exports: {} };
139
- (function(module, exports$1) {
154
+ (function(module, exports) {
140
155
  !function(e) {
141
156
  module.exports = e();
142
157
  }(function() {
@@ -1255,14 +1270,56 @@ const buildPropertyCallbacks = (properties) => {
1255
1270
  }
1256
1271
  return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1257
1272
  };
1258
- function sanitizeRelation(relation, sourceCollection) {
1273
+ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1259
1274
  if (!relation.target) {
1260
1275
  throw new Error("Relation is missing a `target` collection.");
1261
1276
  }
1262
- const targetCollection = relation.target();
1277
+ const rawTarget = relation.target;
1278
+ let targetCollection;
1279
+ if (typeof rawTarget === "string") {
1280
+ if (resolveCollection) {
1281
+ targetCollection = resolveCollection(rawTarget);
1282
+ }
1283
+ if (!targetCollection) {
1284
+ targetCollection = {
1285
+ slug: rawTarget,
1286
+ name: rawTarget
1287
+ };
1288
+ }
1289
+ } else if (typeof rawTarget === "function") {
1290
+ const evaluated = rawTarget();
1291
+ if (typeof evaluated === "string") {
1292
+ if (resolveCollection) {
1293
+ targetCollection = resolveCollection(evaluated);
1294
+ }
1295
+ if (!targetCollection) {
1296
+ targetCollection = {
1297
+ slug: evaluated,
1298
+ name: evaluated
1299
+ };
1300
+ }
1301
+ } else {
1302
+ targetCollection = evaluated;
1303
+ }
1304
+ }
1305
+ if (!targetCollection) {
1306
+ throw new Error("Relation is missing a valid `target` collection.");
1307
+ }
1263
1308
  const newRelation = {
1264
1309
  ...relation
1265
1310
  };
1311
+ newRelation.target = () => {
1312
+ if (typeof rawTarget === "string") {
1313
+ return resolveCollection && resolveCollection(rawTarget) || targetCollection;
1314
+ } else if (typeof rawTarget === "function") {
1315
+ const evaluated = rawTarget();
1316
+ if (typeof evaluated === "string") {
1317
+ return resolveCollection && resolveCollection(evaluated) || targetCollection;
1318
+ }
1319
+ return evaluated;
1320
+ }
1321
+ return targetCollection;
1322
+ };
1266
1323
  if (!newRelation.relationName) {
1267
1324
  newRelation.relationName = toSnakeCase(targetCollection.slug);
1268
1325
  }
@@ -1315,6 +1372,17 @@ function sanitizeRelation(relation, sourceCollection) {
1315
1372
  break;
1316
1373
  }
1317
1374
  }
1375
+ if (!isManyToManyInverse && targetCollection.properties) {
1376
+ for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
1377
+ if (prop.type !== "relation") continue;
1378
+ const relProp = prop;
1379
+ const relName = relProp.relationName || propKey;
1380
+ if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
1381
+ isManyToManyInverse = true;
1382
+ break;
1383
+ }
1384
+ }
1385
+ }
1318
1386
  } catch (e) {
1319
1387
  }
1320
1388
  }
@@ -1437,7 +1505,7 @@ function findRelation(resolvedRelations, key) {
1437
1505
  return void 0;
1438
1506
  }
1439
1507
  var logic = { exports: {} };
1440
- (function(module, exports$1) {
1508
+ (function(module, exports) {
1441
1509
  (function(root, factory) {
1442
1510
  {
1443
1511
  module.exports = factory();
@@ -2194,7 +2262,7 @@ function createSupportedComparatorMap({ areArrayBuffersEqual: areArrayBuffersEqu
2194
2262
  "[object Uint32Array]": areTypedArraysEqual2
2195
2263
  };
2196
2264
  }
2197
- const deepEqual = createCustomEqual();
2265
+ const deepEqual$1 = createCustomEqual();
2198
2266
  createCustomEqual({ strict: true });
2199
2267
  createCustomEqual({ circular: true });
2200
2268
  createCustomEqual({
@@ -2263,10 +2331,16 @@ class CollectionRegistry {
2263
2331
  */
2264
2332
  registerMultiple(collections) {
2265
2333
  const rawSnapshot = collections.map((c) => removeFunctions(c));
2266
- if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
2334
+ if (this.lastRawInputSnapshot && deepEqual$1(this.lastRawInputSnapshot, rawSnapshot)) {
2267
2335
  return false;
2268
2336
  }
2269
2337
  this.reset();
2338
+ collections.forEach((c) => {
2339
+ if (c.slug) {
2340
+ this.collectionsBySlug.set(c.slug, c);
2341
+ }
2342
+ this.collectionsByTableName.set(getTableName(c), c);
2343
+ });
2270
2344
  const normalizedCollections = collections.map((c) => this.normalizeCollection({
2271
2345
  ...c
2272
2346
  }));
@@ -2345,7 +2419,7 @@ class CollectionRegistry {
2345
2419
  if (getDataSourceCapabilities(result.driver).supportsRelations) {
2346
2420
  mergedRelations = mergedRelationsRaw.map((r) => {
2347
2421
  try {
2348
- return sanitizeRelation(r, result);
2422
+ return sanitizeRelation(r, result, (slug) => this.get(slug));
2349
2423
  } catch {
2350
2424
  return r;
2351
2425
  }
@@ -2784,8 +2858,14 @@ class DrizzleConditionBuilder {
2784
2858
  static buildSingleFilterCondition(column, op, value) {
2785
2859
  switch (op) {
2786
2860
  case "==":
2861
+ if (value === null || value === void 0) {
2862
+ return sql`${column} IS NULL`;
2863
+ }
2787
2864
  return eq(column, value);
2788
2865
  case "!=":
2866
+ if (value === null || value === void 0) {
2867
+ return sql`${column} IS NOT NULL`;
2868
+ }
2789
2869
  return sql`${column} != ${value}`;
2790
2870
  case ">":
2791
2871
  return sql`${column} > ${value}`;
@@ -3116,7 +3196,10 @@ class DrizzleConditionBuilder {
3116
3196
  if (p.type === "string" && !p.enum && p.isId !== "uuid") {
3117
3197
  const fieldColumn = table[key];
3118
3198
  if (fieldColumn) {
3119
- searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
3199
+ const supportsILike = fieldColumn instanceof PgVarchar || fieldColumn instanceof PgText || fieldColumn instanceof PgChar || fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn);
3200
+ if (supportsILike) {
3201
+ searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
3202
+ }
3120
3203
  }
3121
3204
  }
3122
3205
  }
@@ -3589,6 +3672,31 @@ function serializePropertyToServer(value, property) {
3589
3672
  return result;
3590
3673
  }
3591
3674
  return value;
3675
+ case "vector": {
3676
+ if (value instanceof Vector) {
3677
+ return value.value;
3678
+ }
3679
+ if (value && typeof value === "object" && "value" in value && Array.isArray(value.value)) {
3680
+ return value.value.map(Number);
3681
+ }
3682
+ if (Array.isArray(value)) {
3683
+ return value.map(Number);
3684
+ }
3685
+ return value;
3686
+ }
3687
+ case "binary":
3688
+ if (typeof value === "string") {
3689
+ if (value.startsWith("data:application/octet-stream;base64,")) {
3690
+ const base64Data = value.split(",")[1];
3691
+ if (base64Data) {
3692
+ return Buffer.from(base64Data, "base64");
3693
+ }
3694
+ }
3695
+ }
3696
+ if (Buffer.isBuffer(value)) {
3697
+ return value;
3698
+ }
3699
+ return value;
3592
3700
  case "string":
3593
3701
  if (typeof value === "string") {
3594
3702
  if (value.startsWith("data:application/octet-stream;base64,")) {
@@ -3733,6 +3841,21 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3733
3841
  return value;
3734
3842
  }
3735
3843
  switch (property.type) {
3844
+ case "binary": {
3845
+ let buf = null;
3846
+ if (Buffer.isBuffer(value)) {
3847
+ buf = value;
3848
+ } else if (typeof value === "object" && value !== null) {
3849
+ const rawVal = value;
3850
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
3851
+ buf = Buffer.from(rawVal.data);
3852
+ }
3853
+ }
3854
+ if (buf) {
3855
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
3856
+ }
3857
+ return value;
3858
+ }
3736
3859
  case "string": {
3737
3860
  if (typeof value === "string") return value;
3738
3861
  let isBuffer = false;
@@ -3740,9 +3863,12 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3740
3863
  if (Buffer.isBuffer(value)) {
3741
3864
  isBuffer = true;
3742
3865
  buf = value;
3743
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3744
- isBuffer = true;
3745
- buf = Buffer.from(value.data);
3866
+ } else if (typeof value === "object" && value !== null) {
3867
+ const rawVal = value;
3868
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
3869
+ isBuffer = true;
3870
+ buf = Buffer.from(rawVal.data);
3871
+ }
3746
3872
  }
3747
3873
  if (isBuffer && buf) {
3748
3874
  let isPrintable = true;
@@ -3829,6 +3955,25 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3829
3955
  return isNaN(parsed) ? null : parsed;
3830
3956
  }
3831
3957
  return value;
3958
+ case "vector": {
3959
+ let nums = [];
3960
+ if (typeof value === "string") {
3961
+ nums = value.slice(1, -1).split(",").map(Number);
3962
+ } else if (Array.isArray(value)) {
3963
+ nums = value.map(Number);
3964
+ } else if (value instanceof Vector) {
3965
+ nums = value.value;
3966
+ } else if (typeof value === "object" && value !== null && "value" in value) {
3967
+ const valObj = value;
3968
+ if (Array.isArray(valObj.value)) {
3969
+ nums = valObj.value.map(Number);
3970
+ }
3971
+ }
3972
+ return {
3973
+ __type: "Vector",
3974
+ value: nums
3975
+ };
3976
+ }
3832
3977
  case "date": {
3833
3978
  let date;
3834
3979
  if (value instanceof Date) {
@@ -3853,9 +3998,12 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3853
3998
  if (Buffer.isBuffer(value)) {
3854
3999
  isBuffer = true;
3855
4000
  buf = value;
3856
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3857
- isBuffer = true;
3858
- buf = Buffer.from(value.data);
4001
+ } else if (typeof value === "object" && value !== null) {
4002
+ const rawVal = value;
4003
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
4004
+ isBuffer = true;
4005
+ buf = Buffer.from(rawVal.data);
4006
+ }
3859
4007
  }
3860
4008
  if (isBuffer && buf) {
3861
4009
  let isPrintable = true;
@@ -4645,7 +4793,7 @@ class RelationService {
4645
4793
  if (parentFKValue !== null && parentFKValue !== void 0) {
4646
4794
  await tx.update(targetTable).set({
4647
4795
  [targetFKColName]: null
4648
- }).where(eq(targetFKCol, parentFKValue));
4796
+ }).where(eq(targetFKCol, String(parentFKValue)));
4649
4797
  }
4650
4798
  continue;
4651
4799
  }
@@ -4654,7 +4802,7 @@ class RelationService {
4654
4802
  if (parentFKValue !== null && parentFKValue !== void 0) {
4655
4803
  await tx.update(targetTable).set({
4656
4804
  [targetFKColName]: null
4657
- }).where(eq(targetFKCol, parentFKValue));
4805
+ }).where(eq(targetFKCol, String(parentFKValue)));
4658
4806
  } else {
4659
4807
  console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
4660
4808
  continue;
@@ -6731,23 +6879,33 @@ class PostgresBackendDriver {
6731
6879
  client: this.client
6732
6880
  };
6733
6881
  if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
6882
+ let preventDefault = false;
6734
6883
  if (callbacks?.beforeDelete) {
6735
- await callbacks.beforeDelete({
6884
+ const result = await callbacks.beforeDelete({
6736
6885
  collection: resolvedCollection,
6737
6886
  path: entity.path,
6738
6887
  entityId: entity.id,
6739
6888
  entity,
6740
6889
  context: contextForCallback
6741
6890
  });
6891
+ if (result === false) {
6892
+ preventDefault = true;
6893
+ }
6742
6894
  }
6743
6895
  if (propertyCallbacks?.beforeDelete) {
6744
- await propertyCallbacks.beforeDelete({
6896
+ const result = await propertyCallbacks.beforeDelete({
6745
6897
  collection: resolvedCollection,
6746
6898
  path: entity.path,
6747
6899
  entityId: entity.id,
6748
6900
  entity,
6749
6901
  context: contextForCallback
6750
6902
  });
6903
+ if (result === false) {
6904
+ preventDefault = true;
6905
+ }
6906
+ }
6907
+ if (preventDefault) {
6908
+ return;
6751
6909
  }
6752
6910
  }
6753
6911
  await this.entityService.deleteEntity(entity.path, entity.id, entity.databaseId || resolvedCollection?.databaseId);
@@ -6820,7 +6978,17 @@ class PostgresBackendDriver {
6820
6978
  }
6821
6979
  const targetDb = this.getTargetDb(options?.database);
6822
6980
  try {
6823
- if (options?.role) {
6981
+ let needsRoleSwitch = false;
6982
+ if (options?.role && process.env.DISABLE_DB_ROLE_SWITCHING !== "true") {
6983
+ try {
6984
+ const currentRoleResult = await targetDb.execute(sql.raw("SELECT current_user AS role"));
6985
+ const currentRole = currentRoleResult.rows?.[0]?.role;
6986
+ needsRoleSwitch = !!currentRole && currentRole !== options.role;
6987
+ } catch {
6988
+ needsRoleSwitch = true;
6989
+ }
6990
+ }
6991
+ if (needsRoleSwitch && options?.role) {
6824
6992
  const safeRole = options.role.replace(/"/g, '""');
6825
6993
  return await targetDb.transaction(async (tx) => {
6826
6994
  await tx.execute(sql.raw(`SET LOCAL ROLE "${safeRole}"`));
@@ -6858,7 +7026,7 @@ class PostgresBackendDriver {
6858
7026
  return databases;
6859
7027
  }
6860
7028
  async fetchAvailableRoles() {
6861
- const result = await this.executeSql("SELECT rolname FROM pg_roles;");
7029
+ const result = await this.executeSql("SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;");
6862
7030
  return result.map((r) => r.rolname);
6863
7031
  }
6864
7032
  async fetchCurrentDatabase() {
@@ -7027,6 +7195,21 @@ class AuthenticatedPostgresBackendDriver {
7027
7195
  * Typed admin capabilities — delegates to the base driver.
7028
7196
  */
7029
7197
  admin;
7198
+ get restFetchService() {
7199
+ if (!this.delegate.restFetchService) return void 0;
7200
+ return {
7201
+ fetchCollectionForRest: async (collectionPath, options, include) => {
7202
+ return this.withTransaction(async (delegate) => {
7203
+ return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);
7204
+ });
7205
+ },
7206
+ fetchEntityForRest: async (collectionPath, entityId, include, databaseId) => {
7207
+ return this.withTransaction(async (delegate) => {
7208
+ return delegate.restFetchService.fetchEntityForRest(collectionPath, entityId, include, databaseId);
7209
+ });
7210
+ }
7211
+ };
7212
+ }
7030
7213
  async withTransaction(operation) {
7031
7214
  const pendingNotifications = [];
7032
7215
  const result = await this.delegate.db.transaction(async (tx) => {
@@ -7181,113 +7364,140 @@ class DatabasePoolManager {
7181
7364
  this.pools.clear();
7182
7365
  }
7183
7366
  }
7184
- const rebaseSchema = pgSchema("rebase");
7185
- const users = rebaseSchema.table("users", {
7186
- id: uuid("id").defaultRandom().primaryKey(),
7187
- email: varchar("email", {
7188
- length: 255
7189
- }).notNull().unique(),
7190
- passwordHash: varchar("password_hash", {
7191
- length: 255
7192
- }),
7193
- // NULL for OAuth-only users
7194
- displayName: varchar("display_name", {
7195
- length: 255
7196
- }),
7197
- photoUrl: varchar("photo_url", {
7198
- length: 500
7199
- }),
7200
- emailVerified: boolean("email_verified").default(false).notNull(),
7201
- emailVerificationToken: varchar("email_verification_token", {
7202
- length: 255
7203
- }),
7204
- emailVerificationSentAt: timestamp("email_verification_sent_at"),
7205
- createdAt: timestamp("created_at").defaultNow().notNull(),
7206
- updatedAt: timestamp("updated_at").defaultNow().notNull()
7207
- });
7208
- const roles = rebaseSchema.table("roles", {
7209
- id: varchar("id", {
7210
- length: 50
7211
- }).primaryKey(),
7212
- // 'admin', 'editor', 'viewer'
7213
- name: varchar("name", {
7214
- length: 100
7215
- }).notNull(),
7216
- isAdmin: boolean("is_admin").default(false).notNull(),
7217
- defaultPermissions: jsonb("default_permissions").$type(),
7218
- collectionPermissions: jsonb("collection_permissions").$type(),
7219
- config: jsonb("config").$type()
7220
- });
7221
- const userRoles = rebaseSchema.table("user_roles", {
7222
- userId: uuid("user_id").notNull().references(() => users.id, {
7223
- onDelete: "cascade"
7224
- }),
7225
- roleId: varchar("role_id", {
7226
- length: 50
7227
- }).notNull().references(() => roles.id, {
7228
- onDelete: "cascade"
7229
- })
7230
- }, (table) => ({
7231
- pk: primaryKey({
7232
- columns: [table.userId, table.roleId]
7233
- })
7234
- }));
7235
- const refreshTokens = rebaseSchema.table("refresh_tokens", {
7236
- id: uuid("id").defaultRandom().primaryKey(),
7237
- userId: uuid("user_id").notNull().references(() => users.id, {
7238
- onDelete: "cascade"
7239
- }),
7240
- tokenHash: varchar("token_hash", {
7241
- length: 255
7242
- }).notNull().unique(),
7243
- expiresAt: timestamp("expires_at").notNull(),
7244
- userAgent: varchar("user_agent", {
7245
- length: 500
7246
- }),
7247
- ipAddress: varchar("ip_address", {
7248
- length: 45
7249
- }),
7250
- createdAt: timestamp("created_at").defaultNow().notNull()
7251
- }, (table) => ({
7252
- uniqueDeviceSession: unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7253
- }));
7254
- const passwordResetTokens = rebaseSchema.table("password_reset_tokens", {
7255
- id: uuid("id").defaultRandom().primaryKey(),
7256
- userId: uuid("user_id").notNull().references(() => users.id, {
7257
- onDelete: "cascade"
7258
- }),
7259
- tokenHash: varchar("token_hash", {
7260
- length: 255
7261
- }).notNull().unique(),
7262
- expiresAt: timestamp("expires_at").notNull(),
7263
- usedAt: timestamp("used_at"),
7264
- createdAt: timestamp("created_at").defaultNow().notNull()
7265
- });
7266
- const appConfig = rebaseSchema.table("app_config", {
7267
- key: varchar("key", {
7268
- length: 100
7269
- }).primaryKey(),
7270
- value: jsonb("value").notNull(),
7271
- updatedAt: timestamp("updated_at").defaultNow().notNull()
7272
- });
7273
- const userIdentities = rebaseSchema.table("user_identities", {
7274
- id: uuid("id").defaultRandom().primaryKey(),
7275
- userId: uuid("user_id").notNull().references(() => users.id, {
7276
- onDelete: "cascade"
7277
- }),
7278
- provider: varchar("provider", {
7279
- length: 50
7280
- }).notNull(),
7281
- // e.g. 'google', 'linkedin'
7282
- providerId: varchar("provider_id", {
7283
- length: 255
7284
- }).notNull(),
7285
- profileData: jsonb("profile_data"),
7286
- createdAt: timestamp("created_at").defaultNow().notNull(),
7287
- updatedAt: timestamp("updated_at").defaultNow().notNull()
7288
- }, (table) => ({
7289
- uniqueProviderId: unique("unique_provider_id").on(table.provider, table.providerId)
7290
- }));
7367
+ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
7368
+ const rolesSchema = rolesSchemaName === "public" ? null : pgSchema(rolesSchemaName);
7369
+ const usersSchema2 = usersSchemaName === "public" ? null : pgSchema(usersSchemaName);
7370
+ const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgTable;
7371
+ const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgTable;
7372
+ const users2 = usersTableCreator("users", {
7373
+ id: uuid("id").defaultRandom().primaryKey(),
7374
+ email: varchar("email", {
7375
+ length: 255
7376
+ }).notNull().unique(),
7377
+ passwordHash: varchar("password_hash", {
7378
+ length: 255
7379
+ }),
7380
+ // NULL for OAuth-only users
7381
+ displayName: varchar("display_name", {
7382
+ length: 255
7383
+ }),
7384
+ photoUrl: varchar("photo_url", {
7385
+ length: 500
7386
+ }),
7387
+ emailVerified: boolean("email_verified").default(false).notNull(),
7388
+ emailVerificationToken: varchar("email_verification_token", {
7389
+ length: 255
7390
+ }),
7391
+ emailVerificationSentAt: timestamp("email_verification_sent_at"),
7392
+ metadata: jsonb("metadata").$type().default({}).notNull(),
7393
+ createdAt: timestamp("created_at").defaultNow().notNull(),
7394
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
7395
+ });
7396
+ const roles2 = rolesTableCreator("roles", {
7397
+ id: varchar("id", {
7398
+ length: 50
7399
+ }).primaryKey(),
7400
+ // 'admin', 'editor', 'viewer'
7401
+ name: varchar("name", {
7402
+ length: 100
7403
+ }).notNull(),
7404
+ isAdmin: boolean("is_admin").default(false).notNull(),
7405
+ defaultPermissions: jsonb("default_permissions").$type(),
7406
+ collectionPermissions: jsonb("collection_permissions").$type(),
7407
+ config: jsonb("config").$type()
7408
+ });
7409
+ const userRoles2 = rolesTableCreator("user_roles", {
7410
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7411
+ onDelete: "cascade"
7412
+ }),
7413
+ roleId: varchar("role_id", {
7414
+ length: 50
7415
+ }).notNull().references(() => roles2.id, {
7416
+ onDelete: "cascade"
7417
+ })
7418
+ }, (table) => ({
7419
+ pk: primaryKey({
7420
+ columns: [table.userId, table.roleId]
7421
+ })
7422
+ }));
7423
+ const refreshTokens2 = rolesTableCreator("refresh_tokens", {
7424
+ id: uuid("id").defaultRandom().primaryKey(),
7425
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7426
+ onDelete: "cascade"
7427
+ }),
7428
+ tokenHash: varchar("token_hash", {
7429
+ length: 255
7430
+ }).notNull().unique(),
7431
+ expiresAt: timestamp("expires_at").notNull(),
7432
+ userAgent: varchar("user_agent", {
7433
+ length: 500
7434
+ }),
7435
+ ipAddress: varchar("ip_address", {
7436
+ length: 45
7437
+ }),
7438
+ createdAt: timestamp("created_at").defaultNow().notNull()
7439
+ }, (table) => ({
7440
+ uniqueDeviceSession: unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7441
+ }));
7442
+ const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
7443
+ id: uuid("id").defaultRandom().primaryKey(),
7444
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7445
+ onDelete: "cascade"
7446
+ }),
7447
+ tokenHash: varchar("token_hash", {
7448
+ length: 255
7449
+ }).notNull().unique(),
7450
+ expiresAt: timestamp("expires_at").notNull(),
7451
+ usedAt: timestamp("used_at"),
7452
+ createdAt: timestamp("created_at").defaultNow().notNull()
7453
+ });
7454
+ const appConfig2 = rolesTableCreator("app_config", {
7455
+ key: varchar("key", {
7456
+ length: 100
7457
+ }).primaryKey(),
7458
+ value: jsonb("value").notNull(),
7459
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
7460
+ });
7461
+ const userIdentities2 = rolesTableCreator("user_identities", {
7462
+ id: uuid("id").defaultRandom().primaryKey(),
7463
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7464
+ onDelete: "cascade"
7465
+ }),
7466
+ provider: varchar("provider", {
7467
+ length: 50
7468
+ }).notNull(),
7469
+ // e.g. 'google', 'linkedin'
7470
+ providerId: varchar("provider_id", {
7471
+ length: 255
7472
+ }).notNull(),
7473
+ profileData: jsonb("profile_data"),
7474
+ createdAt: timestamp("created_at").defaultNow().notNull(),
7475
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
7476
+ }, (table) => ({
7477
+ uniqueProviderId: unique("unique_provider_id").on(table.provider, table.providerId)
7478
+ }));
7479
+ return {
7480
+ rolesSchema,
7481
+ usersSchema: usersSchema2,
7482
+ users: users2,
7483
+ roles: roles2,
7484
+ userRoles: userRoles2,
7485
+ refreshTokens: refreshTokens2,
7486
+ passwordResetTokens: passwordResetTokens2,
7487
+ appConfig: appConfig2,
7488
+ userIdentities: userIdentities2
7489
+ };
7490
+ }
7491
+ const defaultAuthSchema = createAuthSchema("rebase", "rebase");
7492
+ const rebaseSchema = defaultAuthSchema.rolesSchema;
7493
+ const usersSchema = defaultAuthSchema.usersSchema;
7494
+ const users = defaultAuthSchema.users;
7495
+ const roles = defaultAuthSchema.roles;
7496
+ const userRoles = defaultAuthSchema.userRoles;
7497
+ const refreshTokens = defaultAuthSchema.refreshTokens;
7498
+ const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
7499
+ const appConfig = defaultAuthSchema.appConfig;
7500
+ const userIdentities = defaultAuthSchema.userIdentities;
7291
7501
  const usersRelations = relations(users, ({
7292
7502
  many
7293
7503
  }) => ({
@@ -7470,6 +7680,15 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
7470
7680
  }
7471
7681
  break;
7472
7682
  }
7683
+ case "vector": {
7684
+ const vp = prop;
7685
+ columnDefinition = `vector("${colName}", { dimensions: ${vp.dimensions} })`;
7686
+ break;
7687
+ }
7688
+ case "binary": {
7689
+ columnDefinition = `customType({ dataType() { return 'bytea'; } })("${colName}")`;
7690
+ break;
7691
+ }
7473
7692
  case "relation": {
7474
7693
  const refProp = prop;
7475
7694
  const resolvedRelations = resolveCollectionRelations(collection);
@@ -7536,7 +7755,7 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
7536
7755
  return ` ${propName}: ${columnDefinition}`;
7537
7756
  };
7538
7757
  const resolveRawSql = (expression) => {
7539
- const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => `\${table.${col}}`);
7758
+ const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
7540
7759
  return `sql\`${resolved}\``;
7541
7760
  };
7542
7761
  const wrapWithRoleCheck = (clause, roles2) => {
@@ -7548,7 +7767,7 @@ const unwrapSql = (sqlExpr) => {
7548
7767
  const match = sqlExpr.match(/^sql`(.*)`$/s);
7549
7768
  return match ? match[1] : sqlExpr;
7550
7769
  };
7551
- const buildUsingClause = (rule) => {
7770
+ const buildUsingClause = (rule, collection) => {
7552
7771
  if (rule.using) {
7553
7772
  return resolveRawSql(rule.using);
7554
7773
  }
@@ -7556,15 +7775,17 @@ const buildUsingClause = (rule) => {
7556
7775
  return "sql`true`";
7557
7776
  }
7558
7777
  if (rule.ownerField) {
7559
- return `sql\`\${table.${rule.ownerField}} = auth.uid()\``;
7778
+ const prop = collection.properties?.[rule.ownerField];
7779
+ const colName = resolveColumnName(rule.ownerField, prop);
7780
+ return `sql\`${colName} = auth.uid()\``;
7560
7781
  }
7561
7782
  return null;
7562
7783
  };
7563
- const buildWithCheckClause = (rule) => {
7784
+ const buildWithCheckClause = (rule, collection) => {
7564
7785
  if (rule.withCheck) {
7565
7786
  return resolveRawSql(rule.withCheck);
7566
7787
  }
7567
- return buildUsingClause(rule);
7788
+ return buildUsingClause(rule, collection);
7568
7789
  };
7569
7790
  const getPolicyNameHash = (rule) => {
7570
7791
  const data = JSON.stringify({
@@ -7580,21 +7801,22 @@ const getPolicyNameHash = (rule) => {
7580
7801
  });
7581
7802
  return createHash("sha1").update(data).digest("hex").substring(0, 7);
7582
7803
  };
7583
- const generatePolicyCode = (tableName, rule, index) => {
7804
+ const generatePolicyCode = (collection, rule, index) => {
7805
+ const tableName = getTableName(collection);
7584
7806
  const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
7585
7807
  const ruleHash = getPolicyNameHash(rule);
7586
7808
  return ops.map((op, opIdx) => {
7587
7809
  const policyName = rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
7588
- return generateSinglePolicyCode(tableName, rule, op, policyName);
7810
+ return generateSinglePolicyCode(collection, rule, op, policyName);
7589
7811
  }).join("");
7590
7812
  };
7591
- const generateSinglePolicyCode = (tableName, rule, operation, policyName) => {
7813
+ const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
7592
7814
  const mode = rule.mode ?? "permissive";
7593
7815
  const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
7594
7816
  const needsUsing = operation !== "insert";
7595
7817
  const needsWithCheck = operation !== "select" && operation !== "delete";
7596
- let usingClause = needsUsing ? buildUsingClause(rule) : null;
7597
- let withCheckClause = needsWithCheck ? buildWithCheckClause(rule) : null;
7818
+ let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
7819
+ let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
7598
7820
  if (roles2 && roles2.length > 0) {
7599
7821
  if (usingClause) {
7600
7822
  usingClause = wrapWithRoleCheck(usingClause, roles2);
@@ -7663,12 +7885,26 @@ const computeSharedRelationName = (rel, sourceCollection, _collections) => {
7663
7885
  const generateSchema = async (collections, stripPolicies = false) => {
7664
7886
  let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
7665
7887
  const hasUuid = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "string" && (p.autoValue === "uuid" || p.isId === "uuid")));
7666
- collections.some((c) => c.properties && Object.values(c.properties).some((p) => (p.type === "map" || p.type === "array") && p.columnType === "json"));
7888
+ const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
7889
+ const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
7667
7890
  const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
7668
7891
  if (hasUuid) pgCoreImports.push("uuid");
7892
+ if (hasVector) pgCoreImports.push("vector");
7893
+ if (hasBinary) pgCoreImports.push("customType");
7894
+ const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollection(c) ? c.schema : void 0).filter(Boolean)));
7895
+ if (uniqueSchemas.length > 0) {
7896
+ pgCoreImports.push("pgSchema");
7897
+ }
7669
7898
  schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';
7670
7899
  `;
7671
7900
  schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
7901
+ uniqueSchemas.forEach((schema) => {
7902
+ schemaContent += `export const ${schema}Schema = pgSchema("${schema}");
7903
+ `;
7904
+ });
7905
+ if (uniqueSchemas.length > 0) {
7906
+ schemaContent += "\n";
7907
+ }
7672
7908
  const exportedTableVars = [];
7673
7909
  const exportedEnumVars = [];
7674
7910
  const exportedRelationVars = [];
@@ -7723,6 +7959,9 @@ const generateSchema = async (collections, stripPolicies = false) => {
7723
7959
  const tableVarName = getTableVarName(tableName);
7724
7960
  if (isJunction && relation && sourceCollection && relation.through) {
7725
7961
  const targetCollection = relation.target();
7962
+ const schema = (isPostgresCollection(targetCollection) ? targetCollection.schema : void 0) || (isPostgresCollection(sourceCollection) ? sourceCollection.schema : void 0);
7963
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
7964
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
7726
7965
  const {
7727
7966
  sourceColumn,
7728
7967
  targetColumn
@@ -7733,7 +7972,7 @@ const generateSchema = async (collections, stripPolicies = false) => {
7733
7972
  const targetColType = isNumericId(targetCollection) ? "integer" : getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar";
7734
7973
  const sourceId = getPrimaryKeyName(sourceCollection);
7735
7974
  const targetId = getPrimaryKeyName(targetCollection);
7736
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
7975
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7737
7976
  `;
7738
7977
  schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
7739
7978
  `;
@@ -7744,7 +7983,10 @@ const generateSchema = async (collections, stripPolicies = false) => {
7744
7983
  `;
7745
7984
  schemaContent += "}));\n\n";
7746
7985
  } else if (!isJunction) {
7747
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
7986
+ const schema = isPostgresCollection(collection) ? collection.schema : void 0;
7987
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
7988
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
7989
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7748
7990
  `;
7749
7991
  const columns = /* @__PURE__ */ new Set();
7750
7992
  Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
@@ -7760,7 +8002,7 @@ const generateSchema = async (collections, stripPolicies = false) => {
7760
8002
  if (!stripPolicies && securityRules && securityRules.length > 0) {
7761
8003
  schemaContent += "\n}, (table) => ([\n";
7762
8004
  securityRules.forEach((rule, idx) => {
7763
- schemaContent += generatePolicyCode(tableName, rule);
8005
+ schemaContent += generatePolicyCode(collection, rule);
7764
8006
  });
7765
8007
  schemaContent += "])).enableRLS();\n\n";
7766
8008
  } else {
@@ -7805,11 +8047,11 @@ const generateSchema = async (collections, stripPolicies = false) => {
7805
8047
  references: [${sourceTableVar}.${sourceId}],
7806
8048
  relationName: "${owningRelationName}"
7807
8049
  })`);
7808
- const targetRelName = inverseRelationName ?? owningRelationName;
8050
+ const targetRelationName = inverseRelationName ? inverseRelationName : `${tableName}_${relation.through.targetColumn}`;
7809
8051
  tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {
7810
8052
  fields: [${tableVarName}.${relation.through.targetColumn}],
7811
8053
  references: [${targetTableVar}.${targetId}],
7812
- relationName: "${targetRelName}"
8054
+ relationName: "${targetRelationName}"
7813
8055
  })`);
7814
8056
  }
7815
8057
  } else {
@@ -7908,6 +8150,89 @@ ${tableRelations.join(",\n")}
7908
8150
  schemaContent += tablesExport + enumsExport + relationsExport;
7909
8151
  return schemaContent;
7910
8152
  };
8153
+ const defaultUsersCollection = {
8154
+ name: "Users",
8155
+ singularName: "User",
8156
+ slug: "users",
8157
+ table: "users",
8158
+ icon: "Users",
8159
+ group: "Settings",
8160
+ properties: {
8161
+ id: {
8162
+ name: "ID",
8163
+ type: "string",
8164
+ isId: "uuid"
8165
+ },
8166
+ email: {
8167
+ name: "Email",
8168
+ type: "string",
8169
+ validation: {
8170
+ required: true,
8171
+ unique: true
8172
+ }
8173
+ },
8174
+ password_hash: {
8175
+ name: "Password Hash",
8176
+ type: "string",
8177
+ ui: {
8178
+ hideFromCollection: true
8179
+ }
8180
+ },
8181
+ display_name: {
8182
+ name: "Display Name",
8183
+ type: "string"
8184
+ },
8185
+ photo_url: {
8186
+ name: "Photo URL",
8187
+ type: "string"
8188
+ },
8189
+ email_verified: {
8190
+ name: "Email Verified",
8191
+ type: "boolean",
8192
+ defaultValue: false
8193
+ },
8194
+ email_verification_token: {
8195
+ name: "Email Verification Token",
8196
+ type: "string",
8197
+ ui: {
8198
+ hideFromCollection: true
8199
+ }
8200
+ },
8201
+ email_verification_sent_at: {
8202
+ name: "Email Verification Sent At",
8203
+ type: "date",
8204
+ ui: {
8205
+ hideFromCollection: true
8206
+ }
8207
+ },
8208
+ metadata: {
8209
+ name: "Metadata",
8210
+ type: "map",
8211
+ defaultValue: {},
8212
+ ui: {
8213
+ hideFromCollection: true
8214
+ }
8215
+ },
8216
+ created_at: {
8217
+ name: "Created At",
8218
+ type: "date",
8219
+ autoValue: "on_create",
8220
+ ui: {
8221
+ readOnly: true,
8222
+ hideFromCollection: true
8223
+ }
8224
+ },
8225
+ updated_at: {
8226
+ name: "Updated At",
8227
+ type: "date",
8228
+ autoValue: "on_update",
8229
+ ui: {
8230
+ readOnly: true,
8231
+ hideFromCollection: true
8232
+ }
8233
+ }
8234
+ }
8235
+ };
7911
8236
  const formatTerminalText = (text, options = {}) => {
7912
8237
  let codes = "";
7913
8238
  if (options.bold) codes += "\x1B[1m";
@@ -7970,10 +8295,14 @@ const runGeneration = async (collectionsFilePath, outputPath) => {
7970
8295
  const imported = await dynamicImport(fileUrl);
7971
8296
  collections = imported.backendCollections || imported.collections;
7972
8297
  }
7973
- if (!collections || !Array.isArray(collections) || collections.length === 0) {
7974
- console.error("Error: Could not find collections array or failed to load directory.");
7975
- return;
8298
+ if (!collections || !Array.isArray(collections)) {
8299
+ collections = [];
8300
+ }
8301
+ const hasUsersCollection = collections.some((c) => c.slug === "users");
8302
+ if (!hasUsersCollection) {
8303
+ collections.push(defaultUsersCollection);
7976
8304
  }
8305
+ collections.sort((a, b) => a.slug.localeCompare(b.slug));
7977
8306
  const schemaContent = await generateSchema(collections);
7978
8307
  if (outputPath) {
7979
8308
  const outputDir = path.dirname(outputPath);
@@ -8201,29 +8530,14 @@ class RealtimeService extends EventEmitter {
8201
8530
  },
8202
8531
  authContext
8203
8532
  });
8204
- let entities;
8205
- if (this.driver) {
8206
- entities = await this.driver.fetchCollection({
8207
- path: request.path,
8208
- collection,
8209
- filter: request.filter,
8210
- orderBy: request.orderBy,
8211
- order: request.order,
8212
- limit: request.limit,
8213
- startAfter: request.startAfter,
8214
- searchString: request.searchString
8215
- });
8216
- } else {
8217
- entities = await this.entityService.fetchCollection(request.path, {
8218
- filter: request.filter,
8219
- orderBy: request.orderBy,
8220
- order: request.order,
8221
- limit: request.limit,
8222
- startAfter: request.startAfter,
8223
- databaseId: request.collection?.databaseId,
8224
- searchString: request.searchString
8225
- });
8226
- }
8533
+ const entities = await this.fetchCollectionWithAuth(request.path, {
8534
+ filter: request.filter,
8535
+ orderBy: request.orderBy,
8536
+ order: request.order,
8537
+ limit: request.limit,
8538
+ startAfter: request.startAfter,
8539
+ searchString: request.searchString
8540
+ }, authContext);
8227
8541
  this.sendCollectionUpdate(clientId, subscriptionId, entities);
8228
8542
  } catch (error) {
8229
8543
  this.sendError(clientId, `Failed to subscribe to collection: ${error}`, subscriptionId);
@@ -8247,16 +8561,7 @@ class RealtimeService extends EventEmitter {
8247
8561
  entityId: request.entityId,
8248
8562
  authContext
8249
8563
  });
8250
- let entity;
8251
- if (this.driver) {
8252
- entity = await this.driver.fetchEntity({
8253
- path: request.path,
8254
- entityId: request.entityId,
8255
- collection
8256
- });
8257
- } else {
8258
- entity = await this.entityService.fetchEntity(request.path, request.entityId, request.collection?.databaseId);
8259
- }
8564
+ const entity = await this.fetchEntityWithAuth(request.path, String(request.entityId), authContext);
8260
8565
  this.sendEntityUpdate(clientId, subscriptionId, entity || null);
8261
8566
  } catch (error) {
8262
8567
  this.sendError(clientId, `Failed to subscribe to entity: ${request.path} ${request.entityId} ${error}`, subscriptionId);
@@ -8401,87 +8706,77 @@ class RealtimeService extends EventEmitter {
8401
8706
  async fetchCollectionWithAuth(notifyPath, collectionRequest, authContext) {
8402
8707
  if (this.driver) {
8403
8708
  const collection = this.registry.getCollectionByPath(notifyPath);
8404
- const fetchFn = async () => this.driver.fetchCollection({
8405
- path: notifyPath,
8406
- collection,
8407
- filter: collectionRequest.filter,
8408
- orderBy: collectionRequest.orderBy,
8409
- order: collectionRequest.order,
8410
- limit: collectionRequest.limit,
8411
- offset: collectionRequest.offset,
8412
- startAfter: collectionRequest.startAfter,
8413
- searchString: collectionRequest.searchString
8709
+ const activeAuth = authContext || {
8710
+ userId: "anon",
8711
+ roles: ["anon"]
8712
+ };
8713
+ return await this.db.transaction(async (tx) => {
8714
+ await tx.execute(sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
8715
+ await tx.execute(sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
8716
+ await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
8717
+ sub: activeAuth.userId,
8718
+ roles: activeAuth.roles
8719
+ })}, true)`);
8720
+ const txEntityService = new EntityService(tx, this.registry);
8721
+ let fetchedEntities;
8722
+ if (collectionRequest.searchString) {
8723
+ fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8724
+ filter: collectionRequest.filter,
8725
+ orderBy: collectionRequest.orderBy,
8726
+ order: collectionRequest.order,
8727
+ limit: collectionRequest.limit,
8728
+ databaseId: collectionRequest.databaseId
8729
+ });
8730
+ } else {
8731
+ fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8732
+ filter: collectionRequest.filter,
8733
+ orderBy: collectionRequest.orderBy,
8734
+ order: collectionRequest.order,
8735
+ limit: collectionRequest.limit,
8736
+ offset: collectionRequest.offset,
8737
+ startAfter: collectionRequest.startAfter,
8738
+ databaseId: collectionRequest.databaseId
8739
+ });
8740
+ }
8741
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
8742
+ const resolvedCollection = collection ? {
8743
+ ...collection,
8744
+ ...registryCollection
8745
+ } : registryCollection;
8746
+ const callbacks = resolvedCollection?.callbacks;
8747
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8748
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8749
+ const contextForCallback = {
8750
+ user: {
8751
+ uid: activeAuth.userId,
8752
+ roles: activeAuth.roles
8753
+ },
8754
+ driver: this.driver,
8755
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
8756
+ };
8757
+ return await Promise.all(fetchedEntities.map(async (entity) => {
8758
+ let processedEntity = entity;
8759
+ if (callbacks?.afterRead) {
8760
+ processedEntity = await callbacks.afterRead({
8761
+ collection: resolvedCollection,
8762
+ path: notifyPath,
8763
+ entity: processedEntity,
8764
+ context: contextForCallback
8765
+ }) ?? processedEntity;
8766
+ }
8767
+ if (propertyCallbacks?.afterRead) {
8768
+ processedEntity = await propertyCallbacks.afterRead({
8769
+ collection: resolvedCollection,
8770
+ path: notifyPath,
8771
+ entity: processedEntity,
8772
+ context: contextForCallback
8773
+ }) ?? processedEntity;
8774
+ }
8775
+ return processedEntity;
8776
+ }));
8777
+ }
8778
+ return fetchedEntities;
8414
8779
  });
8415
- if (authContext) {
8416
- return await this.db.transaction(async (tx) => {
8417
- await tx.execute(sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8418
- await tx.execute(sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8419
- await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
8420
- sub: authContext.userId,
8421
- roles: authContext.roles
8422
- })}, true)`);
8423
- const txEntityService = new EntityService(tx, this.registry);
8424
- let fetchedEntities;
8425
- if (collectionRequest.searchString) {
8426
- fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8427
- filter: collectionRequest.filter,
8428
- orderBy: collectionRequest.orderBy,
8429
- order: collectionRequest.order,
8430
- limit: collectionRequest.limit,
8431
- databaseId: collectionRequest.databaseId
8432
- });
8433
- } else {
8434
- fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8435
- filter: collectionRequest.filter,
8436
- orderBy: collectionRequest.orderBy,
8437
- order: collectionRequest.order,
8438
- limit: collectionRequest.limit,
8439
- offset: collectionRequest.offset,
8440
- startAfter: collectionRequest.startAfter,
8441
- databaseId: collectionRequest.databaseId
8442
- });
8443
- }
8444
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8445
- const resolvedCollection = collection ? {
8446
- ...collection,
8447
- ...registryCollection
8448
- } : registryCollection;
8449
- const callbacks = resolvedCollection?.callbacks;
8450
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8451
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8452
- const contextForCallback = {
8453
- user: {
8454
- uid: authContext.userId,
8455
- roles: authContext.roles
8456
- },
8457
- driver: this.driver,
8458
- data: this.driver ? this.driver.data : void 0
8459
- };
8460
- return await Promise.all(fetchedEntities.map(async (entity) => {
8461
- let processedEntity = entity;
8462
- if (callbacks?.afterRead) {
8463
- processedEntity = await callbacks.afterRead({
8464
- collection: resolvedCollection,
8465
- path: notifyPath,
8466
- entity: processedEntity,
8467
- context: contextForCallback
8468
- }) ?? processedEntity;
8469
- }
8470
- if (propertyCallbacks?.afterRead) {
8471
- processedEntity = await propertyCallbacks.afterRead({
8472
- collection: resolvedCollection,
8473
- path: notifyPath,
8474
- entity: processedEntity,
8475
- context: contextForCallback
8476
- }) ?? processedEntity;
8477
- }
8478
- return processedEntity;
8479
- }));
8480
- }
8481
- return fetchedEntities;
8482
- });
8483
- }
8484
- return fetchFn();
8485
8780
  }
8486
8781
  if (collectionRequest.searchString) {
8487
8782
  return await this.entityService.searchEntities(notifyPath, collectionRequest.searchString, {
@@ -8545,60 +8840,56 @@ class RealtimeService extends EventEmitter {
8545
8840
  async fetchEntityWithAuth(notifyPath, entityId, authContext) {
8546
8841
  if (this.driver) {
8547
8842
  const collection = this.registry.getCollectionByPath(notifyPath);
8548
- const fetchFn = async () => this.driver.fetchEntity({
8549
- path: notifyPath,
8550
- entityId,
8551
- collection
8552
- });
8553
- if (authContext) {
8554
- return await this.db.transaction(async (tx) => {
8555
- await tx.execute(sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8556
- await tx.execute(sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8557
- await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
8558
- sub: authContext.userId,
8559
- roles: authContext.roles
8560
- })}, true)`);
8561
- const txEntityService = new EntityService(tx, this.registry);
8562
- let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
8563
- if (processedEntity) {
8564
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8565
- const resolvedCollection = collection ? {
8566
- ...collection,
8567
- ...registryCollection
8568
- } : registryCollection;
8569
- const callbacks = resolvedCollection?.callbacks;
8570
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8571
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8572
- const contextForCallback = {
8573
- user: {
8574
- uid: authContext.userId,
8575
- roles: authContext.roles
8576
- },
8577
- driver: this.driver,
8578
- data: this.driver ? this.driver.data : void 0
8579
- };
8580
- if (callbacks?.afterRead) {
8581
- processedEntity = await callbacks.afterRead({
8582
- collection: resolvedCollection,
8583
- path: notifyPath,
8584
- entity: processedEntity,
8585
- context: contextForCallback
8586
- }) ?? processedEntity;
8587
- }
8588
- if (propertyCallbacks?.afterRead) {
8589
- processedEntity = await propertyCallbacks.afterRead({
8590
- collection: resolvedCollection,
8591
- path: notifyPath,
8592
- entity: processedEntity,
8593
- context: contextForCallback
8594
- }) ?? processedEntity;
8595
- }
8843
+ const activeAuth = authContext || {
8844
+ userId: "anon",
8845
+ roles: ["anon"]
8846
+ };
8847
+ return await this.db.transaction(async (tx) => {
8848
+ await tx.execute(sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
8849
+ await tx.execute(sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
8850
+ await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
8851
+ sub: activeAuth.userId,
8852
+ roles: activeAuth.roles
8853
+ })}, true)`);
8854
+ const txEntityService = new EntityService(tx, this.registry);
8855
+ let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
8856
+ if (processedEntity) {
8857
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
8858
+ const resolvedCollection = collection ? {
8859
+ ...collection,
8860
+ ...registryCollection
8861
+ } : registryCollection;
8862
+ const callbacks = resolvedCollection?.callbacks;
8863
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8864
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8865
+ const contextForCallback = {
8866
+ user: {
8867
+ uid: activeAuth.userId,
8868
+ roles: activeAuth.roles
8869
+ },
8870
+ driver: this.driver,
8871
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
8872
+ };
8873
+ if (callbacks?.afterRead) {
8874
+ processedEntity = await callbacks.afterRead({
8875
+ collection: resolvedCollection,
8876
+ path: notifyPath,
8877
+ entity: processedEntity,
8878
+ context: contextForCallback
8879
+ }) ?? processedEntity;
8880
+ }
8881
+ if (propertyCallbacks?.afterRead) {
8882
+ processedEntity = await propertyCallbacks.afterRead({
8883
+ collection: resolvedCollection,
8884
+ path: notifyPath,
8885
+ entity: processedEntity,
8886
+ context: contextForCallback
8887
+ }) ?? processedEntity;
8596
8888
  }
8597
8889
  }
8598
- return processedEntity;
8599
- });
8600
- }
8601
- return fetchFn();
8890
+ }
8891
+ return processedEntity;
8892
+ });
8602
8893
  }
8603
8894
  return await this.entityService.fetchEntity(notifyPath, entityId);
8604
8895
  }
@@ -8666,6 +8957,31 @@ class RealtimeService extends EventEmitter {
8666
8957
  return parentPaths;
8667
8958
  }
8668
8959
  // =============================================================================
8960
+ // Lifecycle / Cleanup
8961
+ // =============================================================================
8962
+ /**
8963
+ * Gracefully tear down all realtime resources.
8964
+ *
8965
+ * This MUST be called during process shutdown, **before** `pool.end()`.
8966
+ * It ensures:
8967
+ * 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
8968
+ * 2. All subscription state and callbacks are cleared.
8969
+ * 3. The dedicated LISTEN client (outside the pool) is disconnected.
8970
+ * 4. All WebSocket clients are removed (but not forcefully closed — the
8971
+ * HTTP server close will handle that).
8972
+ */
8973
+ async destroy() {
8974
+ for (const [key, timer] of this.refetchTimers) {
8975
+ clearTimeout(timer);
8976
+ this.refetchTimers.delete(key);
8977
+ }
8978
+ this._subscriptions.clear();
8979
+ this.subscriptionCallbacks.clear();
8980
+ await this.stopListening();
8981
+ this.clients.clear();
8982
+ this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
8983
+ }
8984
+ // =============================================================================
8669
8985
  // Cross-Instance LISTEN/NOTIFY
8670
8986
  // =============================================================================
8671
8987
  /**
@@ -8806,8 +9122,23 @@ const clientSessions = /* @__PURE__ */ new Map();
8806
9122
  const WS_RATE_LIMIT = 2e3;
8807
9123
  const WS_RATE_WINDOW_MS = 6e4;
8808
9124
  const ADMIN_ONLY_TYPES = /* @__PURE__ */ new Set(["EXECUTE_SQL", "FETCH_DATABASES", "FETCH_ROLES", "FETCH_UNMAPPED_TABLES", "FETCH_TABLE_METADATA", "FETCH_CURRENT_DATABASE", "CREATE_BRANCH", "DELETE_BRANCH", "LIST_BRANCHES"]);
9125
+ function extractErrorMessage(error) {
9126
+ if (!error) return "Unknown error";
9127
+ if (typeof error === "object") {
9128
+ const err = error;
9129
+ if (err.cause) {
9130
+ return extractErrorMessage(err.cause);
9131
+ }
9132
+ if (typeof err.message === "string") {
9133
+ return err.message;
9134
+ }
9135
+ }
9136
+ return String(error);
9137
+ }
8809
9138
  function isAdminSession(session) {
8810
- if (!session?.user?.roles) return false;
9139
+ if (!session?.user) return false;
9140
+ if (session.user.isAdmin) return true;
9141
+ if (!session.user.roles) return false;
8811
9142
  return session.user.roles.some((r) => {
8812
9143
  if (typeof r === "string") return r === "admin";
8813
9144
  if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
@@ -8815,7 +9146,7 @@ function isAdminSession(session) {
8815
9146
  return false;
8816
9147
  });
8817
9148
  }
8818
- function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9149
+ function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
8819
9150
  const isProduction = process.env.NODE_ENV === "production";
8820
9151
  const wsDebug = (...args) => {
8821
9152
  if (!isProduction) console.debug(...args);
@@ -8829,7 +9160,7 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8829
9160
  }
8830
9161
  console.error("❌ [WebSocket Server] Error:", err);
8831
9162
  });
8832
- const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
9163
+ const requireAuth = authAdapter ? true : authConfig?.requireAuth !== false && !!authConfig?.jwtSecret;
8833
9164
  wss.on("connection", (ws) => {
8834
9165
  const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
8835
9166
  wsDebug(`WebSocket client connected: ${clientId}`);
@@ -8874,11 +9205,37 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8874
9205
  sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
8875
9206
  return;
8876
9207
  }
8877
- const user = extractUserFromToken(token);
8878
- if (user) {
9208
+ let verifiedUser = null;
9209
+ if (authAdapter) {
9210
+ try {
9211
+ const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", {
9212
+ headers: {
9213
+ Authorization: `Bearer ${token}`
9214
+ }
9215
+ }));
9216
+ if (adapterUser) {
9217
+ verifiedUser = {
9218
+ userId: adapterUser.uid,
9219
+ roles: adapterUser.roles,
9220
+ isAdmin: adapterUser.isAdmin
9221
+ };
9222
+ }
9223
+ } catch {
9224
+ }
9225
+ } else {
9226
+ const jwtPayload = extractUserFromToken(token);
9227
+ if (jwtPayload) {
9228
+ verifiedUser = {
9229
+ userId: jwtPayload.userId,
9230
+ roles: jwtPayload.roles ?? [],
9231
+ isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
9232
+ };
9233
+ }
9234
+ }
9235
+ if (verifiedUser) {
8879
9236
  const session = clientSessions.get(clientId);
8880
9237
  if (session) {
8881
- session.user = user;
9238
+ session.user = verifiedUser;
8882
9239
  session.authenticated = true;
8883
9240
  }
8884
9241
  wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
@@ -8886,11 +9243,11 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8886
9243
  type: "AUTH_SUCCESS",
8887
9244
  requestId,
8888
9245
  payload: {
8889
- userId: user.userId,
8890
- roles: user.roles
9246
+ userId: verifiedUser.userId,
9247
+ roles: verifiedUser.roles
8891
9248
  }
8892
9249
  }));
8893
- wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${user.userId}`);
9250
+ wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.userId}`);
8894
9251
  } else {
8895
9252
  wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
8896
9253
  sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
@@ -8928,16 +9285,19 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8928
9285
  }
8929
9286
  const getScopedDelegate = async () => {
8930
9287
  const session = clientSessions.get(clientId);
8931
- if (session?.user && "withAuth" in driver && typeof driver.withAuth === "function") {
9288
+ if ("withAuth" in driver && typeof driver.withAuth === "function") {
8932
9289
  try {
8933
- const userForAuth = {
9290
+ const userForAuth = session?.user ? {
8934
9291
  uid: session.user.userId,
8935
9292
  roles: session.user.roles ?? []
9293
+ } : {
9294
+ uid: "anon",
9295
+ roles: ["anon"]
8936
9296
  };
8937
9297
  return await driver.withAuth(userForAuth);
8938
9298
  } catch (e) {
8939
- console.error("Failed to create authenticated delegate for WS request", e);
8940
- return driver;
9299
+ console.error("Failed to create RLS scoped delegate for WS request", e);
9300
+ throw new Error("Internal authentication error");
8941
9301
  }
8942
9302
  }
8943
9303
  return driver;
@@ -9068,24 +9428,29 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9068
9428
  sql: sql2,
9069
9429
  options
9070
9430
  } = payload;
9071
- const delegate = await getScopedDelegate();
9072
- const admin = delegate.admin;
9073
- if (!isSQLAdmin(admin)) {
9074
- sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9075
- break;
9076
- }
9077
- const result = await admin.executeSql(sql2, options);
9078
- if (process.env.NODE_ENV !== "production") {
9079
- wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9431
+ try {
9432
+ const delegate = await getScopedDelegate();
9433
+ const admin = delegate.admin;
9434
+ if (!isSQLAdmin(admin)) {
9435
+ sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9436
+ break;
9437
+ }
9438
+ const result = await admin.executeSql(sql2, options);
9439
+ if (process.env.NODE_ENV !== "production") {
9440
+ wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9441
+ }
9442
+ const response = {
9443
+ type: "EXECUTE_SQL_SUCCESS",
9444
+ payload: {
9445
+ result
9446
+ },
9447
+ requestId
9448
+ };
9449
+ ws.send(JSON.stringify(response));
9450
+ } catch (sqlError) {
9451
+ const errMsg = extractErrorMessage(sqlError);
9452
+ sendError("ERROR", "SQL_ERROR", errMsg);
9080
9453
  }
9081
- const response = {
9082
- type: "EXECUTE_SQL_SUCCESS",
9083
- payload: {
9084
- result
9085
- },
9086
- requestId
9087
- };
9088
- ws.send(JSON.stringify(response));
9089
9454
  }
9090
9455
  break;
9091
9456
  case "FETCH_DATABASES":
@@ -9264,7 +9629,10 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9264
9629
  const authContext = session?.user ? {
9265
9630
  userId: session.user.userId,
9266
9631
  roles: session.user.roles ?? []
9267
- } : void 0;
9632
+ } : {
9633
+ userId: "anon",
9634
+ roles: ["anon"]
9635
+ };
9268
9636
  await realtimeService.handleClientMessage(clientId, {
9269
9637
  type,
9270
9638
  payload,
@@ -9408,29 +9776,58 @@ const DEFAULT_ROLES = [{
9408
9776
  },
9409
9777
  config: null
9410
9778
  }];
9411
- async function ensureAuthTablesExist(db) {
9779
+ async function ensureAuthTablesExist(db, registry) {
9412
9780
  console.log("🔍 Checking auth tables...");
9413
9781
  try {
9782
+ let usersTableName = '"users"';
9783
+ let userIdType = "TEXT";
9784
+ let usersSchema2 = "public";
9785
+ if (registry) {
9786
+ const usersTable = registry.getTable("users");
9787
+ if (usersTable) {
9788
+ const {
9789
+ getTableName: getTableName2
9790
+ } = await import("drizzle-orm");
9791
+ usersSchema2 = getTableConfig(usersTable).schema || "public";
9792
+ usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
9793
+ if (usersTable.id) {
9794
+ const col = usersTable.id;
9795
+ const meta = getColumnMeta(col);
9796
+ const columnType = meta.columnType;
9797
+ if (columnType === "PgUUID") {
9798
+ userIdType = "UUID";
9799
+ } else if (columnType === "PgSerial" || columnType === "PgInteger") {
9800
+ userIdType = "INTEGER";
9801
+ } else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
9802
+ userIdType = "BIGINT";
9803
+ }
9804
+ }
9805
+ }
9806
+ }
9807
+ let rolesSchema = "rebase";
9808
+ if (registry) {
9809
+ const rolesTable = registry.getTable("roles");
9810
+ if (rolesTable) {
9811
+ rolesSchema = getTableConfig(rolesTable).schema || "public";
9812
+ }
9813
+ }
9814
+ if (usersSchema2 !== "public") {
9815
+ await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(usersSchema2)}`);
9816
+ }
9817
+ if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
9818
+ await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(rolesSchema)}`);
9819
+ }
9414
9820
  await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);
9821
+ const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
9822
+ const rolesTableName = `"${rolesSchema}"."roles"`;
9823
+ const userRolesTableName = `"${rolesSchema}"."user_roles"`;
9824
+ const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
9825
+ const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
9826
+ const appConfigTableName = `"${rolesSchema}"."app_config"`;
9415
9827
  await db.execute(sql`
9416
- CREATE TABLE IF NOT EXISTS rebase.users (
9828
+ CREATE TABLE IF NOT EXISTS ${sql.raw(userIdentitiesTable)} (
9417
9829
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9418
- email TEXT NOT NULL UNIQUE,
9419
- password_hash TEXT,
9420
- display_name TEXT,
9421
- photo_url TEXT,
9422
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
9423
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9424
- )
9425
- `);
9426
- await db.execute(sql`
9427
- CREATE INDEX IF NOT EXISTS idx_users_email
9428
- ON rebase.users(email)
9429
- `);
9430
- await db.execute(sql`
9431
- CREATE TABLE IF NOT EXISTS rebase.user_identities (
9432
- id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9433
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9830
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9434
9831
  provider TEXT NOT NULL,
9435
9832
  provider_id TEXT NOT NULL,
9436
9833
  profile_data JSONB,
@@ -9441,10 +9838,10 @@ async function ensureAuthTablesExist(db) {
9441
9838
  `);
9442
9839
  await db.execute(sql`
9443
9840
  CREATE INDEX IF NOT EXISTS idx_user_identities_user
9444
- ON rebase.user_identities(user_id)
9841
+ ON ${sql.raw(userIdentitiesTable)}(user_id)
9445
9842
  `);
9446
9843
  await db.execute(sql`
9447
- CREATE TABLE IF NOT EXISTS rebase.roles (
9844
+ CREATE TABLE IF NOT EXISTS ${sql.raw(rolesTableName)} (
9448
9845
  id TEXT PRIMARY KEY,
9449
9846
  name TEXT NOT NULL,
9450
9847
  is_admin BOOLEAN DEFAULT FALSE,
@@ -9455,20 +9852,20 @@ async function ensureAuthTablesExist(db) {
9455
9852
  )
9456
9853
  `);
9457
9854
  await db.execute(sql`
9458
- CREATE TABLE IF NOT EXISTS rebase.user_roles (
9459
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9460
- role_id TEXT NOT NULL REFERENCES rebase.roles(id) ON DELETE CASCADE,
9855
+ CREATE TABLE IF NOT EXISTS ${sql.raw(userRolesTableName)} (
9856
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9857
+ role_id TEXT NOT NULL REFERENCES ${sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
9461
9858
  PRIMARY KEY (user_id, role_id)
9462
9859
  )
9463
9860
  `);
9464
9861
  await db.execute(sql`
9465
9862
  CREATE INDEX IF NOT EXISTS idx_user_roles_user
9466
- ON rebase.user_roles(user_id)
9863
+ ON ${sql.raw(userRolesTableName)}(user_id)
9467
9864
  `);
9468
9865
  await db.execute(sql`
9469
- CREATE TABLE IF NOT EXISTS rebase.refresh_tokens (
9866
+ CREATE TABLE IF NOT EXISTS ${sql.raw(refreshTokensTableName)} (
9470
9867
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9471
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9868
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9472
9869
  token_hash TEXT NOT NULL UNIQUE,
9473
9870
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9474
9871
  user_agent TEXT,
@@ -9479,16 +9876,16 @@ async function ensureAuthTablesExist(db) {
9479
9876
  `);
9480
9877
  await db.execute(sql`
9481
9878
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash
9482
- ON rebase.refresh_tokens(token_hash)
9879
+ ON ${sql.raw(refreshTokensTableName)}(token_hash)
9483
9880
  `);
9484
9881
  await db.execute(sql`
9485
9882
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user
9486
- ON rebase.refresh_tokens(user_id)
9883
+ ON ${sql.raw(refreshTokensTableName)}(user_id)
9487
9884
  `);
9488
9885
  await db.execute(sql`
9489
- CREATE TABLE IF NOT EXISTS rebase.password_reset_tokens (
9886
+ CREATE TABLE IF NOT EXISTS ${sql.raw(passwordResetTokensTableName)} (
9490
9887
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9491
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9888
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9492
9889
  token_hash TEXT NOT NULL UNIQUE,
9493
9890
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9494
9891
  used_at TIMESTAMP WITH TIME ZONE,
@@ -9497,20 +9894,19 @@ async function ensureAuthTablesExist(db) {
9497
9894
  `);
9498
9895
  await db.execute(sql`
9499
9896
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_hash
9500
- ON rebase.password_reset_tokens(token_hash)
9897
+ ON ${sql.raw(passwordResetTokensTableName)}(token_hash)
9501
9898
  `);
9502
9899
  await db.execute(sql`
9503
9900
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
9504
- ON rebase.password_reset_tokens(user_id)
9901
+ ON ${sql.raw(passwordResetTokensTableName)}(user_id)
9505
9902
  `);
9506
9903
  await db.execute(sql`
9507
- CREATE TABLE IF NOT EXISTS rebase.app_config (
9904
+ CREATE TABLE IF NOT EXISTS ${sql.raw(appConfigTableName)} (
9508
9905
  key TEXT PRIMARY KEY,
9509
9906
  value JSONB NOT NULL,
9510
9907
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9511
9908
  )
9512
9909
  `);
9513
- await applyInternalMigrations(db);
9514
9910
  await db.execute(sql`CREATE SCHEMA IF NOT EXISTS auth`);
9515
9911
  await db.transaction(async (tx) => {
9516
9912
  await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);
@@ -9533,15 +9929,15 @@ async function ensureAuthTablesExist(db) {
9533
9929
  $$ LANGUAGE sql STABLE
9534
9930
  `);
9535
9931
  });
9536
- await seedDefaultRoles(db);
9932
+ await seedDefaultRoles(db, rolesTableName);
9537
9933
  console.log("✅ Auth tables ready");
9538
9934
  } catch (error) {
9539
9935
  console.error("❌ Failed to create auth tables:", error);
9540
9936
  console.warn("⚠️ Continuing without creating auth tables.");
9541
9937
  }
9542
9938
  }
9543
- async function seedDefaultRoles(db) {
9544
- const result = await db.execute(sql`SELECT COUNT(*) as count FROM rebase.roles`);
9939
+ async function seedDefaultRoles(db, rolesTableName) {
9940
+ const result = await db.execute(sql`SELECT COUNT(*) as count FROM ${sql.raw(rolesTableName)}`);
9545
9941
  const count2 = parseInt(result.rows[0]?.count || "0", 10);
9546
9942
  if (count2 > 0) {
9547
9943
  console.log(`📋 Found ${count2} existing roles`);
@@ -9550,7 +9946,7 @@ async function seedDefaultRoles(db) {
9550
9946
  console.log("🌱 Seeding default roles...");
9551
9947
  for (const role of DEFAULT_ROLES) {
9552
9948
  await db.execute(sql`
9553
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, config)
9949
+ INSERT INTO ${sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
9554
9950
  VALUES (
9555
9951
  ${role.id},
9556
9952
  ${role.name},
@@ -9563,142 +9959,156 @@ async function seedDefaultRoles(db) {
9563
9959
  }
9564
9960
  console.log("✅ Default roles created: admin, editor, viewer");
9565
9961
  }
9566
- async function applyInternalMigrations(db) {
9567
- try {
9568
- await db.execute(sql`
9569
- ALTER TABLE rebase.users
9570
- ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE,
9571
- ADD COLUMN IF NOT EXISTS email_verification_token TEXT,
9572
- ADD COLUMN IF NOT EXISTS email_verification_sent_at TIMESTAMP WITH TIME ZONE
9573
- `);
9574
- const columnsCheck = await db.execute(sql`
9575
- SELECT column_name
9576
- FROM information_schema.columns
9577
- WHERE table_schema='rebase' AND table_name='users' AND column_name IN ('google_id', 'linkedin_id', 'provider')
9578
- `);
9579
- const existingColumns = columnsCheck.rows.map((r) => r.column_name);
9580
- if (existingColumns.includes("google_id")) {
9581
- await db.execute(sql`
9582
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9583
- SELECT id, 'google', google_id
9584
- FROM rebase.users
9585
- WHERE google_id IS NOT NULL
9586
- ON CONFLICT (provider, provider_id) DO NOTHING
9587
- `);
9588
- }
9589
- if (existingColumns.includes("linkedin_id")) {
9590
- await db.execute(sql`
9591
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9592
- SELECT id, 'linkedin', linkedin_id
9593
- FROM rebase.users
9594
- WHERE linkedin_id IS NOT NULL
9595
- ON CONFLICT (provider, provider_id) DO NOTHING
9596
- `);
9597
- }
9598
- if (existingColumns.length > 0) {
9599
- await db.execute(sql`
9600
- ALTER TABLE rebase.users
9601
- DROP COLUMN IF EXISTS provider,
9602
- DROP COLUMN IF EXISTS google_id,
9603
- DROP COLUMN IF EXISTS linkedin_id
9604
- `);
9605
- await db.execute(sql`DROP INDEX IF EXISTS rebase.idx_users_google_id`);
9606
- await db.execute(sql`DROP INDEX IF EXISTS rebase.idx_users_linkedin_id`);
9607
- console.log("✅ Migrated to user_identities and dropped legacy columns.");
9608
- }
9609
- await db.execute(sql`
9610
- ALTER TABLE rebase.roles
9611
- ADD COLUMN IF NOT EXISTS collection_permissions JSONB
9612
- `);
9613
- await db.execute(sql`
9614
- ALTER TABLE rebase.refresh_tokens
9615
- ADD COLUMN IF NOT EXISTS user_agent TEXT,
9616
- ADD COLUMN IF NOT EXISTS ip_address TEXT
9617
- `);
9618
- const constraintCheck = await db.execute(sql`
9619
- SELECT 1 FROM information_schema.table_constraints
9620
- WHERE constraint_name = 'unique_device_session'
9621
- AND table_schema = 'rebase'
9622
- AND table_name = 'refresh_tokens'
9623
- `);
9624
- if (constraintCheck.rows.length === 0) {
9625
- try {
9626
- await db.execute(sql`
9627
- ALTER TABLE rebase.refresh_tokens
9628
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9629
- `);
9630
- console.log("✅ Added unique_device_session constraint");
9631
- } catch (e) {
9632
- const errorMessage = e instanceof Error ? e.message : String(e);
9633
- if (errorMessage.includes("could not create unique index")) {
9634
- console.warn("⚠️ Duplicate sessions found, cleaning up before adding constraint...");
9635
- await db.execute(sql`
9636
- DELETE FROM rebase.refresh_tokens a
9637
- USING rebase.refresh_tokens b
9638
- WHERE a.user_id = b.user_id
9639
- AND COALESCE(a.user_agent, '') = COALESCE(b.user_agent, '')
9640
- AND COALESCE(a.ip_address, '') = COALESCE(b.ip_address, '')
9641
- AND a.created_at < b.created_at
9642
- `);
9643
- await db.execute(sql`
9644
- ALTER TABLE rebase.refresh_tokens
9645
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9646
- `).catch((retryErr) => {
9647
- const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
9648
- console.error("Failed to add unique_device_session constraint after cleanup:", retryMessage);
9649
- });
9650
- } else {
9651
- console.error("Constraint migration issue:", errorMessage);
9652
- }
9653
- }
9654
- }
9655
- } catch (error) {
9656
- console.error("❌ Failed to run internal migrations:", error);
9962
+ function getColumnKey(table, ...keys2) {
9963
+ if (!table) return void 0;
9964
+ for (const key of keys2) {
9965
+ if (key in table) return key;
9966
+ const snake = toSnakeCase(key);
9967
+ if (snake in table) return snake;
9968
+ const camel = camelCase(key);
9969
+ if (camel in table) return camel;
9657
9970
  }
9971
+ return void 0;
9972
+ }
9973
+ function getColumn(table, ...keys2) {
9974
+ if (!table) return void 0;
9975
+ const key = getColumnKey(table, ...keys2);
9976
+ return key ? table[key] : void 0;
9658
9977
  }
9659
9978
  class UserService {
9660
- constructor(db) {
9979
+ constructor(db, tableOrTables) {
9661
9980
  this.db = db;
9981
+ if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
9982
+ const tables = tableOrTables;
9983
+ this.usersTable = tables.users || users;
9984
+ this.userIdentitiesTable = tables.userIdentities || userIdentities;
9985
+ this.userRolesTable = tables.userRoles || userRoles;
9986
+ this.rolesTable = tables.roles || roles;
9987
+ } else {
9988
+ const table = tableOrTables;
9989
+ this.usersTable = table || users;
9990
+ this.userIdentitiesTable = userIdentities;
9991
+ this.userRolesTable = userRoles;
9992
+ this.rolesTable = roles;
9993
+ }
9994
+ }
9995
+ usersTable;
9996
+ userIdentitiesTable;
9997
+ userRolesTable;
9998
+ rolesTable;
9999
+ getQualifiedUsersTableName() {
10000
+ const name = getTableName$1(this.usersTable);
10001
+ const schema = getTableConfig(this.usersTable).schema || "public";
10002
+ return `"${schema}"."${name}"`;
10003
+ }
10004
+ mapRowToUser(row) {
10005
+ if (!row) return row;
10006
+ const id = row.id ?? row.uid;
10007
+ const email = row.email;
10008
+ const passwordHash = row.password_hash ?? row.passwordHash ?? null;
10009
+ const displayName = row.display_name ?? row.displayName ?? null;
10010
+ const photoUrl = row.photo_url ?? row.photoUrl ?? row.photoURL ?? null;
10011
+ const emailVerified = row.email_verified ?? row.emailVerified ?? false;
10012
+ const emailVerificationToken = row.email_verification_token ?? row.emailVerificationToken ?? null;
10013
+ const emailVerificationSentAt = row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null;
10014
+ const createdAt = row.created_at ?? row.createdAt;
10015
+ const updatedAt = row.updated_at ?? row.updatedAt;
10016
+ const metadata = {
10017
+ ...row.metadata || {}
10018
+ };
10019
+ const knownKeys = /* @__PURE__ */ new Set(["id", "uid", "email", "password_hash", "passwordHash", "display_name", "displayName", "photo_url", "photoUrl", "photoURL", "email_verified", "emailVerified", "email_verification_token", "emailVerificationToken", "email_verification_sent_at", "emailVerificationSentAt", "created_at", "createdAt", "updated_at", "updatedAt", "metadata"]);
10020
+ for (const [key, val] of Object.entries(row)) {
10021
+ if (!knownKeys.has(key)) {
10022
+ const camelKey = camelCase(key);
10023
+ metadata[camelKey] = val;
10024
+ }
10025
+ }
10026
+ return {
10027
+ id,
10028
+ email,
10029
+ passwordHash,
10030
+ displayName,
10031
+ photoUrl,
10032
+ emailVerified,
10033
+ emailVerificationToken,
10034
+ emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
10035
+ createdAt: createdAt ? new Date(createdAt) : /* @__PURE__ */ new Date(),
10036
+ updatedAt: updatedAt ? new Date(updatedAt) : /* @__PURE__ */ new Date(),
10037
+ metadata
10038
+ };
10039
+ }
10040
+ mapPayload(data) {
10041
+ if (!data) return {};
10042
+ const payload = {};
10043
+ const idKey = getColumnKey(this.usersTable, "id") || "id";
10044
+ const emailKey = getColumnKey(this.usersTable, "email") || "email";
10045
+ const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10046
+ const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
10047
+ const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
10048
+ const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10049
+ const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10050
+ const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10051
+ const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
10052
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10053
+ const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
10054
+ if ("id" in data) payload[idKey] = data.id;
10055
+ if ("email" in data) payload[emailKey] = data.email;
10056
+ if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
10057
+ if ("displayName" in data) payload[displayNameKey] = data.displayName;
10058
+ if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
10059
+ if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
10060
+ if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
10061
+ if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
10062
+ if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
10063
+ if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
10064
+ const metadata = {
10065
+ ...data.metadata || {}
10066
+ };
10067
+ const remainingMetadata = {};
10068
+ for (const [key, val] of Object.entries(metadata)) {
10069
+ const tableColKey = getColumnKey(this.usersTable, key);
10070
+ if (tableColKey && tableColKey !== idKey && tableColKey !== emailKey && tableColKey !== passwordHashKey && tableColKey !== displayNameKey && tableColKey !== photoUrlKey && tableColKey !== emailVerifiedKey && tableColKey !== emailVerificationTokenKey && tableColKey !== emailVerificationSentAtKey && tableColKey !== createdAtKey && tableColKey !== updatedAtKey && tableColKey !== metadataKey) {
10071
+ payload[tableColKey] = val;
10072
+ } else {
10073
+ remainingMetadata[key] = val;
10074
+ }
10075
+ }
10076
+ if (metadataKey in this.usersTable) {
10077
+ payload[metadataKey] = remainingMetadata;
10078
+ }
10079
+ return payload;
9662
10080
  }
9663
10081
  async createUser(data) {
9664
- const [user] = await this.db.insert(users).values(data).returning();
9665
- return user;
10082
+ const payload = this.mapPayload(data);
10083
+ const [row] = await this.db.insert(this.usersTable).values(payload).returning();
10084
+ return this.mapRowToUser(row);
9666
10085
  }
9667
10086
  async getUserById(id) {
9668
- const [user] = await this.db.select().from(users).where(eq(users.id, id));
9669
- return user || null;
10087
+ const idCol = getColumn(this.usersTable, "id");
10088
+ if (!idCol) return null;
10089
+ const [row] = await this.db.select().from(this.usersTable).where(eq(idCol, id));
10090
+ return row ? this.mapRowToUser(row) : null;
9670
10091
  }
9671
10092
  async getUserByEmail(email) {
9672
- const [user] = await this.db.select().from(users).where(eq(users.email, email.toLowerCase()));
9673
- return user || null;
10093
+ const emailCol = getColumn(this.usersTable, "email");
10094
+ if (!emailCol) return null;
10095
+ const [row] = await this.db.select().from(this.usersTable).where(eq(emailCol, email.toLowerCase()));
10096
+ return row ? this.mapRowToUser(row) : null;
9674
10097
  }
9675
10098
  async getUserByIdentity(provider, providerId) {
9676
- const result = await this.db.execute(sql`
9677
- SELECT u.*
9678
- FROM rebase.users u
9679
- INNER JOIN rebase.user_identities ui ON u.id = ui.user_id
9680
- WHERE ui.provider = ${provider} AND ui.provider_id = ${providerId}
9681
- LIMIT 1
9682
- `);
9683
- if (result.rows.length === 0) return null;
9684
- const row = result.rows[0];
9685
- return {
9686
- id: row.id,
9687
- email: row.email,
9688
- passwordHash: row.password_hash ?? null,
9689
- displayName: row.display_name ?? null,
9690
- photoUrl: row.photo_url ?? null,
9691
- emailVerified: row.email_verified ?? false,
9692
- emailVerificationToken: row.email_verification_token ?? null,
9693
- emailVerificationSentAt: row.email_verification_sent_at ?? null,
9694
- createdAt: row.created_at,
9695
- updatedAt: row.updated_at
9696
- };
10099
+ const userIdCol = getColumn(this.usersTable, "id");
10100
+ if (!userIdCol) return null;
10101
+ const result = await this.db.select({
10102
+ user: this.usersTable
10103
+ }).from(this.usersTable).innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.userId)).where(sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`).limit(1);
10104
+ if (result.length === 0) return null;
10105
+ return this.mapRowToUser(result[0].user);
9697
10106
  }
9698
10107
  async getUserIdentities(userId) {
10108
+ const schema = getTableConfig(this.userIdentitiesTable).schema || "public";
9699
10109
  const result = await this.db.execute(sql`
9700
10110
  SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
9701
- FROM rebase.user_identities
10111
+ FROM ${sql.raw(`"${schema}"."user_identities"`)}
9702
10112
  WHERE user_id = ${userId}
9703
10113
  `);
9704
10114
  return result.rows.map((row) => ({
@@ -9712,27 +10122,32 @@ class UserService {
9712
10122
  }));
9713
10123
  }
9714
10124
  async linkUserIdentity(userId, provider, providerId, profileData) {
9715
- await this.db.insert(userIdentities).values({
10125
+ await this.db.insert(this.userIdentitiesTable).values({
9716
10126
  userId,
9717
10127
  provider,
9718
10128
  providerId,
9719
10129
  profileData: profileData || null
9720
10130
  }).onConflictDoNothing({
9721
- target: [userIdentities.provider, userIdentities.providerId]
10131
+ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId]
9722
10132
  });
9723
10133
  }
9724
10134
  async updateUser(id, data) {
9725
- const [user] = await this.db.update(users).set({
9726
- ...data,
9727
- updatedAt: /* @__PURE__ */ new Date()
9728
- }).where(eq(users.id, id)).returning();
9729
- return user || null;
10135
+ const idCol = getColumn(this.usersTable, "id");
10136
+ if (!idCol) return null;
10137
+ const payload = this.mapPayload(data);
10138
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10139
+ payload[updatedAtKey] = /* @__PURE__ */ new Date();
10140
+ const [row] = await this.db.update(this.usersTable).set(payload).where(eq(idCol, id)).returning();
10141
+ return row ? this.mapRowToUser(row) : null;
9730
10142
  }
9731
10143
  async deleteUser(id) {
9732
- await this.db.delete(users).where(eq(users.id, id));
10144
+ const idCol = getColumn(this.usersTable, "id");
10145
+ if (!idCol) return;
10146
+ await this.db.delete(this.usersTable).where(eq(idCol, id));
9733
10147
  }
9734
10148
  async listUsers() {
9735
- return this.db.select().from(users);
10149
+ const rows = await this.db.select().from(this.usersTable);
10150
+ return rows.map((row) => this.mapRowToUser(row));
9736
10151
  }
9737
10152
  async listUsersPaginated(options) {
9738
10153
  const limit = options?.limit ?? 25;
@@ -9741,49 +10156,40 @@ class UserService {
9741
10156
  const orderBy = options?.orderBy || "createdAt";
9742
10157
  const orderDir = options?.orderDir || "desc";
9743
10158
  const roleId = options?.roleId;
9744
- const columnMap = {
9745
- email: "email",
9746
- displayName: "display_name",
9747
- createdAt: "created_at",
9748
- updatedAt: "updated_at",
9749
- provider: "provider"
9750
- };
9751
- const orderColumn = columnMap[orderBy] || "created_at";
10159
+ const orderCol = getColumn(this.usersTable, orderBy);
10160
+ const orderColumn = orderCol ? orderCol.name : "created_at";
9752
10161
  const direction = orderDir === "asc" ? sql`ASC` : sql`DESC`;
10162
+ const emailCol = getColumn(this.usersTable, "email");
10163
+ const emailColumn = emailCol ? emailCol.name : "email";
10164
+ const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
10165
+ const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
10166
+ const idCol = getColumn(this.usersTable, "id");
10167
+ const idColumn = idCol ? idCol.name : "id";
10168
+ const usersTableName = this.getQualifiedUsersTableName();
10169
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
9753
10170
  const conditions = [];
9754
10171
  if (roleId) {
9755
- conditions.push(sql`EXISTS (SELECT 1 FROM rebase.user_roles ur WHERE ur.user_id = users.id AND ur.role_id = ${roleId})`);
10172
+ conditions.push(sql`EXISTS (SELECT 1 FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
9756
10173
  }
9757
10174
  if (search) {
9758
10175
  const pattern = `%${search}%`;
9759
- conditions.push(sql`(email ILIKE ${pattern} OR display_name ILIKE ${pattern})`);
10176
+ conditions.push(sql`(${sql.raw(usersTableName)}.${sql.raw(emailColumn)} ILIKE ${pattern} OR ${sql.raw(usersTableName)}.${sql.raw(displayNameColumn)} ILIKE ${pattern})`);
9760
10177
  }
9761
10178
  const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;
9762
- const orderByClause = roleId ? sql`ORDER BY ${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY (SELECT count(*) FROM rebase.user_roles ur WHERE ur.user_id = users.id) DESC, ${sql.raw(orderColumn)} ${direction}`;
10179
+ const orderByClause = roleId ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY (SELECT count(*) FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)}) DESC, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;
9763
10180
  const countResult = await this.db.execute(sql`
9764
- SELECT count(*)::int as total FROM rebase.users
10181
+ SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}
9765
10182
  ${whereClause}
9766
10183
  `);
9767
10184
  const total = countResult.rows[0].total;
9768
10185
  const dataResult = await this.db.execute(sql`
9769
- SELECT * FROM rebase.users
10186
+ SELECT * FROM ${sql.raw(usersTableName)}
9770
10187
  ${whereClause}
9771
10188
  ${orderByClause}
9772
10189
  LIMIT ${limit} OFFSET ${offset}
9773
10190
  `);
9774
10191
  const rows = dataResult.rows;
9775
- const mappedUsers = rows.map((row) => ({
9776
- id: row.id,
9777
- email: row.email,
9778
- passwordHash: row.password_hash ?? row.passwordHash ?? null,
9779
- displayName: row.display_name ?? row.displayName ?? null,
9780
- photoUrl: row.photo_url ?? row.photoUrl ?? null,
9781
- emailVerified: row.email_verified ?? row.emailVerified ?? false,
9782
- emailVerificationToken: row.email_verification_token ?? row.emailVerificationToken ?? null,
9783
- emailVerificationSentAt: row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null,
9784
- createdAt: row.created_at ?? row.createdAt,
9785
- updatedAt: row.updated_at ?? row.updatedAt
9786
- }));
10192
+ const mappedUsers = rows.map((row) => this.mapRowToUser(row));
9787
10193
  return {
9788
10194
  users: mappedUsers,
9789
10195
  total,
@@ -9795,46 +10201,63 @@ class UserService {
9795
10201
  * Update user's password hash
9796
10202
  */
9797
10203
  async updatePassword(id, passwordHash) {
9798
- await this.db.update(users).set({
9799
- passwordHash,
9800
- updatedAt: /* @__PURE__ */ new Date()
9801
- }).where(eq(users.id, id));
10204
+ const idCol = getColumn(this.usersTable, "id");
10205
+ if (!idCol) return;
10206
+ const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10207
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10208
+ await this.db.update(this.usersTable).set({
10209
+ [passwordHashColKey]: passwordHash,
10210
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10211
+ }).where(eq(idCol, id));
9802
10212
  }
9803
10213
  /**
9804
10214
  * Set email verification status
9805
10215
  */
9806
10216
  async setEmailVerified(id, verified) {
9807
- await this.db.update(users).set({
9808
- emailVerified: verified,
9809
- emailVerificationToken: null,
9810
- updatedAt: /* @__PURE__ */ new Date()
9811
- }).where(eq(users.id, id));
10217
+ const idCol = getColumn(this.usersTable, "id");
10218
+ if (!idCol) return;
10219
+ const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10220
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10221
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10222
+ await this.db.update(this.usersTable).set({
10223
+ [emailVerifiedColKey]: verified,
10224
+ [emailVerificationTokenColKey]: null,
10225
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10226
+ }).where(eq(idCol, id));
9812
10227
  }
9813
10228
  /**
9814
10229
  * Set email verification token
9815
10230
  */
9816
10231
  async setVerificationToken(id, token) {
9817
- await this.db.update(users).set({
9818
- emailVerificationToken: token,
9819
- emailVerificationSentAt: token ? /* @__PURE__ */ new Date() : null,
9820
- updatedAt: /* @__PURE__ */ new Date()
9821
- }).where(eq(users.id, id));
10232
+ const idCol = getColumn(this.usersTable, "id");
10233
+ if (!idCol) return;
10234
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10235
+ const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10236
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10237
+ await this.db.update(this.usersTable).set({
10238
+ [emailVerificationTokenColKey]: token,
10239
+ [emailVerificationSentAtColKey]: token ? /* @__PURE__ */ new Date() : null,
10240
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10241
+ }).where(eq(idCol, id));
9822
10242
  }
9823
10243
  /**
9824
10244
  * Find user by email verification token
9825
10245
  */
9826
10246
  async getUserByVerificationToken(token) {
9827
- const [user] = await this.db.select().from(users).where(eq(users.emailVerificationToken, token));
9828
- return user || null;
10247
+ const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
10248
+ if (!tokenCol) return null;
10249
+ const [row] = await this.db.select().from(this.usersTable).where(eq(tokenCol, token));
10250
+ return row ? this.mapRowToUser(row) : null;
9829
10251
  }
9830
10252
  /**
9831
10253
  * Get roles for a user from database
9832
10254
  */
9833
10255
  async getUserRoles(userId) {
10256
+ const rolesSchema = getTableConfig(this.rolesTable).schema || "public";
9834
10257
  const result = await this.db.execute(sql`
9835
10258
  SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
9836
- FROM rebase.roles r
9837
- INNER JOIN rebase.user_roles ur ON r.id = ur.role_id
10259
+ FROM ${sql.raw(`"${rolesSchema}"."roles"`)} r
10260
+ INNER JOIN ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
9838
10261
  WHERE ur.user_id = ${userId}
9839
10262
  `);
9840
10263
  return result.rows.map((row) => ({
@@ -9857,10 +10280,11 @@ class UserService {
9857
10280
  * Set roles for a user
9858
10281
  */
9859
10282
  async setUserRoles(userId, roleIds) {
9860
- await this.db.execute(sql`DELETE FROM rebase.user_roles WHERE user_id = ${userId}`);
10283
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
10284
+ await this.db.execute(sql`DELETE FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
9861
10285
  for (const roleId of roleIds) {
9862
10286
  await this.db.execute(sql`
9863
- INSERT INTO rebase.user_roles (user_id, role_id)
10287
+ INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9864
10288
  VALUES (${userId}, ${roleId})
9865
10289
  ON CONFLICT DO NOTHING
9866
10290
  `);
@@ -9870,8 +10294,9 @@ class UserService {
9870
10294
  * Assign a specific role to new user
9871
10295
  */
9872
10296
  async assignDefaultRole(userId, roleId) {
10297
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
9873
10298
  await this.db.execute(sql`
9874
- INSERT INTO rebase.user_roles (user_id, role_id)
10299
+ INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9875
10300
  VALUES (${userId}, ${roleId})
9876
10301
  ON CONFLICT DO NOTHING
9877
10302
  `);
@@ -9890,13 +10315,25 @@ class UserService {
9890
10315
  }
9891
10316
  }
9892
10317
  class RoleService {
9893
- constructor(db) {
10318
+ constructor(db, tableOrTables) {
9894
10319
  this.db = db;
10320
+ if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
10321
+ this.rolesTable = tableOrTables.roles || roles;
10322
+ } else {
10323
+ this.rolesTable = tableOrTables || roles;
10324
+ }
10325
+ }
10326
+ rolesTable;
10327
+ getQualifiedRolesTableName() {
10328
+ const name = getTableName$1(this.rolesTable);
10329
+ const schema = getTableConfig(this.rolesTable).schema || "public";
10330
+ return `"${schema}"."${name}"`;
9895
10331
  }
9896
10332
  async getRoleById(id) {
10333
+ const tableName = this.getQualifiedRolesTableName();
9897
10334
  const result = await this.db.execute(sql`
9898
10335
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9899
- FROM rebase.roles
10336
+ FROM ${sql.raw(tableName)}
9900
10337
  WHERE id = ${id}
9901
10338
  `);
9902
10339
  if (result.rows.length === 0) return null;
@@ -9911,9 +10348,10 @@ class RoleService {
9911
10348
  };
9912
10349
  }
9913
10350
  async listRoles() {
10351
+ const tableName = this.getQualifiedRolesTableName();
9914
10352
  const result = await this.db.execute(sql`
9915
10353
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9916
- FROM rebase.roles
10354
+ FROM ${sql.raw(tableName)}
9917
10355
  ORDER BY name
9918
10356
  `);
9919
10357
  return result.rows.map((row) => ({
@@ -9926,8 +10364,9 @@ class RoleService {
9926
10364
  }));
9927
10365
  }
9928
10366
  async createRole(data) {
10367
+ const tableName = this.getQualifiedRolesTableName();
9929
10368
  const result = await this.db.execute(sql`
9930
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, collection_permissions, config)
10369
+ INSERT INTO ${sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
9931
10370
  VALUES (
9932
10371
  ${data.id},
9933
10372
  ${data.name},
@@ -9951,8 +10390,9 @@ class RoleService {
9951
10390
  async updateRole(id, data) {
9952
10391
  const existing = await this.getRoleById(id);
9953
10392
  if (!existing) return null;
10393
+ const tableName = this.getQualifiedRolesTableName();
9954
10394
  await this.db.execute(sql`
9955
- UPDATE rebase.roles
10395
+ UPDATE ${sql.raw(tableName)}
9956
10396
  SET
9957
10397
  name = ${data.name ?? existing.name},
9958
10398
  is_admin = ${data.isAdmin ?? existing.isAdmin},
@@ -9964,23 +10404,36 @@ class RoleService {
9964
10404
  return this.getRoleById(id);
9965
10405
  }
9966
10406
  async deleteRole(id) {
9967
- await this.db.execute(sql`DELETE FROM rebase.roles WHERE id = ${id}`);
10407
+ const tableName = this.getQualifiedRolesTableName();
10408
+ await this.db.execute(sql`DELETE FROM ${sql.raw(tableName)} WHERE id = ${id}`);
9968
10409
  }
9969
10410
  }
9970
10411
  class RefreshTokenService {
9971
- constructor(db) {
10412
+ constructor(db, tableOrTables) {
9972
10413
  this.db = db;
10414
+ if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) {
10415
+ this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
10416
+ } else {
10417
+ this.refreshTokensTable = tableOrTables || refreshTokens;
10418
+ }
10419
+ }
10420
+ refreshTokensTable;
10421
+ getQualifiedRefreshTokensTableName() {
10422
+ const name = getTableName$1(this.refreshTokensTable);
10423
+ const schema = getTableConfig(this.refreshTokensTable).schema || "public";
10424
+ return `"${schema}"."${name}"`;
9973
10425
  }
9974
10426
  async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
9975
10427
  const safeUserAgent = userAgent || "";
9976
10428
  const safeIpAddress = ipAddress || "";
10429
+ const tableName = this.getQualifiedRefreshTokensTableName();
9977
10430
  await this.db.execute(sql`
9978
- DELETE FROM rebase.refresh_tokens
10431
+ DELETE FROM ${sql.raw(tableName)}
9979
10432
  WHERE user_id = ${userId}
9980
10433
  AND user_agent = ${safeUserAgent}
9981
10434
  AND ip_address = ${safeIpAddress}
9982
10435
  `);
9983
- await this.db.insert(refreshTokens).values({
10436
+ await this.db.insert(this.refreshTokensTable).values({
9984
10437
  userId,
9985
10438
  tokenHash,
9986
10439
  expiresAt,
@@ -9990,51 +10443,63 @@ class RefreshTokenService {
9990
10443
  }
9991
10444
  async findByHash(tokenHash) {
9992
10445
  const [token] = await this.db.select({
9993
- id: refreshTokens.id,
9994
- userId: refreshTokens.userId,
9995
- tokenHash: refreshTokens.tokenHash,
9996
- expiresAt: refreshTokens.expiresAt,
9997
- createdAt: refreshTokens.createdAt,
9998
- userAgent: refreshTokens.userAgent,
9999
- ipAddress: refreshTokens.ipAddress
10000
- }).from(refreshTokens).where(eq(refreshTokens.tokenHash, tokenHash));
10446
+ id: this.refreshTokensTable.id,
10447
+ userId: this.refreshTokensTable.userId,
10448
+ tokenHash: this.refreshTokensTable.tokenHash,
10449
+ expiresAt: this.refreshTokensTable.expiresAt,
10450
+ createdAt: this.refreshTokensTable.createdAt,
10451
+ userAgent: this.refreshTokensTable.userAgent,
10452
+ ipAddress: this.refreshTokensTable.ipAddress
10453
+ }).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
10001
10454
  return token || null;
10002
10455
  }
10003
10456
  async deleteByHash(tokenHash) {
10004
- await this.db.delete(refreshTokens).where(eq(refreshTokens.tokenHash, tokenHash));
10457
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
10005
10458
  }
10006
10459
  async deleteAllForUser(userId) {
10007
- await this.db.delete(refreshTokens).where(eq(refreshTokens.userId, userId));
10460
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId));
10008
10461
  }
10009
10462
  async listForUser(userId) {
10010
10463
  const tokens = await this.db.select({
10011
- id: refreshTokens.id,
10012
- userId: refreshTokens.userId,
10013
- tokenHash: refreshTokens.tokenHash,
10014
- expiresAt: refreshTokens.expiresAt,
10015
- createdAt: refreshTokens.createdAt,
10016
- userAgent: refreshTokens.userAgent,
10017
- ipAddress: refreshTokens.ipAddress
10018
- }).from(refreshTokens).where(eq(refreshTokens.userId, userId)).orderBy(refreshTokens.createdAt);
10464
+ id: this.refreshTokensTable.id,
10465
+ userId: this.refreshTokensTable.userId,
10466
+ tokenHash: this.refreshTokensTable.tokenHash,
10467
+ expiresAt: this.refreshTokensTable.expiresAt,
10468
+ createdAt: this.refreshTokensTable.createdAt,
10469
+ userAgent: this.refreshTokensTable.userAgent,
10470
+ ipAddress: this.refreshTokensTable.ipAddress
10471
+ }).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId)).orderBy(this.refreshTokensTable.createdAt);
10019
10472
  return tokens;
10020
10473
  }
10021
10474
  async deleteById(id, userId) {
10022
- await this.db.delete(refreshTokens).where(sql`${refreshTokens.id} = ${id} AND ${refreshTokens.userId} = ${userId}`);
10475
+ await this.db.delete(this.refreshTokensTable).where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
10023
10476
  }
10024
10477
  }
10025
10478
  class PasswordResetTokenService {
10026
- constructor(db) {
10479
+ constructor(db, tableOrTables) {
10027
10480
  this.db = db;
10481
+ if (tableOrTables && (tableOrTables.passwordResetTokens || tableOrTables.users)) {
10482
+ this.passwordResetTokensTable = tableOrTables.passwordResetTokens || passwordResetTokens;
10483
+ } else {
10484
+ this.passwordResetTokensTable = tableOrTables || passwordResetTokens;
10485
+ }
10486
+ }
10487
+ passwordResetTokensTable;
10488
+ getQualifiedPasswordResetTokensTableName() {
10489
+ const name = getTableName$1(this.passwordResetTokensTable);
10490
+ const schema = getTableConfig(this.passwordResetTokensTable).schema || "public";
10491
+ return `"${schema}"."${name}"`;
10028
10492
  }
10029
10493
  /**
10030
10494
  * Create a password reset token
10031
10495
  */
10032
10496
  async createToken(userId, tokenHash, expiresAt) {
10497
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10033
10498
  await this.db.execute(sql`
10034
- DELETE FROM rebase.password_reset_tokens
10499
+ DELETE FROM ${sql.raw(tableName)}
10035
10500
  WHERE user_id = ${userId} AND used_at IS NULL
10036
10501
  `);
10037
- await this.db.insert(passwordResetTokens).values({
10502
+ await this.db.insert(this.passwordResetTokensTable).values({
10038
10503
  userId,
10039
10504
  tokenHash,
10040
10505
  expiresAt
@@ -10045,13 +10510,14 @@ class PasswordResetTokenService {
10045
10510
  */
10046
10511
  async findValidByHash(tokenHash) {
10047
10512
  const [token] = await this.db.select({
10048
- userId: passwordResetTokens.userId,
10049
- expiresAt: passwordResetTokens.expiresAt
10050
- }).from(passwordResetTokens).where(eq(passwordResetTokens.tokenHash, tokenHash));
10513
+ userId: this.passwordResetTokensTable.userId,
10514
+ expiresAt: this.passwordResetTokensTable.expiresAt
10515
+ }).from(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10051
10516
  if (!token) return null;
10517
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10052
10518
  const result = await this.db.execute(sql`
10053
10519
  SELECT user_id, expires_at
10054
- FROM rebase.password_reset_tokens
10520
+ FROM ${sql.raw(tableName)}
10055
10521
  WHERE token_hash = ${tokenHash}
10056
10522
  AND used_at IS NULL
10057
10523
  AND expires_at > NOW()
@@ -10067,31 +10533,32 @@ class PasswordResetTokenService {
10067
10533
  * Mark token as used
10068
10534
  */
10069
10535
  async markAsUsed(tokenHash) {
10070
- await this.db.update(passwordResetTokens).set({
10536
+ await this.db.update(this.passwordResetTokensTable).set({
10071
10537
  usedAt: /* @__PURE__ */ new Date()
10072
- }).where(eq(passwordResetTokens.tokenHash, tokenHash));
10538
+ }).where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10073
10539
  }
10074
10540
  /**
10075
10541
  * Delete all tokens for a user
10076
10542
  */
10077
10543
  async deleteAllForUser(userId) {
10078
- await this.db.delete(passwordResetTokens).where(eq(passwordResetTokens.userId, userId));
10544
+ await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.userId, userId));
10079
10545
  }
10080
10546
  /**
10081
10547
  * Clean up expired tokens
10082
10548
  */
10083
10549
  async deleteExpired() {
10550
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10084
10551
  await this.db.execute(sql`
10085
- DELETE FROM rebase.password_reset_tokens
10552
+ DELETE FROM ${sql.raw(tableName)}
10086
10553
  WHERE expires_at < NOW()
10087
10554
  `);
10088
10555
  }
10089
10556
  }
10090
10557
  class PostgresTokenRepository {
10091
- constructor(db) {
10558
+ constructor(db, tableOrTables) {
10092
10559
  this.db = db;
10093
- this.refreshTokenService = new RefreshTokenService(db);
10094
- this.passwordResetTokenService = new PasswordResetTokenService(db);
10560
+ this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
10561
+ this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
10095
10562
  }
10096
10563
  refreshTokenService;
10097
10564
  passwordResetTokenService;
@@ -10132,11 +10599,11 @@ class PostgresTokenRepository {
10132
10599
  }
10133
10600
  }
10134
10601
  class PostgresAuthRepository {
10135
- constructor(db) {
10602
+ constructor(db, tableOrTables) {
10136
10603
  this.db = db;
10137
- this.userService = new UserService(db);
10138
- this.roleService = new RoleService(db);
10139
- this.tokenRepository = new PostgresTokenRepository(db);
10604
+ this.userService = new UserService(db, tableOrTables);
10605
+ this.roleService = new RoleService(db, tableOrTables);
10606
+ this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
10140
10607
  }
10141
10608
  userService;
10142
10609
  roleService;
@@ -10197,8 +10664,7 @@ class PostgresAuthRepository {
10197
10664
  await this.userService.assignDefaultRole(userId, roleId);
10198
10665
  }
10199
10666
  async getUserWithRoles(userId) {
10200
- const result = await this.userService.getUserWithRoles(userId);
10201
- return result;
10667
+ return this.userService.getUserWithRoles(userId);
10202
10668
  }
10203
10669
  // Role operations (delegate to RoleService)
10204
10670
  async getRoleById(id) {
@@ -10386,6 +10852,24 @@ class HistoryService {
10386
10852
  return result.rowCount ?? 0;
10387
10853
  }
10388
10854
  }
10855
+ function deepEqual(a, b) {
10856
+ if (a === b) return true;
10857
+ if (a == null || b == null) return false;
10858
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
10859
+ if (Array.isArray(a) && Array.isArray(b)) {
10860
+ if (a.length !== b.length) return false;
10861
+ return a.every((v, i) => deepEqual(v, b[i]));
10862
+ }
10863
+ if (typeof a === "object" && typeof b === "object") {
10864
+ const aObj = a;
10865
+ const bObj = b;
10866
+ const aKeys = Object.keys(aObj);
10867
+ const bKeys = Object.keys(bObj);
10868
+ if (aKeys.length !== bKeys.length) return false;
10869
+ return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
10870
+ }
10871
+ return false;
10872
+ }
10389
10873
  function findChangedFields(oldValues, newValues) {
10390
10874
  const changed = [];
10391
10875
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
@@ -10395,7 +10879,7 @@ function findChangedFields(oldValues, newValues) {
10395
10879
  if (key.startsWith("__")) continue;
10396
10880
  if (oldVal !== newVal) {
10397
10881
  if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
10398
- if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
10882
+ if (!deepEqual(oldVal, newVal)) {
10399
10883
  changed.push(key);
10400
10884
  }
10401
10885
  } else {
@@ -10513,14 +10997,32 @@ function createPostgresBootstrapper(pgConfig) {
10513
10997
  if (!authConfig) return void 0;
10514
10998
  const internals = driverResult.internals;
10515
10999
  const db = internals.db;
10516
- await ensureAuthTablesExist(db);
11000
+ const registry = internals.registry;
11001
+ await ensureAuthTablesExist(db, registry);
10517
11002
  let emailService;
10518
11003
  if (authConfig.email) {
10519
11004
  emailService = createEmailService(authConfig.email);
10520
11005
  }
10521
- const userService = new UserService(db);
10522
- const roleService = new RoleService(db);
10523
- const authRepository = new PostgresAuthRepository(db);
11006
+ const customUsersTable = registry?.getTable("users");
11007
+ const customRolesTable = registry?.getTable("roles");
11008
+ let usersSchemaName = "rebase";
11009
+ let rolesSchemaName = "rebase";
11010
+ if (customUsersTable) {
11011
+ usersSchemaName = getTableConfig(customUsersTable).schema || "public";
11012
+ }
11013
+ if (customRolesTable) {
11014
+ rolesSchemaName = getTableConfig(customRolesTable).schema || "public";
11015
+ }
11016
+ const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
11017
+ if (customUsersTable) {
11018
+ authTables.users = customUsersTable;
11019
+ }
11020
+ if (customRolesTable) {
11021
+ authTables.roles = customRolesTable;
11022
+ }
11023
+ const userService = new UserService(db, authTables);
11024
+ const roleService = new RoleService(db, authTables);
11025
+ const authRepository = new PostgresAuthRepository(db, authTables);
10524
11026
  return {
10525
11027
  userService,
10526
11028
  roleService,
@@ -10552,11 +11054,54 @@ function createPostgresBootstrapper(pgConfig) {
10552
11054
  },
10553
11055
  mountRoutes(app, basePath, driverResult) {
10554
11056
  },
10555
- async initializeWebsockets(server, realtimeService, driver, config) {
11057
+ async initializeWebsockets(server, realtimeService, driver, config, adapter) {
10556
11058
  const {
10557
11059
  createPostgresWebSocket: createPostgresWebSocket2
10558
11060
  } = await Promise.resolve().then(() => websocket);
10559
- createPostgresWebSocket2(server, realtimeService, driver, config);
11061
+ createPostgresWebSocket2(server, realtimeService, driver, config, adapter);
11062
+ }
11063
+ };
11064
+ }
11065
+ function createPostgresAdapter(pgConfig) {
11066
+ const bootstrapper = createPostgresBootstrapper(pgConfig);
11067
+ return {
11068
+ type: bootstrapper.type,
11069
+ async initializeDriver(config) {
11070
+ return bootstrapper.initializeDriver(config);
11071
+ },
11072
+ async initializeRealtime(driverResult) {
11073
+ if (bootstrapper.initializeRealtime) {
11074
+ return bootstrapper.initializeRealtime({}, driverResult);
11075
+ }
11076
+ return void 0;
11077
+ },
11078
+ async initializeAuth(config, driverResult) {
11079
+ if (bootstrapper.initializeAuth) {
11080
+ return bootstrapper.initializeAuth(config, driverResult);
11081
+ }
11082
+ return void 0;
11083
+ },
11084
+ async initializeHistory(config, driverResult) {
11085
+ if (bootstrapper.initializeHistory) {
11086
+ return bootstrapper.initializeHistory(config, driverResult);
11087
+ }
11088
+ return void 0;
11089
+ },
11090
+ initializeWebsockets(server, realtimeService, driver, config) {
11091
+ if (bootstrapper.initializeWebsockets) {
11092
+ return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
11093
+ }
11094
+ },
11095
+ getAdmin(driverResult) {
11096
+ if (bootstrapper.getAdmin) {
11097
+ return bootstrapper.getAdmin(driverResult);
11098
+ }
11099
+ return void 0;
11100
+ },
11101
+ mountRoutes(app, basePath, driverResult) {
11102
+ if (bootstrapper.mountRoutes) {
11103
+ bootstrapper.mountRoutes(app, basePath, driverResult);
11104
+ }
10560
11105
  }
10561
11106
  };
10562
11107
  }
@@ -10571,6 +11116,8 @@ export {
10571
11116
  PostgresRealtimeProvider,
10572
11117
  RealtimeService,
10573
11118
  appConfig,
11119
+ createAuthSchema,
11120
+ createPostgresAdapter,
10574
11121
  createPostgresBootstrapper,
10575
11122
  createPostgresDatabaseConnection,
10576
11123
  createPostgresWebSocket,
@@ -10587,6 +11134,7 @@ export {
10587
11134
  userRoles,
10588
11135
  userRolesRelations,
10589
11136
  users,
10590
- usersRelations
11137
+ usersRelations,
11138
+ usersSchema
10591
11139
  };
10592
11140
  //# sourceMappingURL=index.es.js.map