@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.umd.js CHANGED
@@ -59,6 +59,12 @@
59
59
  connectionString
60
60
  };
61
61
  }
62
+ class Vector {
63
+ value;
64
+ constructor(value) {
65
+ this.value = value;
66
+ }
67
+ }
62
68
  function isPostgresCollection(collection) {
63
69
  return !collection.driver || collection.driver === "postgres";
64
70
  }
@@ -135,16 +141,25 @@
135
141
  const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
136
142
  const snakeCaseRegex = tokenizeRegex;
137
143
  const toSnakeCase = (str) => {
144
+ if (!str || typeof str !== "string") return "";
138
145
  const regExpMatchArray = str.match(snakeCaseRegex);
139
146
  if (!regExpMatchArray) return "";
140
147
  return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
141
148
  };
149
+ function camelCase(str) {
150
+ if (!str) return "";
151
+ if (str.length === 1) return str.toLowerCase();
152
+ const parts = str.split(/[-_ ]+/).filter(Boolean);
153
+ if (parts.length === 0) return "";
154
+ return parts[0].toLowerCase() + // Transform remaining parts to have first letter uppercase
155
+ parts.slice(1).map((part) => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase()).join("");
156
+ }
142
157
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
143
158
  function commonjsRequire(path2) {
144
159
  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.');
145
160
  }
146
161
  var object_hash = { exports: {} };
147
- (function(module2, exports$1) {
162
+ (function(module2, exports3) {
148
163
  !function(e) {
149
164
  module2.exports = e();
150
165
  }(function() {
@@ -1263,14 +1278,56 @@
1263
1278
  }
1264
1279
  return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1265
1280
  };
1266
- function sanitizeRelation(relation, sourceCollection) {
1281
+ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1267
1282
  if (!relation.target) {
1268
1283
  throw new Error("Relation is missing a `target` collection.");
1269
1284
  }
1270
- const targetCollection = relation.target();
1285
+ const rawTarget = relation.target;
1286
+ let targetCollection;
1287
+ if (typeof rawTarget === "string") {
1288
+ if (resolveCollection) {
1289
+ targetCollection = resolveCollection(rawTarget);
1290
+ }
1291
+ if (!targetCollection) {
1292
+ targetCollection = {
1293
+ slug: rawTarget,
1294
+ name: rawTarget
1295
+ };
1296
+ }
1297
+ } else if (typeof rawTarget === "function") {
1298
+ const evaluated = rawTarget();
1299
+ if (typeof evaluated === "string") {
1300
+ if (resolveCollection) {
1301
+ targetCollection = resolveCollection(evaluated);
1302
+ }
1303
+ if (!targetCollection) {
1304
+ targetCollection = {
1305
+ slug: evaluated,
1306
+ name: evaluated
1307
+ };
1308
+ }
1309
+ } else {
1310
+ targetCollection = evaluated;
1311
+ }
1312
+ }
1313
+ if (!targetCollection) {
1314
+ throw new Error("Relation is missing a valid `target` collection.");
1315
+ }
1271
1316
  const newRelation = {
1272
1317
  ...relation
1273
1318
  };
1319
+ newRelation.target = () => {
1320
+ if (typeof rawTarget === "string") {
1321
+ return resolveCollection && resolveCollection(rawTarget) || targetCollection;
1322
+ } else if (typeof rawTarget === "function") {
1323
+ const evaluated = rawTarget();
1324
+ if (typeof evaluated === "string") {
1325
+ return resolveCollection && resolveCollection(evaluated) || targetCollection;
1326
+ }
1327
+ return evaluated;
1328
+ }
1329
+ return targetCollection;
1330
+ };
1274
1331
  if (!newRelation.relationName) {
1275
1332
  newRelation.relationName = toSnakeCase(targetCollection.slug);
1276
1333
  }
@@ -1323,6 +1380,17 @@
1323
1380
  break;
1324
1381
  }
1325
1382
  }
1383
+ if (!isManyToManyInverse && targetCollection.properties) {
1384
+ for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
1385
+ if (prop.type !== "relation") continue;
1386
+ const relProp = prop;
1387
+ const relName = relProp.relationName || propKey;
1388
+ if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
1389
+ isManyToManyInverse = true;
1390
+ break;
1391
+ }
1392
+ }
1393
+ }
1326
1394
  } catch (e) {
1327
1395
  }
1328
1396
  }
@@ -1445,7 +1513,7 @@
1445
1513
  return void 0;
1446
1514
  }
1447
1515
  var logic = { exports: {} };
1448
- (function(module2, exports$1) {
1516
+ (function(module2, exports3) {
1449
1517
  (function(root, factory) {
1450
1518
  {
1451
1519
  module2.exports = factory();
@@ -2202,7 +2270,7 @@
2202
2270
  "[object Uint32Array]": areTypedArraysEqual2
2203
2271
  };
2204
2272
  }
2205
- const deepEqual = createCustomEqual();
2273
+ const deepEqual$1 = createCustomEqual();
2206
2274
  createCustomEqual({ strict: true });
2207
2275
  createCustomEqual({ circular: true });
2208
2276
  createCustomEqual({
@@ -2271,10 +2339,16 @@
2271
2339
  */
2272
2340
  registerMultiple(collections) {
2273
2341
  const rawSnapshot = collections.map((c) => removeFunctions(c));
2274
- if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
2342
+ if (this.lastRawInputSnapshot && deepEqual$1(this.lastRawInputSnapshot, rawSnapshot)) {
2275
2343
  return false;
2276
2344
  }
2277
2345
  this.reset();
2346
+ collections.forEach((c) => {
2347
+ if (c.slug) {
2348
+ this.collectionsBySlug.set(c.slug, c);
2349
+ }
2350
+ this.collectionsByTableName.set(getTableName(c), c);
2351
+ });
2278
2352
  const normalizedCollections = collections.map((c) => this.normalizeCollection({
2279
2353
  ...c
2280
2354
  }));
@@ -2353,7 +2427,7 @@
2353
2427
  if (getDataSourceCapabilities(result.driver).supportsRelations) {
2354
2428
  mergedRelations = mergedRelationsRaw.map((r) => {
2355
2429
  try {
2356
- return sanitizeRelation(r, result);
2430
+ return sanitizeRelation(r, result, (slug) => this.get(slug));
2357
2431
  } catch {
2358
2432
  return r;
2359
2433
  }
@@ -2792,8 +2866,14 @@
2792
2866
  static buildSingleFilterCondition(column, op, value) {
2793
2867
  switch (op) {
2794
2868
  case "==":
2869
+ if (value === null || value === void 0) {
2870
+ return drizzleOrm.sql`${column} IS NULL`;
2871
+ }
2795
2872
  return drizzleOrm.eq(column, value);
2796
2873
  case "!=":
2874
+ if (value === null || value === void 0) {
2875
+ return drizzleOrm.sql`${column} IS NOT NULL`;
2876
+ }
2797
2877
  return drizzleOrm.sql`${column} != ${value}`;
2798
2878
  case ">":
2799
2879
  return drizzleOrm.sql`${column} > ${value}`;
@@ -3124,7 +3204,10 @@
3124
3204
  if (p.type === "string" && !p.enum && p.isId !== "uuid") {
3125
3205
  const fieldColumn = table[key];
3126
3206
  if (fieldColumn) {
3127
- searchConditions.push(drizzleOrm.ilike(fieldColumn, `%${searchString}%`));
3207
+ const supportsILike = fieldColumn instanceof pgCore.PgVarchar || fieldColumn instanceof pgCore.PgText || fieldColumn instanceof pgCore.PgChar || fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn);
3208
+ if (supportsILike) {
3209
+ searchConditions.push(drizzleOrm.ilike(fieldColumn, `%${searchString}%`));
3210
+ }
3128
3211
  }
3129
3212
  }
3130
3213
  }
@@ -3597,6 +3680,31 @@
3597
3680
  return result;
3598
3681
  }
3599
3682
  return value;
3683
+ case "vector": {
3684
+ if (value instanceof Vector) {
3685
+ return value.value;
3686
+ }
3687
+ if (value && typeof value === "object" && "value" in value && Array.isArray(value.value)) {
3688
+ return value.value.map(Number);
3689
+ }
3690
+ if (Array.isArray(value)) {
3691
+ return value.map(Number);
3692
+ }
3693
+ return value;
3694
+ }
3695
+ case "binary":
3696
+ if (typeof value === "string") {
3697
+ if (value.startsWith("data:application/octet-stream;base64,")) {
3698
+ const base64Data = value.split(",")[1];
3699
+ if (base64Data) {
3700
+ return Buffer.from(base64Data, "base64");
3701
+ }
3702
+ }
3703
+ }
3704
+ if (Buffer.isBuffer(value)) {
3705
+ return value;
3706
+ }
3707
+ return value;
3600
3708
  case "string":
3601
3709
  if (typeof value === "string") {
3602
3710
  if (value.startsWith("data:application/octet-stream;base64,")) {
@@ -3741,6 +3849,21 @@
3741
3849
  return value;
3742
3850
  }
3743
3851
  switch (property.type) {
3852
+ case "binary": {
3853
+ let buf = null;
3854
+ if (Buffer.isBuffer(value)) {
3855
+ buf = value;
3856
+ } else if (typeof value === "object" && value !== null) {
3857
+ const rawVal = value;
3858
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
3859
+ buf = Buffer.from(rawVal.data);
3860
+ }
3861
+ }
3862
+ if (buf) {
3863
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
3864
+ }
3865
+ return value;
3866
+ }
3744
3867
  case "string": {
3745
3868
  if (typeof value === "string") return value;
3746
3869
  let isBuffer = false;
@@ -3748,9 +3871,12 @@
3748
3871
  if (Buffer.isBuffer(value)) {
3749
3872
  isBuffer = true;
3750
3873
  buf = value;
3751
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3752
- isBuffer = true;
3753
- buf = Buffer.from(value.data);
3874
+ } else if (typeof value === "object" && value !== null) {
3875
+ const rawVal = value;
3876
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
3877
+ isBuffer = true;
3878
+ buf = Buffer.from(rawVal.data);
3879
+ }
3754
3880
  }
3755
3881
  if (isBuffer && buf) {
3756
3882
  let isPrintable = true;
@@ -3837,6 +3963,25 @@
3837
3963
  return isNaN(parsed) ? null : parsed;
3838
3964
  }
3839
3965
  return value;
3966
+ case "vector": {
3967
+ let nums = [];
3968
+ if (typeof value === "string") {
3969
+ nums = value.slice(1, -1).split(",").map(Number);
3970
+ } else if (Array.isArray(value)) {
3971
+ nums = value.map(Number);
3972
+ } else if (value instanceof Vector) {
3973
+ nums = value.value;
3974
+ } else if (typeof value === "object" && value !== null && "value" in value) {
3975
+ const valObj = value;
3976
+ if (Array.isArray(valObj.value)) {
3977
+ nums = valObj.value.map(Number);
3978
+ }
3979
+ }
3980
+ return {
3981
+ __type: "Vector",
3982
+ value: nums
3983
+ };
3984
+ }
3840
3985
  case "date": {
3841
3986
  let date;
3842
3987
  if (value instanceof Date) {
@@ -3861,9 +4006,12 @@
3861
4006
  if (Buffer.isBuffer(value)) {
3862
4007
  isBuffer = true;
3863
4008
  buf = value;
3864
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3865
- isBuffer = true;
3866
- buf = Buffer.from(value.data);
4009
+ } else if (typeof value === "object" && value !== null) {
4010
+ const rawVal = value;
4011
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
4012
+ isBuffer = true;
4013
+ buf = Buffer.from(rawVal.data);
4014
+ }
3867
4015
  }
3868
4016
  if (isBuffer && buf) {
3869
4017
  let isPrintable = true;
@@ -4653,7 +4801,7 @@
4653
4801
  if (parentFKValue !== null && parentFKValue !== void 0) {
4654
4802
  await tx.update(targetTable).set({
4655
4803
  [targetFKColName]: null
4656
- }).where(drizzleOrm.eq(targetFKCol, parentFKValue));
4804
+ }).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
4657
4805
  }
4658
4806
  continue;
4659
4807
  }
@@ -4662,7 +4810,7 @@
4662
4810
  if (parentFKValue !== null && parentFKValue !== void 0) {
4663
4811
  await tx.update(targetTable).set({
4664
4812
  [targetFKColName]: null
4665
- }).where(drizzleOrm.eq(targetFKCol, parentFKValue));
4813
+ }).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
4666
4814
  } else {
4667
4815
  console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
4668
4816
  continue;
@@ -6739,23 +6887,33 @@
6739
6887
  client: this.client
6740
6888
  };
6741
6889
  if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
6890
+ let preventDefault = false;
6742
6891
  if (callbacks?.beforeDelete) {
6743
- await callbacks.beforeDelete({
6892
+ const result = await callbacks.beforeDelete({
6744
6893
  collection: resolvedCollection,
6745
6894
  path: entity.path,
6746
6895
  entityId: entity.id,
6747
6896
  entity,
6748
6897
  context: contextForCallback
6749
6898
  });
6899
+ if (result === false) {
6900
+ preventDefault = true;
6901
+ }
6750
6902
  }
6751
6903
  if (propertyCallbacks?.beforeDelete) {
6752
- await propertyCallbacks.beforeDelete({
6904
+ const result = await propertyCallbacks.beforeDelete({
6753
6905
  collection: resolvedCollection,
6754
6906
  path: entity.path,
6755
6907
  entityId: entity.id,
6756
6908
  entity,
6757
6909
  context: contextForCallback
6758
6910
  });
6911
+ if (result === false) {
6912
+ preventDefault = true;
6913
+ }
6914
+ }
6915
+ if (preventDefault) {
6916
+ return;
6759
6917
  }
6760
6918
  }
6761
6919
  await this.entityService.deleteEntity(entity.path, entity.id, entity.databaseId || resolvedCollection?.databaseId);
@@ -6828,7 +6986,17 @@
6828
6986
  }
6829
6987
  const targetDb = this.getTargetDb(options?.database);
6830
6988
  try {
6831
- if (options?.role) {
6989
+ let needsRoleSwitch = false;
6990
+ if (options?.role && process.env.DISABLE_DB_ROLE_SWITCHING !== "true") {
6991
+ try {
6992
+ const currentRoleResult = await targetDb.execute(drizzleOrm.sql.raw("SELECT current_user AS role"));
6993
+ const currentRole = currentRoleResult.rows?.[0]?.role;
6994
+ needsRoleSwitch = !!currentRole && currentRole !== options.role;
6995
+ } catch {
6996
+ needsRoleSwitch = true;
6997
+ }
6998
+ }
6999
+ if (needsRoleSwitch && options?.role) {
6832
7000
  const safeRole = options.role.replace(/"/g, '""');
6833
7001
  return await targetDb.transaction(async (tx) => {
6834
7002
  await tx.execute(drizzleOrm.sql.raw(`SET LOCAL ROLE "${safeRole}"`));
@@ -6866,7 +7034,7 @@
6866
7034
  return databases;
6867
7035
  }
6868
7036
  async fetchAvailableRoles() {
6869
- const result = await this.executeSql("SELECT rolname FROM pg_roles;");
7037
+ const result = await this.executeSql("SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;");
6870
7038
  return result.map((r) => r.rolname);
6871
7039
  }
6872
7040
  async fetchCurrentDatabase() {
@@ -7035,6 +7203,21 @@
7035
7203
  * Typed admin capabilities — delegates to the base driver.
7036
7204
  */
7037
7205
  admin;
7206
+ get restFetchService() {
7207
+ if (!this.delegate.restFetchService) return void 0;
7208
+ return {
7209
+ fetchCollectionForRest: async (collectionPath, options, include) => {
7210
+ return this.withTransaction(async (delegate) => {
7211
+ return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);
7212
+ });
7213
+ },
7214
+ fetchEntityForRest: async (collectionPath, entityId, include, databaseId) => {
7215
+ return this.withTransaction(async (delegate) => {
7216
+ return delegate.restFetchService.fetchEntityForRest(collectionPath, entityId, include, databaseId);
7217
+ });
7218
+ }
7219
+ };
7220
+ }
7038
7221
  async withTransaction(operation) {
7039
7222
  const pendingNotifications = [];
7040
7223
  const result = await this.delegate.db.transaction(async (tx) => {
@@ -7189,113 +7372,140 @@
7189
7372
  this.pools.clear();
7190
7373
  }
7191
7374
  }
7192
- const rebaseSchema = pgCore.pgSchema("rebase");
7193
- const users = rebaseSchema.table("users", {
7194
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7195
- email: pgCore.varchar("email", {
7196
- length: 255
7197
- }).notNull().unique(),
7198
- passwordHash: pgCore.varchar("password_hash", {
7199
- length: 255
7200
- }),
7201
- // NULL for OAuth-only users
7202
- displayName: pgCore.varchar("display_name", {
7203
- length: 255
7204
- }),
7205
- photoUrl: pgCore.varchar("photo_url", {
7206
- length: 500
7207
- }),
7208
- emailVerified: pgCore.boolean("email_verified").default(false).notNull(),
7209
- emailVerificationToken: pgCore.varchar("email_verification_token", {
7210
- length: 255
7211
- }),
7212
- emailVerificationSentAt: pgCore.timestamp("email_verification_sent_at"),
7213
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7214
- updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7215
- });
7216
- const roles = rebaseSchema.table("roles", {
7217
- id: pgCore.varchar("id", {
7218
- length: 50
7219
- }).primaryKey(),
7220
- // 'admin', 'editor', 'viewer'
7221
- name: pgCore.varchar("name", {
7222
- length: 100
7223
- }).notNull(),
7224
- isAdmin: pgCore.boolean("is_admin").default(false).notNull(),
7225
- defaultPermissions: pgCore.jsonb("default_permissions").$type(),
7226
- collectionPermissions: pgCore.jsonb("collection_permissions").$type(),
7227
- config: pgCore.jsonb("config").$type()
7228
- });
7229
- const userRoles = rebaseSchema.table("user_roles", {
7230
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7231
- onDelete: "cascade"
7232
- }),
7233
- roleId: pgCore.varchar("role_id", {
7234
- length: 50
7235
- }).notNull().references(() => roles.id, {
7236
- onDelete: "cascade"
7237
- })
7238
- }, (table) => ({
7239
- pk: pgCore.primaryKey({
7240
- columns: [table.userId, table.roleId]
7241
- })
7242
- }));
7243
- const refreshTokens = rebaseSchema.table("refresh_tokens", {
7244
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7245
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7246
- onDelete: "cascade"
7247
- }),
7248
- tokenHash: pgCore.varchar("token_hash", {
7249
- length: 255
7250
- }).notNull().unique(),
7251
- expiresAt: pgCore.timestamp("expires_at").notNull(),
7252
- userAgent: pgCore.varchar("user_agent", {
7253
- length: 500
7254
- }),
7255
- ipAddress: pgCore.varchar("ip_address", {
7256
- length: 45
7257
- }),
7258
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7259
- }, (table) => ({
7260
- uniqueDeviceSession: pgCore.unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7261
- }));
7262
- const passwordResetTokens = rebaseSchema.table("password_reset_tokens", {
7263
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7264
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7265
- onDelete: "cascade"
7266
- }),
7267
- tokenHash: pgCore.varchar("token_hash", {
7268
- length: 255
7269
- }).notNull().unique(),
7270
- expiresAt: pgCore.timestamp("expires_at").notNull(),
7271
- usedAt: pgCore.timestamp("used_at"),
7272
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7273
- });
7274
- const appConfig = rebaseSchema.table("app_config", {
7275
- key: pgCore.varchar("key", {
7276
- length: 100
7277
- }).primaryKey(),
7278
- value: pgCore.jsonb("value").notNull(),
7279
- updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7280
- });
7281
- const userIdentities = rebaseSchema.table("user_identities", {
7282
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7283
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7284
- onDelete: "cascade"
7285
- }),
7286
- provider: pgCore.varchar("provider", {
7287
- length: 50
7288
- }).notNull(),
7289
- // e.g. 'google', 'linkedin'
7290
- providerId: pgCore.varchar("provider_id", {
7291
- length: 255
7292
- }).notNull(),
7293
- profileData: pgCore.jsonb("profile_data"),
7294
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7295
- updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7296
- }, (table) => ({
7297
- uniqueProviderId: pgCore.unique("unique_provider_id").on(table.provider, table.providerId)
7298
- }));
7375
+ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
7376
+ const rolesSchema = rolesSchemaName === "public" ? null : pgCore.pgSchema(rolesSchemaName);
7377
+ const usersSchema2 = usersSchemaName === "public" ? null : pgCore.pgSchema(usersSchemaName);
7378
+ const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgCore.pgTable;
7379
+ const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgCore.pgTable;
7380
+ const users2 = usersTableCreator("users", {
7381
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7382
+ email: pgCore.varchar("email", {
7383
+ length: 255
7384
+ }).notNull().unique(),
7385
+ passwordHash: pgCore.varchar("password_hash", {
7386
+ length: 255
7387
+ }),
7388
+ // NULL for OAuth-only users
7389
+ displayName: pgCore.varchar("display_name", {
7390
+ length: 255
7391
+ }),
7392
+ photoUrl: pgCore.varchar("photo_url", {
7393
+ length: 500
7394
+ }),
7395
+ emailVerified: pgCore.boolean("email_verified").default(false).notNull(),
7396
+ emailVerificationToken: pgCore.varchar("email_verification_token", {
7397
+ length: 255
7398
+ }),
7399
+ emailVerificationSentAt: pgCore.timestamp("email_verification_sent_at"),
7400
+ metadata: pgCore.jsonb("metadata").$type().default({}).notNull(),
7401
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7402
+ updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7403
+ });
7404
+ const roles2 = rolesTableCreator("roles", {
7405
+ id: pgCore.varchar("id", {
7406
+ length: 50
7407
+ }).primaryKey(),
7408
+ // 'admin', 'editor', 'viewer'
7409
+ name: pgCore.varchar("name", {
7410
+ length: 100
7411
+ }).notNull(),
7412
+ isAdmin: pgCore.boolean("is_admin").default(false).notNull(),
7413
+ defaultPermissions: pgCore.jsonb("default_permissions").$type(),
7414
+ collectionPermissions: pgCore.jsonb("collection_permissions").$type(),
7415
+ config: pgCore.jsonb("config").$type()
7416
+ });
7417
+ const userRoles2 = rolesTableCreator("user_roles", {
7418
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7419
+ onDelete: "cascade"
7420
+ }),
7421
+ roleId: pgCore.varchar("role_id", {
7422
+ length: 50
7423
+ }).notNull().references(() => roles2.id, {
7424
+ onDelete: "cascade"
7425
+ })
7426
+ }, (table) => ({
7427
+ pk: pgCore.primaryKey({
7428
+ columns: [table.userId, table.roleId]
7429
+ })
7430
+ }));
7431
+ const refreshTokens2 = rolesTableCreator("refresh_tokens", {
7432
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7433
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7434
+ onDelete: "cascade"
7435
+ }),
7436
+ tokenHash: pgCore.varchar("token_hash", {
7437
+ length: 255
7438
+ }).notNull().unique(),
7439
+ expiresAt: pgCore.timestamp("expires_at").notNull(),
7440
+ userAgent: pgCore.varchar("user_agent", {
7441
+ length: 500
7442
+ }),
7443
+ ipAddress: pgCore.varchar("ip_address", {
7444
+ length: 45
7445
+ }),
7446
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7447
+ }, (table) => ({
7448
+ uniqueDeviceSession: pgCore.unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7449
+ }));
7450
+ const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
7451
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7452
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7453
+ onDelete: "cascade"
7454
+ }),
7455
+ tokenHash: pgCore.varchar("token_hash", {
7456
+ length: 255
7457
+ }).notNull().unique(),
7458
+ expiresAt: pgCore.timestamp("expires_at").notNull(),
7459
+ usedAt: pgCore.timestamp("used_at"),
7460
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7461
+ });
7462
+ const appConfig2 = rolesTableCreator("app_config", {
7463
+ key: pgCore.varchar("key", {
7464
+ length: 100
7465
+ }).primaryKey(),
7466
+ value: pgCore.jsonb("value").notNull(),
7467
+ updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7468
+ });
7469
+ const userIdentities2 = rolesTableCreator("user_identities", {
7470
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7471
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7472
+ onDelete: "cascade"
7473
+ }),
7474
+ provider: pgCore.varchar("provider", {
7475
+ length: 50
7476
+ }).notNull(),
7477
+ // e.g. 'google', 'linkedin'
7478
+ providerId: pgCore.varchar("provider_id", {
7479
+ length: 255
7480
+ }).notNull(),
7481
+ profileData: pgCore.jsonb("profile_data"),
7482
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7483
+ updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7484
+ }, (table) => ({
7485
+ uniqueProviderId: pgCore.unique("unique_provider_id").on(table.provider, table.providerId)
7486
+ }));
7487
+ return {
7488
+ rolesSchema,
7489
+ usersSchema: usersSchema2,
7490
+ users: users2,
7491
+ roles: roles2,
7492
+ userRoles: userRoles2,
7493
+ refreshTokens: refreshTokens2,
7494
+ passwordResetTokens: passwordResetTokens2,
7495
+ appConfig: appConfig2,
7496
+ userIdentities: userIdentities2
7497
+ };
7498
+ }
7499
+ const defaultAuthSchema = createAuthSchema("rebase", "rebase");
7500
+ const rebaseSchema = defaultAuthSchema.rolesSchema;
7501
+ const usersSchema = defaultAuthSchema.usersSchema;
7502
+ const users = defaultAuthSchema.users;
7503
+ const roles = defaultAuthSchema.roles;
7504
+ const userRoles = defaultAuthSchema.userRoles;
7505
+ const refreshTokens = defaultAuthSchema.refreshTokens;
7506
+ const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
7507
+ const appConfig = defaultAuthSchema.appConfig;
7508
+ const userIdentities = defaultAuthSchema.userIdentities;
7299
7509
  const usersRelations = drizzleOrm.relations(users, ({
7300
7510
  many
7301
7511
  }) => ({
@@ -7478,6 +7688,15 @@
7478
7688
  }
7479
7689
  break;
7480
7690
  }
7691
+ case "vector": {
7692
+ const vp = prop;
7693
+ columnDefinition = `vector("${colName}", { dimensions: ${vp.dimensions} })`;
7694
+ break;
7695
+ }
7696
+ case "binary": {
7697
+ columnDefinition = `customType({ dataType() { return 'bytea'; } })("${colName}")`;
7698
+ break;
7699
+ }
7481
7700
  case "relation": {
7482
7701
  const refProp = prop;
7483
7702
  const resolvedRelations = resolveCollectionRelations(collection);
@@ -7544,7 +7763,7 @@
7544
7763
  return ` ${propName}: ${columnDefinition}`;
7545
7764
  };
7546
7765
  const resolveRawSql = (expression) => {
7547
- const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => `\${table.${col}}`);
7766
+ const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
7548
7767
  return `sql\`${resolved}\``;
7549
7768
  };
7550
7769
  const wrapWithRoleCheck = (clause, roles2) => {
@@ -7556,7 +7775,7 @@
7556
7775
  const match = sqlExpr.match(/^sql`(.*)`$/s);
7557
7776
  return match ? match[1] : sqlExpr;
7558
7777
  };
7559
- const buildUsingClause = (rule) => {
7778
+ const buildUsingClause = (rule, collection) => {
7560
7779
  if (rule.using) {
7561
7780
  return resolveRawSql(rule.using);
7562
7781
  }
@@ -7564,15 +7783,17 @@
7564
7783
  return "sql`true`";
7565
7784
  }
7566
7785
  if (rule.ownerField) {
7567
- return `sql\`\${table.${rule.ownerField}} = auth.uid()\``;
7786
+ const prop = collection.properties?.[rule.ownerField];
7787
+ const colName = resolveColumnName(rule.ownerField, prop);
7788
+ return `sql\`${colName} = auth.uid()\``;
7568
7789
  }
7569
7790
  return null;
7570
7791
  };
7571
- const buildWithCheckClause = (rule) => {
7792
+ const buildWithCheckClause = (rule, collection) => {
7572
7793
  if (rule.withCheck) {
7573
7794
  return resolveRawSql(rule.withCheck);
7574
7795
  }
7575
- return buildUsingClause(rule);
7796
+ return buildUsingClause(rule, collection);
7576
7797
  };
7577
7798
  const getPolicyNameHash = (rule) => {
7578
7799
  const data = JSON.stringify({
@@ -7588,21 +7809,22 @@
7588
7809
  });
7589
7810
  return crypto.createHash("sha1").update(data).digest("hex").substring(0, 7);
7590
7811
  };
7591
- const generatePolicyCode = (tableName, rule, index) => {
7812
+ const generatePolicyCode = (collection, rule, index) => {
7813
+ const tableName = getTableName(collection);
7592
7814
  const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
7593
7815
  const ruleHash = getPolicyNameHash(rule);
7594
7816
  return ops.map((op, opIdx) => {
7595
7817
  const policyName = rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
7596
- return generateSinglePolicyCode(tableName, rule, op, policyName);
7818
+ return generateSinglePolicyCode(collection, rule, op, policyName);
7597
7819
  }).join("");
7598
7820
  };
7599
- const generateSinglePolicyCode = (tableName, rule, operation, policyName) => {
7821
+ const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
7600
7822
  const mode = rule.mode ?? "permissive";
7601
7823
  const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
7602
7824
  const needsUsing = operation !== "insert";
7603
7825
  const needsWithCheck = operation !== "select" && operation !== "delete";
7604
- let usingClause = needsUsing ? buildUsingClause(rule) : null;
7605
- let withCheckClause = needsWithCheck ? buildWithCheckClause(rule) : null;
7826
+ let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
7827
+ let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
7606
7828
  if (roles2 && roles2.length > 0) {
7607
7829
  if (usingClause) {
7608
7830
  usingClause = wrapWithRoleCheck(usingClause, roles2);
@@ -7671,12 +7893,26 @@
7671
7893
  const generateSchema = async (collections, stripPolicies = false) => {
7672
7894
  let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
7673
7895
  const hasUuid = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "string" && (p.autoValue === "uuid" || p.isId === "uuid")));
7674
- collections.some((c) => c.properties && Object.values(c.properties).some((p) => (p.type === "map" || p.type === "array") && p.columnType === "json"));
7896
+ const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
7897
+ const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
7675
7898
  const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
7676
7899
  if (hasUuid) pgCoreImports.push("uuid");
7900
+ if (hasVector) pgCoreImports.push("vector");
7901
+ if (hasBinary) pgCoreImports.push("customType");
7902
+ const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollection(c) ? c.schema : void 0).filter(Boolean)));
7903
+ if (uniqueSchemas.length > 0) {
7904
+ pgCoreImports.push("pgSchema");
7905
+ }
7677
7906
  schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';
7678
7907
  `;
7679
7908
  schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
7909
+ uniqueSchemas.forEach((schema) => {
7910
+ schemaContent += `export const ${schema}Schema = pgSchema("${schema}");
7911
+ `;
7912
+ });
7913
+ if (uniqueSchemas.length > 0) {
7914
+ schemaContent += "\n";
7915
+ }
7680
7916
  const exportedTableVars = [];
7681
7917
  const exportedEnumVars = [];
7682
7918
  const exportedRelationVars = [];
@@ -7731,6 +7967,9 @@
7731
7967
  const tableVarName = getTableVarName(tableName);
7732
7968
  if (isJunction && relation && sourceCollection && relation.through) {
7733
7969
  const targetCollection = relation.target();
7970
+ const schema = (isPostgresCollection(targetCollection) ? targetCollection.schema : void 0) || (isPostgresCollection(sourceCollection) ? sourceCollection.schema : void 0);
7971
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
7972
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
7734
7973
  const {
7735
7974
  sourceColumn,
7736
7975
  targetColumn
@@ -7741,7 +7980,7 @@
7741
7980
  const targetColType = isNumericId(targetCollection) ? "integer" : getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar";
7742
7981
  const sourceId = getPrimaryKeyName(sourceCollection);
7743
7982
  const targetId = getPrimaryKeyName(targetCollection);
7744
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
7983
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7745
7984
  `;
7746
7985
  schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
7747
7986
  `;
@@ -7752,7 +7991,10 @@
7752
7991
  `;
7753
7992
  schemaContent += "}));\n\n";
7754
7993
  } else if (!isJunction) {
7755
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
7994
+ const schema = isPostgresCollection(collection) ? collection.schema : void 0;
7995
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
7996
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
7997
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7756
7998
  `;
7757
7999
  const columns = /* @__PURE__ */ new Set();
7758
8000
  Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
@@ -7768,7 +8010,7 @@
7768
8010
  if (!stripPolicies && securityRules && securityRules.length > 0) {
7769
8011
  schemaContent += "\n}, (table) => ([\n";
7770
8012
  securityRules.forEach((rule, idx) => {
7771
- schemaContent += generatePolicyCode(tableName, rule);
8013
+ schemaContent += generatePolicyCode(collection, rule);
7772
8014
  });
7773
8015
  schemaContent += "])).enableRLS();\n\n";
7774
8016
  } else {
@@ -7813,11 +8055,11 @@
7813
8055
  references: [${sourceTableVar}.${sourceId}],
7814
8056
  relationName: "${owningRelationName}"
7815
8057
  })`);
7816
- const targetRelName = inverseRelationName ?? owningRelationName;
8058
+ const targetRelationName = inverseRelationName ? inverseRelationName : `${tableName}_${relation.through.targetColumn}`;
7817
8059
  tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {
7818
8060
  fields: [${tableVarName}.${relation.through.targetColumn}],
7819
8061
  references: [${targetTableVar}.${targetId}],
7820
- relationName: "${targetRelName}"
8062
+ relationName: "${targetRelationName}"
7821
8063
  })`);
7822
8064
  }
7823
8065
  } else {
@@ -7916,6 +8158,89 @@ ${tableRelations.join(",\n")}
7916
8158
  schemaContent += tablesExport + enumsExport + relationsExport;
7917
8159
  return schemaContent;
7918
8160
  };
8161
+ const defaultUsersCollection = {
8162
+ name: "Users",
8163
+ singularName: "User",
8164
+ slug: "users",
8165
+ table: "users",
8166
+ icon: "Users",
8167
+ group: "Settings",
8168
+ properties: {
8169
+ id: {
8170
+ name: "ID",
8171
+ type: "string",
8172
+ isId: "uuid"
8173
+ },
8174
+ email: {
8175
+ name: "Email",
8176
+ type: "string",
8177
+ validation: {
8178
+ required: true,
8179
+ unique: true
8180
+ }
8181
+ },
8182
+ password_hash: {
8183
+ name: "Password Hash",
8184
+ type: "string",
8185
+ ui: {
8186
+ hideFromCollection: true
8187
+ }
8188
+ },
8189
+ display_name: {
8190
+ name: "Display Name",
8191
+ type: "string"
8192
+ },
8193
+ photo_url: {
8194
+ name: "Photo URL",
8195
+ type: "string"
8196
+ },
8197
+ email_verified: {
8198
+ name: "Email Verified",
8199
+ type: "boolean",
8200
+ defaultValue: false
8201
+ },
8202
+ email_verification_token: {
8203
+ name: "Email Verification Token",
8204
+ type: "string",
8205
+ ui: {
8206
+ hideFromCollection: true
8207
+ }
8208
+ },
8209
+ email_verification_sent_at: {
8210
+ name: "Email Verification Sent At",
8211
+ type: "date",
8212
+ ui: {
8213
+ hideFromCollection: true
8214
+ }
8215
+ },
8216
+ metadata: {
8217
+ name: "Metadata",
8218
+ type: "map",
8219
+ defaultValue: {},
8220
+ ui: {
8221
+ hideFromCollection: true
8222
+ }
8223
+ },
8224
+ created_at: {
8225
+ name: "Created At",
8226
+ type: "date",
8227
+ autoValue: "on_create",
8228
+ ui: {
8229
+ readOnly: true,
8230
+ hideFromCollection: true
8231
+ }
8232
+ },
8233
+ updated_at: {
8234
+ name: "Updated At",
8235
+ type: "date",
8236
+ autoValue: "on_update",
8237
+ ui: {
8238
+ readOnly: true,
8239
+ hideFromCollection: true
8240
+ }
8241
+ }
8242
+ }
8243
+ };
7919
8244
  const formatTerminalText = (text, options = {}) => {
7920
8245
  let codes = "";
7921
8246
  if (options.bold) codes += "\x1B[1m";
@@ -7978,10 +8303,14 @@ ${tableRelations.join(",\n")}
7978
8303
  const imported = await dynamicImport(fileUrl);
7979
8304
  collections = imported.backendCollections || imported.collections;
7980
8305
  }
7981
- if (!collections || !Array.isArray(collections) || collections.length === 0) {
7982
- console.error("Error: Could not find collections array or failed to load directory.");
7983
- return;
8306
+ if (!collections || !Array.isArray(collections)) {
8307
+ collections = [];
8308
+ }
8309
+ const hasUsersCollection = collections.some((c) => c.slug === "users");
8310
+ if (!hasUsersCollection) {
8311
+ collections.push(defaultUsersCollection);
7984
8312
  }
8313
+ collections.sort((a, b) => a.slug.localeCompare(b.slug));
7985
8314
  const schemaContent = await generateSchema(collections);
7986
8315
  if (outputPath) {
7987
8316
  const outputDir = path.dirname(outputPath);
@@ -8209,29 +8538,14 @@ ${tableRelations.join(",\n")}
8209
8538
  },
8210
8539
  authContext
8211
8540
  });
8212
- let entities;
8213
- if (this.driver) {
8214
- entities = await this.driver.fetchCollection({
8215
- path: request.path,
8216
- collection,
8217
- filter: request.filter,
8218
- orderBy: request.orderBy,
8219
- order: request.order,
8220
- limit: request.limit,
8221
- startAfter: request.startAfter,
8222
- searchString: request.searchString
8223
- });
8224
- } else {
8225
- entities = await this.entityService.fetchCollection(request.path, {
8226
- filter: request.filter,
8227
- orderBy: request.orderBy,
8228
- order: request.order,
8229
- limit: request.limit,
8230
- startAfter: request.startAfter,
8231
- databaseId: request.collection?.databaseId,
8232
- searchString: request.searchString
8233
- });
8234
- }
8541
+ const entities = await this.fetchCollectionWithAuth(request.path, {
8542
+ filter: request.filter,
8543
+ orderBy: request.orderBy,
8544
+ order: request.order,
8545
+ limit: request.limit,
8546
+ startAfter: request.startAfter,
8547
+ searchString: request.searchString
8548
+ }, authContext);
8235
8549
  this.sendCollectionUpdate(clientId, subscriptionId, entities);
8236
8550
  } catch (error) {
8237
8551
  this.sendError(clientId, `Failed to subscribe to collection: ${error}`, subscriptionId);
@@ -8255,16 +8569,7 @@ ${tableRelations.join(",\n")}
8255
8569
  entityId: request.entityId,
8256
8570
  authContext
8257
8571
  });
8258
- let entity;
8259
- if (this.driver) {
8260
- entity = await this.driver.fetchEntity({
8261
- path: request.path,
8262
- entityId: request.entityId,
8263
- collection
8264
- });
8265
- } else {
8266
- entity = await this.entityService.fetchEntity(request.path, request.entityId, request.collection?.databaseId);
8267
- }
8572
+ const entity = await this.fetchEntityWithAuth(request.path, String(request.entityId), authContext);
8268
8573
  this.sendEntityUpdate(clientId, subscriptionId, entity || null);
8269
8574
  } catch (error) {
8270
8575
  this.sendError(clientId, `Failed to subscribe to entity: ${request.path} ${request.entityId} ${error}`, subscriptionId);
@@ -8409,87 +8714,77 @@ ${tableRelations.join(",\n")}
8409
8714
  async fetchCollectionWithAuth(notifyPath, collectionRequest, authContext) {
8410
8715
  if (this.driver) {
8411
8716
  const collection = this.registry.getCollectionByPath(notifyPath);
8412
- const fetchFn = async () => this.driver.fetchCollection({
8413
- path: notifyPath,
8414
- collection,
8415
- filter: collectionRequest.filter,
8416
- orderBy: collectionRequest.orderBy,
8417
- order: collectionRequest.order,
8418
- limit: collectionRequest.limit,
8419
- offset: collectionRequest.offset,
8420
- startAfter: collectionRequest.startAfter,
8421
- searchString: collectionRequest.searchString
8717
+ const activeAuth = authContext || {
8718
+ userId: "anon",
8719
+ roles: ["anon"]
8720
+ };
8721
+ return await this.db.transaction(async (tx) => {
8722
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
8723
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
8724
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
8725
+ sub: activeAuth.userId,
8726
+ roles: activeAuth.roles
8727
+ })}, true)`);
8728
+ const txEntityService = new EntityService(tx, this.registry);
8729
+ let fetchedEntities;
8730
+ if (collectionRequest.searchString) {
8731
+ fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8732
+ filter: collectionRequest.filter,
8733
+ orderBy: collectionRequest.orderBy,
8734
+ order: collectionRequest.order,
8735
+ limit: collectionRequest.limit,
8736
+ databaseId: collectionRequest.databaseId
8737
+ });
8738
+ } else {
8739
+ fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8740
+ filter: collectionRequest.filter,
8741
+ orderBy: collectionRequest.orderBy,
8742
+ order: collectionRequest.order,
8743
+ limit: collectionRequest.limit,
8744
+ offset: collectionRequest.offset,
8745
+ startAfter: collectionRequest.startAfter,
8746
+ databaseId: collectionRequest.databaseId
8747
+ });
8748
+ }
8749
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
8750
+ const resolvedCollection = collection ? {
8751
+ ...collection,
8752
+ ...registryCollection
8753
+ } : registryCollection;
8754
+ const callbacks = resolvedCollection?.callbacks;
8755
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8756
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8757
+ const contextForCallback = {
8758
+ user: {
8759
+ uid: activeAuth.userId,
8760
+ roles: activeAuth.roles
8761
+ },
8762
+ driver: this.driver,
8763
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
8764
+ };
8765
+ return await Promise.all(fetchedEntities.map(async (entity) => {
8766
+ let processedEntity = entity;
8767
+ if (callbacks?.afterRead) {
8768
+ processedEntity = await callbacks.afterRead({
8769
+ collection: resolvedCollection,
8770
+ path: notifyPath,
8771
+ entity: processedEntity,
8772
+ context: contextForCallback
8773
+ }) ?? processedEntity;
8774
+ }
8775
+ if (propertyCallbacks?.afterRead) {
8776
+ processedEntity = await propertyCallbacks.afterRead({
8777
+ collection: resolvedCollection,
8778
+ path: notifyPath,
8779
+ entity: processedEntity,
8780
+ context: contextForCallback
8781
+ }) ?? processedEntity;
8782
+ }
8783
+ return processedEntity;
8784
+ }));
8785
+ }
8786
+ return fetchedEntities;
8422
8787
  });
8423
- if (authContext) {
8424
- return await this.db.transaction(async (tx) => {
8425
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8426
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8427
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
8428
- sub: authContext.userId,
8429
- roles: authContext.roles
8430
- })}, true)`);
8431
- const txEntityService = new EntityService(tx, this.registry);
8432
- let fetchedEntities;
8433
- if (collectionRequest.searchString) {
8434
- fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8435
- filter: collectionRequest.filter,
8436
- orderBy: collectionRequest.orderBy,
8437
- order: collectionRequest.order,
8438
- limit: collectionRequest.limit,
8439
- databaseId: collectionRequest.databaseId
8440
- });
8441
- } else {
8442
- fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8443
- filter: collectionRequest.filter,
8444
- orderBy: collectionRequest.orderBy,
8445
- order: collectionRequest.order,
8446
- limit: collectionRequest.limit,
8447
- offset: collectionRequest.offset,
8448
- startAfter: collectionRequest.startAfter,
8449
- databaseId: collectionRequest.databaseId
8450
- });
8451
- }
8452
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8453
- const resolvedCollection = collection ? {
8454
- ...collection,
8455
- ...registryCollection
8456
- } : registryCollection;
8457
- const callbacks = resolvedCollection?.callbacks;
8458
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8459
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8460
- const contextForCallback = {
8461
- user: {
8462
- uid: authContext.userId,
8463
- roles: authContext.roles
8464
- },
8465
- driver: this.driver,
8466
- data: this.driver ? this.driver.data : void 0
8467
- };
8468
- return await Promise.all(fetchedEntities.map(async (entity) => {
8469
- let processedEntity = entity;
8470
- if (callbacks?.afterRead) {
8471
- processedEntity = await callbacks.afterRead({
8472
- collection: resolvedCollection,
8473
- path: notifyPath,
8474
- entity: processedEntity,
8475
- context: contextForCallback
8476
- }) ?? processedEntity;
8477
- }
8478
- if (propertyCallbacks?.afterRead) {
8479
- processedEntity = await propertyCallbacks.afterRead({
8480
- collection: resolvedCollection,
8481
- path: notifyPath,
8482
- entity: processedEntity,
8483
- context: contextForCallback
8484
- }) ?? processedEntity;
8485
- }
8486
- return processedEntity;
8487
- }));
8488
- }
8489
- return fetchedEntities;
8490
- });
8491
- }
8492
- return fetchFn();
8493
8788
  }
8494
8789
  if (collectionRequest.searchString) {
8495
8790
  return await this.entityService.searchEntities(notifyPath, collectionRequest.searchString, {
@@ -8553,60 +8848,56 @@ ${tableRelations.join(",\n")}
8553
8848
  async fetchEntityWithAuth(notifyPath, entityId, authContext) {
8554
8849
  if (this.driver) {
8555
8850
  const collection = this.registry.getCollectionByPath(notifyPath);
8556
- const fetchFn = async () => this.driver.fetchEntity({
8557
- path: notifyPath,
8558
- entityId,
8559
- collection
8560
- });
8561
- if (authContext) {
8562
- return await this.db.transaction(async (tx) => {
8563
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8564
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8565
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
8566
- sub: authContext.userId,
8567
- roles: authContext.roles
8568
- })}, true)`);
8569
- const txEntityService = new EntityService(tx, this.registry);
8570
- let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
8571
- if (processedEntity) {
8572
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8573
- const resolvedCollection = collection ? {
8574
- ...collection,
8575
- ...registryCollection
8576
- } : registryCollection;
8577
- const callbacks = resolvedCollection?.callbacks;
8578
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8579
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8580
- const contextForCallback = {
8581
- user: {
8582
- uid: authContext.userId,
8583
- roles: authContext.roles
8584
- },
8585
- driver: this.driver,
8586
- data: this.driver ? this.driver.data : void 0
8587
- };
8588
- if (callbacks?.afterRead) {
8589
- processedEntity = await callbacks.afterRead({
8590
- collection: resolvedCollection,
8591
- path: notifyPath,
8592
- entity: processedEntity,
8593
- context: contextForCallback
8594
- }) ?? processedEntity;
8595
- }
8596
- if (propertyCallbacks?.afterRead) {
8597
- processedEntity = await propertyCallbacks.afterRead({
8598
- collection: resolvedCollection,
8599
- path: notifyPath,
8600
- entity: processedEntity,
8601
- context: contextForCallback
8602
- }) ?? processedEntity;
8603
- }
8851
+ const activeAuth = authContext || {
8852
+ userId: "anon",
8853
+ roles: ["anon"]
8854
+ };
8855
+ return await this.db.transaction(async (tx) => {
8856
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
8857
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
8858
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
8859
+ sub: activeAuth.userId,
8860
+ roles: activeAuth.roles
8861
+ })}, true)`);
8862
+ const txEntityService = new EntityService(tx, this.registry);
8863
+ let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
8864
+ if (processedEntity) {
8865
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
8866
+ const resolvedCollection = collection ? {
8867
+ ...collection,
8868
+ ...registryCollection
8869
+ } : registryCollection;
8870
+ const callbacks = resolvedCollection?.callbacks;
8871
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8872
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8873
+ const contextForCallback = {
8874
+ user: {
8875
+ uid: activeAuth.userId,
8876
+ roles: activeAuth.roles
8877
+ },
8878
+ driver: this.driver,
8879
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
8880
+ };
8881
+ if (callbacks?.afterRead) {
8882
+ processedEntity = await callbacks.afterRead({
8883
+ collection: resolvedCollection,
8884
+ path: notifyPath,
8885
+ entity: processedEntity,
8886
+ context: contextForCallback
8887
+ }) ?? processedEntity;
8888
+ }
8889
+ if (propertyCallbacks?.afterRead) {
8890
+ processedEntity = await propertyCallbacks.afterRead({
8891
+ collection: resolvedCollection,
8892
+ path: notifyPath,
8893
+ entity: processedEntity,
8894
+ context: contextForCallback
8895
+ }) ?? processedEntity;
8604
8896
  }
8605
8897
  }
8606
- return processedEntity;
8607
- });
8608
- }
8609
- return fetchFn();
8898
+ }
8899
+ return processedEntity;
8900
+ });
8610
8901
  }
8611
8902
  return await this.entityService.fetchEntity(notifyPath, entityId);
8612
8903
  }
@@ -8674,6 +8965,31 @@ ${tableRelations.join(",\n")}
8674
8965
  return parentPaths;
8675
8966
  }
8676
8967
  // =============================================================================
8968
+ // Lifecycle / Cleanup
8969
+ // =============================================================================
8970
+ /**
8971
+ * Gracefully tear down all realtime resources.
8972
+ *
8973
+ * This MUST be called during process shutdown, **before** `pool.end()`.
8974
+ * It ensures:
8975
+ * 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
8976
+ * 2. All subscription state and callbacks are cleared.
8977
+ * 3. The dedicated LISTEN client (outside the pool) is disconnected.
8978
+ * 4. All WebSocket clients are removed (but not forcefully closed — the
8979
+ * HTTP server close will handle that).
8980
+ */
8981
+ async destroy() {
8982
+ for (const [key, timer] of this.refetchTimers) {
8983
+ clearTimeout(timer);
8984
+ this.refetchTimers.delete(key);
8985
+ }
8986
+ this._subscriptions.clear();
8987
+ this.subscriptionCallbacks.clear();
8988
+ await this.stopListening();
8989
+ this.clients.clear();
8990
+ this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
8991
+ }
8992
+ // =============================================================================
8677
8993
  // Cross-Instance LISTEN/NOTIFY
8678
8994
  // =============================================================================
8679
8995
  /**
@@ -8814,8 +9130,23 @@ ${tableRelations.join(",\n")}
8814
9130
  const WS_RATE_LIMIT = 2e3;
8815
9131
  const WS_RATE_WINDOW_MS = 6e4;
8816
9132
  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"]);
9133
+ function extractErrorMessage(error) {
9134
+ if (!error) return "Unknown error";
9135
+ if (typeof error === "object") {
9136
+ const err = error;
9137
+ if (err.cause) {
9138
+ return extractErrorMessage(err.cause);
9139
+ }
9140
+ if (typeof err.message === "string") {
9141
+ return err.message;
9142
+ }
9143
+ }
9144
+ return String(error);
9145
+ }
8817
9146
  function isAdminSession(session) {
8818
- if (!session?.user?.roles) return false;
9147
+ if (!session?.user) return false;
9148
+ if (session.user.isAdmin) return true;
9149
+ if (!session.user.roles) return false;
8819
9150
  return session.user.roles.some((r) => {
8820
9151
  if (typeof r === "string") return r === "admin";
8821
9152
  if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
@@ -8823,7 +9154,7 @@ ${tableRelations.join(",\n")}
8823
9154
  return false;
8824
9155
  });
8825
9156
  }
8826
- function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9157
+ function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
8827
9158
  const isProduction = process.env.NODE_ENV === "production";
8828
9159
  const wsDebug = (...args) => {
8829
9160
  if (!isProduction) console.debug(...args);
@@ -8837,7 +9168,7 @@ ${tableRelations.join(",\n")}
8837
9168
  }
8838
9169
  console.error("❌ [WebSocket Server] Error:", err);
8839
9170
  });
8840
- const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
9171
+ const requireAuth = authAdapter ? true : authConfig?.requireAuth !== false && !!authConfig?.jwtSecret;
8841
9172
  wss.on("connection", (ws2) => {
8842
9173
  const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
8843
9174
  wsDebug(`WebSocket client connected: ${clientId}`);
@@ -8882,11 +9213,37 @@ ${tableRelations.join(",\n")}
8882
9213
  sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
8883
9214
  return;
8884
9215
  }
8885
- const user = serverCore.extractUserFromToken(token);
8886
- if (user) {
9216
+ let verifiedUser = null;
9217
+ if (authAdapter) {
9218
+ try {
9219
+ const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", {
9220
+ headers: {
9221
+ Authorization: `Bearer ${token}`
9222
+ }
9223
+ }));
9224
+ if (adapterUser) {
9225
+ verifiedUser = {
9226
+ userId: adapterUser.uid,
9227
+ roles: adapterUser.roles,
9228
+ isAdmin: adapterUser.isAdmin
9229
+ };
9230
+ }
9231
+ } catch {
9232
+ }
9233
+ } else {
9234
+ const jwtPayload = serverCore.extractUserFromToken(token);
9235
+ if (jwtPayload) {
9236
+ verifiedUser = {
9237
+ userId: jwtPayload.userId,
9238
+ roles: jwtPayload.roles ?? [],
9239
+ isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
9240
+ };
9241
+ }
9242
+ }
9243
+ if (verifiedUser) {
8887
9244
  const session = clientSessions.get(clientId);
8888
9245
  if (session) {
8889
- session.user = user;
9246
+ session.user = verifiedUser;
8890
9247
  session.authenticated = true;
8891
9248
  }
8892
9249
  wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
@@ -8894,11 +9251,11 @@ ${tableRelations.join(",\n")}
8894
9251
  type: "AUTH_SUCCESS",
8895
9252
  requestId,
8896
9253
  payload: {
8897
- userId: user.userId,
8898
- roles: user.roles
9254
+ userId: verifiedUser.userId,
9255
+ roles: verifiedUser.roles
8899
9256
  }
8900
9257
  }));
8901
- wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${user.userId}`);
9258
+ wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.userId}`);
8902
9259
  } else {
8903
9260
  wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
8904
9261
  sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
@@ -8936,16 +9293,19 @@ ${tableRelations.join(",\n")}
8936
9293
  }
8937
9294
  const getScopedDelegate = async () => {
8938
9295
  const session = clientSessions.get(clientId);
8939
- if (session?.user && "withAuth" in driver && typeof driver.withAuth === "function") {
9296
+ if ("withAuth" in driver && typeof driver.withAuth === "function") {
8940
9297
  try {
8941
- const userForAuth = {
9298
+ const userForAuth = session?.user ? {
8942
9299
  uid: session.user.userId,
8943
9300
  roles: session.user.roles ?? []
9301
+ } : {
9302
+ uid: "anon",
9303
+ roles: ["anon"]
8944
9304
  };
8945
9305
  return await driver.withAuth(userForAuth);
8946
9306
  } catch (e) {
8947
- console.error("Failed to create authenticated delegate for WS request", e);
8948
- return driver;
9307
+ console.error("Failed to create RLS scoped delegate for WS request", e);
9308
+ throw new Error("Internal authentication error");
8949
9309
  }
8950
9310
  }
8951
9311
  return driver;
@@ -9076,24 +9436,29 @@ ${tableRelations.join(",\n")}
9076
9436
  sql,
9077
9437
  options
9078
9438
  } = payload;
9079
- const delegate = await getScopedDelegate();
9080
- const admin = delegate.admin;
9081
- if (!isSQLAdmin(admin)) {
9082
- sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9083
- break;
9084
- }
9085
- const result = await admin.executeSql(sql, options);
9086
- if (process.env.NODE_ENV !== "production") {
9087
- wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9439
+ try {
9440
+ const delegate = await getScopedDelegate();
9441
+ const admin = delegate.admin;
9442
+ if (!isSQLAdmin(admin)) {
9443
+ sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9444
+ break;
9445
+ }
9446
+ const result = await admin.executeSql(sql, options);
9447
+ if (process.env.NODE_ENV !== "production") {
9448
+ wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9449
+ }
9450
+ const response = {
9451
+ type: "EXECUTE_SQL_SUCCESS",
9452
+ payload: {
9453
+ result
9454
+ },
9455
+ requestId
9456
+ };
9457
+ ws2.send(JSON.stringify(response));
9458
+ } catch (sqlError) {
9459
+ const errMsg = extractErrorMessage(sqlError);
9460
+ sendError("ERROR", "SQL_ERROR", errMsg);
9088
9461
  }
9089
- const response = {
9090
- type: "EXECUTE_SQL_SUCCESS",
9091
- payload: {
9092
- result
9093
- },
9094
- requestId
9095
- };
9096
- ws2.send(JSON.stringify(response));
9097
9462
  }
9098
9463
  break;
9099
9464
  case "FETCH_DATABASES":
@@ -9272,7 +9637,10 @@ ${tableRelations.join(",\n")}
9272
9637
  const authContext = session?.user ? {
9273
9638
  userId: session.user.userId,
9274
9639
  roles: session.user.roles ?? []
9275
- } : void 0;
9640
+ } : {
9641
+ userId: "anon",
9642
+ roles: ["anon"]
9643
+ };
9276
9644
  await realtimeService.handleClientMessage(clientId, {
9277
9645
  type,
9278
9646
  payload,
@@ -9416,29 +9784,58 @@ ${tableRelations.join(",\n")}
9416
9784
  },
9417
9785
  config: null
9418
9786
  }];
9419
- async function ensureAuthTablesExist(db) {
9787
+ async function ensureAuthTablesExist(db, registry) {
9420
9788
  console.log("🔍 Checking auth tables...");
9421
9789
  try {
9790
+ let usersTableName = '"users"';
9791
+ let userIdType = "TEXT";
9792
+ let usersSchema2 = "public";
9793
+ if (registry) {
9794
+ const usersTable = registry.getTable("users");
9795
+ if (usersTable) {
9796
+ const {
9797
+ getTableName: getTableName2
9798
+ } = await import("drizzle-orm");
9799
+ usersSchema2 = pgCore.getTableConfig(usersTable).schema || "public";
9800
+ usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
9801
+ if (usersTable.id) {
9802
+ const col = usersTable.id;
9803
+ const meta = getColumnMeta(col);
9804
+ const columnType = meta.columnType;
9805
+ if (columnType === "PgUUID") {
9806
+ userIdType = "UUID";
9807
+ } else if (columnType === "PgSerial" || columnType === "PgInteger") {
9808
+ userIdType = "INTEGER";
9809
+ } else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
9810
+ userIdType = "BIGINT";
9811
+ }
9812
+ }
9813
+ }
9814
+ }
9815
+ let rolesSchema = "rebase";
9816
+ if (registry) {
9817
+ const rolesTable = registry.getTable("roles");
9818
+ if (rolesTable) {
9819
+ rolesSchema = pgCore.getTableConfig(rolesTable).schema || "public";
9820
+ }
9821
+ }
9822
+ if (usersSchema2 !== "public") {
9823
+ await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(usersSchema2)}`);
9824
+ }
9825
+ if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
9826
+ await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(rolesSchema)}`);
9827
+ }
9422
9828
  await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS rebase`);
9829
+ const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
9830
+ const rolesTableName = `"${rolesSchema}"."roles"`;
9831
+ const userRolesTableName = `"${rolesSchema}"."user_roles"`;
9832
+ const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
9833
+ const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
9834
+ const appConfigTableName = `"${rolesSchema}"."app_config"`;
9423
9835
  await db.execute(drizzleOrm.sql`
9424
- CREATE TABLE IF NOT EXISTS rebase.users (
9836
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userIdentitiesTable)} (
9425
9837
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9426
- email TEXT NOT NULL UNIQUE,
9427
- password_hash TEXT,
9428
- display_name TEXT,
9429
- photo_url TEXT,
9430
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
9431
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9432
- )
9433
- `);
9434
- await db.execute(drizzleOrm.sql`
9435
- CREATE INDEX IF NOT EXISTS idx_users_email
9436
- ON rebase.users(email)
9437
- `);
9438
- await db.execute(drizzleOrm.sql`
9439
- CREATE TABLE IF NOT EXISTS rebase.user_identities (
9440
- id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9441
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9838
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9442
9839
  provider TEXT NOT NULL,
9443
9840
  provider_id TEXT NOT NULL,
9444
9841
  profile_data JSONB,
@@ -9449,10 +9846,10 @@ ${tableRelations.join(",\n")}
9449
9846
  `);
9450
9847
  await db.execute(drizzleOrm.sql`
9451
9848
  CREATE INDEX IF NOT EXISTS idx_user_identities_user
9452
- ON rebase.user_identities(user_id)
9849
+ ON ${drizzleOrm.sql.raw(userIdentitiesTable)}(user_id)
9453
9850
  `);
9454
9851
  await db.execute(drizzleOrm.sql`
9455
- CREATE TABLE IF NOT EXISTS rebase.roles (
9852
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(rolesTableName)} (
9456
9853
  id TEXT PRIMARY KEY,
9457
9854
  name TEXT NOT NULL,
9458
9855
  is_admin BOOLEAN DEFAULT FALSE,
@@ -9463,20 +9860,20 @@ ${tableRelations.join(",\n")}
9463
9860
  )
9464
9861
  `);
9465
9862
  await db.execute(drizzleOrm.sql`
9466
- CREATE TABLE IF NOT EXISTS rebase.user_roles (
9467
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9468
- role_id TEXT NOT NULL REFERENCES rebase.roles(id) ON DELETE CASCADE,
9863
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userRolesTableName)} (
9864
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9865
+ role_id TEXT NOT NULL REFERENCES ${drizzleOrm.sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
9469
9866
  PRIMARY KEY (user_id, role_id)
9470
9867
  )
9471
9868
  `);
9472
9869
  await db.execute(drizzleOrm.sql`
9473
9870
  CREATE INDEX IF NOT EXISTS idx_user_roles_user
9474
- ON rebase.user_roles(user_id)
9871
+ ON ${drizzleOrm.sql.raw(userRolesTableName)}(user_id)
9475
9872
  `);
9476
9873
  await db.execute(drizzleOrm.sql`
9477
- CREATE TABLE IF NOT EXISTS rebase.refresh_tokens (
9874
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(refreshTokensTableName)} (
9478
9875
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9479
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9876
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9480
9877
  token_hash TEXT NOT NULL UNIQUE,
9481
9878
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9482
9879
  user_agent TEXT,
@@ -9487,16 +9884,16 @@ ${tableRelations.join(",\n")}
9487
9884
  `);
9488
9885
  await db.execute(drizzleOrm.sql`
9489
9886
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash
9490
- ON rebase.refresh_tokens(token_hash)
9887
+ ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(token_hash)
9491
9888
  `);
9492
9889
  await db.execute(drizzleOrm.sql`
9493
9890
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user
9494
- ON rebase.refresh_tokens(user_id)
9891
+ ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(user_id)
9495
9892
  `);
9496
9893
  await db.execute(drizzleOrm.sql`
9497
- CREATE TABLE IF NOT EXISTS rebase.password_reset_tokens (
9894
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(passwordResetTokensTableName)} (
9498
9895
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9499
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9896
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9500
9897
  token_hash TEXT NOT NULL UNIQUE,
9501
9898
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9502
9899
  used_at TIMESTAMP WITH TIME ZONE,
@@ -9505,20 +9902,19 @@ ${tableRelations.join(",\n")}
9505
9902
  `);
9506
9903
  await db.execute(drizzleOrm.sql`
9507
9904
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_hash
9508
- ON rebase.password_reset_tokens(token_hash)
9905
+ ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(token_hash)
9509
9906
  `);
9510
9907
  await db.execute(drizzleOrm.sql`
9511
9908
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
9512
- ON rebase.password_reset_tokens(user_id)
9909
+ ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(user_id)
9513
9910
  `);
9514
9911
  await db.execute(drizzleOrm.sql`
9515
- CREATE TABLE IF NOT EXISTS rebase.app_config (
9912
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(appConfigTableName)} (
9516
9913
  key TEXT PRIMARY KEY,
9517
9914
  value JSONB NOT NULL,
9518
9915
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9519
9916
  )
9520
9917
  `);
9521
- await applyInternalMigrations(db);
9522
9918
  await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS auth`);
9523
9919
  await db.transaction(async (tx) => {
9524
9920
  await tx.execute(drizzleOrm.sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);
@@ -9541,15 +9937,15 @@ ${tableRelations.join(",\n")}
9541
9937
  $$ LANGUAGE sql STABLE
9542
9938
  `);
9543
9939
  });
9544
- await seedDefaultRoles(db);
9940
+ await seedDefaultRoles(db, rolesTableName);
9545
9941
  console.log("✅ Auth tables ready");
9546
9942
  } catch (error) {
9547
9943
  console.error("❌ Failed to create auth tables:", error);
9548
9944
  console.warn("⚠️ Continuing without creating auth tables.");
9549
9945
  }
9550
9946
  }
9551
- async function seedDefaultRoles(db) {
9552
- const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM rebase.roles`);
9947
+ async function seedDefaultRoles(db, rolesTableName) {
9948
+ const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM ${drizzleOrm.sql.raw(rolesTableName)}`);
9553
9949
  const count = parseInt(result.rows[0]?.count || "0", 10);
9554
9950
  if (count > 0) {
9555
9951
  console.log(`📋 Found ${count} existing roles`);
@@ -9558,7 +9954,7 @@ ${tableRelations.join(",\n")}
9558
9954
  console.log("🌱 Seeding default roles...");
9559
9955
  for (const role of DEFAULT_ROLES) {
9560
9956
  await db.execute(drizzleOrm.sql`
9561
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, config)
9957
+ INSERT INTO ${drizzleOrm.sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
9562
9958
  VALUES (
9563
9959
  ${role.id},
9564
9960
  ${role.name},
@@ -9571,142 +9967,156 @@ ${tableRelations.join(",\n")}
9571
9967
  }
9572
9968
  console.log("✅ Default roles created: admin, editor, viewer");
9573
9969
  }
9574
- async function applyInternalMigrations(db) {
9575
- try {
9576
- await db.execute(drizzleOrm.sql`
9577
- ALTER TABLE rebase.users
9578
- ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE,
9579
- ADD COLUMN IF NOT EXISTS email_verification_token TEXT,
9580
- ADD COLUMN IF NOT EXISTS email_verification_sent_at TIMESTAMP WITH TIME ZONE
9581
- `);
9582
- const columnsCheck = await db.execute(drizzleOrm.sql`
9583
- SELECT column_name
9584
- FROM information_schema.columns
9585
- WHERE table_schema='rebase' AND table_name='users' AND column_name IN ('google_id', 'linkedin_id', 'provider')
9586
- `);
9587
- const existingColumns = columnsCheck.rows.map((r) => r.column_name);
9588
- if (existingColumns.includes("google_id")) {
9589
- await db.execute(drizzleOrm.sql`
9590
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9591
- SELECT id, 'google', google_id
9592
- FROM rebase.users
9593
- WHERE google_id IS NOT NULL
9594
- ON CONFLICT (provider, provider_id) DO NOTHING
9595
- `);
9596
- }
9597
- if (existingColumns.includes("linkedin_id")) {
9598
- await db.execute(drizzleOrm.sql`
9599
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9600
- SELECT id, 'linkedin', linkedin_id
9601
- FROM rebase.users
9602
- WHERE linkedin_id IS NOT NULL
9603
- ON CONFLICT (provider, provider_id) DO NOTHING
9604
- `);
9605
- }
9606
- if (existingColumns.length > 0) {
9607
- await db.execute(drizzleOrm.sql`
9608
- ALTER TABLE rebase.users
9609
- DROP COLUMN IF EXISTS provider,
9610
- DROP COLUMN IF EXISTS google_id,
9611
- DROP COLUMN IF EXISTS linkedin_id
9612
- `);
9613
- await db.execute(drizzleOrm.sql`DROP INDEX IF EXISTS rebase.idx_users_google_id`);
9614
- await db.execute(drizzleOrm.sql`DROP INDEX IF EXISTS rebase.idx_users_linkedin_id`);
9615
- console.log("✅ Migrated to user_identities and dropped legacy columns.");
9616
- }
9617
- await db.execute(drizzleOrm.sql`
9618
- ALTER TABLE rebase.roles
9619
- ADD COLUMN IF NOT EXISTS collection_permissions JSONB
9620
- `);
9621
- await db.execute(drizzleOrm.sql`
9622
- ALTER TABLE rebase.refresh_tokens
9623
- ADD COLUMN IF NOT EXISTS user_agent TEXT,
9624
- ADD COLUMN IF NOT EXISTS ip_address TEXT
9625
- `);
9626
- const constraintCheck = await db.execute(drizzleOrm.sql`
9627
- SELECT 1 FROM information_schema.table_constraints
9628
- WHERE constraint_name = 'unique_device_session'
9629
- AND table_schema = 'rebase'
9630
- AND table_name = 'refresh_tokens'
9631
- `);
9632
- if (constraintCheck.rows.length === 0) {
9633
- try {
9634
- await db.execute(drizzleOrm.sql`
9635
- ALTER TABLE rebase.refresh_tokens
9636
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9637
- `);
9638
- console.log("✅ Added unique_device_session constraint");
9639
- } catch (e) {
9640
- const errorMessage = e instanceof Error ? e.message : String(e);
9641
- if (errorMessage.includes("could not create unique index")) {
9642
- console.warn("⚠️ Duplicate sessions found, cleaning up before adding constraint...");
9643
- await db.execute(drizzleOrm.sql`
9644
- DELETE FROM rebase.refresh_tokens a
9645
- USING rebase.refresh_tokens b
9646
- WHERE a.user_id = b.user_id
9647
- AND COALESCE(a.user_agent, '') = COALESCE(b.user_agent, '')
9648
- AND COALESCE(a.ip_address, '') = COALESCE(b.ip_address, '')
9649
- AND a.created_at < b.created_at
9650
- `);
9651
- await db.execute(drizzleOrm.sql`
9652
- ALTER TABLE rebase.refresh_tokens
9653
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9654
- `).catch((retryErr) => {
9655
- const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
9656
- console.error("Failed to add unique_device_session constraint after cleanup:", retryMessage);
9657
- });
9658
- } else {
9659
- console.error("Constraint migration issue:", errorMessage);
9660
- }
9661
- }
9662
- }
9663
- } catch (error) {
9664
- console.error("❌ Failed to run internal migrations:", error);
9970
+ function getColumnKey(table, ...keys2) {
9971
+ if (!table) return void 0;
9972
+ for (const key of keys2) {
9973
+ if (key in table) return key;
9974
+ const snake = toSnakeCase(key);
9975
+ if (snake in table) return snake;
9976
+ const camel = camelCase(key);
9977
+ if (camel in table) return camel;
9665
9978
  }
9979
+ return void 0;
9980
+ }
9981
+ function getColumn(table, ...keys2) {
9982
+ if (!table) return void 0;
9983
+ const key = getColumnKey(table, ...keys2);
9984
+ return key ? table[key] : void 0;
9666
9985
  }
9667
9986
  class UserService {
9668
- constructor(db) {
9987
+ constructor(db, tableOrTables) {
9669
9988
  this.db = db;
9989
+ if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
9990
+ const tables = tableOrTables;
9991
+ this.usersTable = tables.users || users;
9992
+ this.userIdentitiesTable = tables.userIdentities || userIdentities;
9993
+ this.userRolesTable = tables.userRoles || userRoles;
9994
+ this.rolesTable = tables.roles || roles;
9995
+ } else {
9996
+ const table = tableOrTables;
9997
+ this.usersTable = table || users;
9998
+ this.userIdentitiesTable = userIdentities;
9999
+ this.userRolesTable = userRoles;
10000
+ this.rolesTable = roles;
10001
+ }
10002
+ }
10003
+ usersTable;
10004
+ userIdentitiesTable;
10005
+ userRolesTable;
10006
+ rolesTable;
10007
+ getQualifiedUsersTableName() {
10008
+ const name = drizzleOrm.getTableName(this.usersTable);
10009
+ const schema = pgCore.getTableConfig(this.usersTable).schema || "public";
10010
+ return `"${schema}"."${name}"`;
10011
+ }
10012
+ mapRowToUser(row) {
10013
+ if (!row) return row;
10014
+ const id = row.id ?? row.uid;
10015
+ const email = row.email;
10016
+ const passwordHash = row.password_hash ?? row.passwordHash ?? null;
10017
+ const displayName = row.display_name ?? row.displayName ?? null;
10018
+ const photoUrl = row.photo_url ?? row.photoUrl ?? row.photoURL ?? null;
10019
+ const emailVerified = row.email_verified ?? row.emailVerified ?? false;
10020
+ const emailVerificationToken = row.email_verification_token ?? row.emailVerificationToken ?? null;
10021
+ const emailVerificationSentAt = row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null;
10022
+ const createdAt = row.created_at ?? row.createdAt;
10023
+ const updatedAt = row.updated_at ?? row.updatedAt;
10024
+ const metadata = {
10025
+ ...row.metadata || {}
10026
+ };
10027
+ 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"]);
10028
+ for (const [key, val] of Object.entries(row)) {
10029
+ if (!knownKeys.has(key)) {
10030
+ const camelKey = camelCase(key);
10031
+ metadata[camelKey] = val;
10032
+ }
10033
+ }
10034
+ return {
10035
+ id,
10036
+ email,
10037
+ passwordHash,
10038
+ displayName,
10039
+ photoUrl,
10040
+ emailVerified,
10041
+ emailVerificationToken,
10042
+ emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
10043
+ createdAt: createdAt ? new Date(createdAt) : /* @__PURE__ */ new Date(),
10044
+ updatedAt: updatedAt ? new Date(updatedAt) : /* @__PURE__ */ new Date(),
10045
+ metadata
10046
+ };
10047
+ }
10048
+ mapPayload(data) {
10049
+ if (!data) return {};
10050
+ const payload = {};
10051
+ const idKey = getColumnKey(this.usersTable, "id") || "id";
10052
+ const emailKey = getColumnKey(this.usersTable, "email") || "email";
10053
+ const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10054
+ const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
10055
+ const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
10056
+ const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10057
+ const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10058
+ const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10059
+ const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
10060
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10061
+ const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
10062
+ if ("id" in data) payload[idKey] = data.id;
10063
+ if ("email" in data) payload[emailKey] = data.email;
10064
+ if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
10065
+ if ("displayName" in data) payload[displayNameKey] = data.displayName;
10066
+ if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
10067
+ if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
10068
+ if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
10069
+ if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
10070
+ if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
10071
+ if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
10072
+ const metadata = {
10073
+ ...data.metadata || {}
10074
+ };
10075
+ const remainingMetadata = {};
10076
+ for (const [key, val] of Object.entries(metadata)) {
10077
+ const tableColKey = getColumnKey(this.usersTable, key);
10078
+ if (tableColKey && tableColKey !== idKey && tableColKey !== emailKey && tableColKey !== passwordHashKey && tableColKey !== displayNameKey && tableColKey !== photoUrlKey && tableColKey !== emailVerifiedKey && tableColKey !== emailVerificationTokenKey && tableColKey !== emailVerificationSentAtKey && tableColKey !== createdAtKey && tableColKey !== updatedAtKey && tableColKey !== metadataKey) {
10079
+ payload[tableColKey] = val;
10080
+ } else {
10081
+ remainingMetadata[key] = val;
10082
+ }
10083
+ }
10084
+ if (metadataKey in this.usersTable) {
10085
+ payload[metadataKey] = remainingMetadata;
10086
+ }
10087
+ return payload;
9670
10088
  }
9671
10089
  async createUser(data) {
9672
- const [user] = await this.db.insert(users).values(data).returning();
9673
- return user;
10090
+ const payload = this.mapPayload(data);
10091
+ const [row] = await this.db.insert(this.usersTable).values(payload).returning();
10092
+ return this.mapRowToUser(row);
9674
10093
  }
9675
10094
  async getUserById(id) {
9676
- const [user] = await this.db.select().from(users).where(drizzleOrm.eq(users.id, id));
9677
- return user || null;
10095
+ const idCol = getColumn(this.usersTable, "id");
10096
+ if (!idCol) return null;
10097
+ const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(idCol, id));
10098
+ return row ? this.mapRowToUser(row) : null;
9678
10099
  }
9679
10100
  async getUserByEmail(email) {
9680
- const [user] = await this.db.select().from(users).where(drizzleOrm.eq(users.email, email.toLowerCase()));
9681
- return user || null;
10101
+ const emailCol = getColumn(this.usersTable, "email");
10102
+ if (!emailCol) return null;
10103
+ const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(emailCol, email.toLowerCase()));
10104
+ return row ? this.mapRowToUser(row) : null;
9682
10105
  }
9683
10106
  async getUserByIdentity(provider, providerId) {
9684
- const result = await this.db.execute(drizzleOrm.sql`
9685
- SELECT u.*
9686
- FROM rebase.users u
9687
- INNER JOIN rebase.user_identities ui ON u.id = ui.user_id
9688
- WHERE ui.provider = ${provider} AND ui.provider_id = ${providerId}
9689
- LIMIT 1
9690
- `);
9691
- if (result.rows.length === 0) return null;
9692
- const row = result.rows[0];
9693
- return {
9694
- id: row.id,
9695
- email: row.email,
9696
- passwordHash: row.password_hash ?? null,
9697
- displayName: row.display_name ?? null,
9698
- photoUrl: row.photo_url ?? null,
9699
- emailVerified: row.email_verified ?? false,
9700
- emailVerificationToken: row.email_verification_token ?? null,
9701
- emailVerificationSentAt: row.email_verification_sent_at ?? null,
9702
- createdAt: row.created_at,
9703
- updatedAt: row.updated_at
9704
- };
10107
+ const userIdCol = getColumn(this.usersTable, "id");
10108
+ if (!userIdCol) return null;
10109
+ const result = await this.db.select({
10110
+ user: this.usersTable
10111
+ }).from(this.usersTable).innerJoin(this.userIdentitiesTable, drizzleOrm.eq(userIdCol, this.userIdentitiesTable.userId)).where(drizzleOrm.sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`).limit(1);
10112
+ if (result.length === 0) return null;
10113
+ return this.mapRowToUser(result[0].user);
9705
10114
  }
9706
10115
  async getUserIdentities(userId) {
10116
+ const schema = pgCore.getTableConfig(this.userIdentitiesTable).schema || "public";
9707
10117
  const result = await this.db.execute(drizzleOrm.sql`
9708
10118
  SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
9709
- FROM rebase.user_identities
10119
+ FROM ${drizzleOrm.sql.raw(`"${schema}"."user_identities"`)}
9710
10120
  WHERE user_id = ${userId}
9711
10121
  `);
9712
10122
  return result.rows.map((row) => ({
@@ -9720,27 +10130,32 @@ ${tableRelations.join(",\n")}
9720
10130
  }));
9721
10131
  }
9722
10132
  async linkUserIdentity(userId, provider, providerId, profileData) {
9723
- await this.db.insert(userIdentities).values({
10133
+ await this.db.insert(this.userIdentitiesTable).values({
9724
10134
  userId,
9725
10135
  provider,
9726
10136
  providerId,
9727
10137
  profileData: profileData || null
9728
10138
  }).onConflictDoNothing({
9729
- target: [userIdentities.provider, userIdentities.providerId]
10139
+ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId]
9730
10140
  });
9731
10141
  }
9732
10142
  async updateUser(id, data) {
9733
- const [user] = await this.db.update(users).set({
9734
- ...data,
9735
- updatedAt: /* @__PURE__ */ new Date()
9736
- }).where(drizzleOrm.eq(users.id, id)).returning();
9737
- return user || null;
10143
+ const idCol = getColumn(this.usersTable, "id");
10144
+ if (!idCol) return null;
10145
+ const payload = this.mapPayload(data);
10146
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10147
+ payload[updatedAtKey] = /* @__PURE__ */ new Date();
10148
+ const [row] = await this.db.update(this.usersTable).set(payload).where(drizzleOrm.eq(idCol, id)).returning();
10149
+ return row ? this.mapRowToUser(row) : null;
9738
10150
  }
9739
10151
  async deleteUser(id) {
9740
- await this.db.delete(users).where(drizzleOrm.eq(users.id, id));
10152
+ const idCol = getColumn(this.usersTable, "id");
10153
+ if (!idCol) return;
10154
+ await this.db.delete(this.usersTable).where(drizzleOrm.eq(idCol, id));
9741
10155
  }
9742
10156
  async listUsers() {
9743
- return this.db.select().from(users);
10157
+ const rows = await this.db.select().from(this.usersTable);
10158
+ return rows.map((row) => this.mapRowToUser(row));
9744
10159
  }
9745
10160
  async listUsersPaginated(options) {
9746
10161
  const limit = options?.limit ?? 25;
@@ -9749,49 +10164,40 @@ ${tableRelations.join(",\n")}
9749
10164
  const orderBy = options?.orderBy || "createdAt";
9750
10165
  const orderDir = options?.orderDir || "desc";
9751
10166
  const roleId = options?.roleId;
9752
- const columnMap = {
9753
- email: "email",
9754
- displayName: "display_name",
9755
- createdAt: "created_at",
9756
- updatedAt: "updated_at",
9757
- provider: "provider"
9758
- };
9759
- const orderColumn = columnMap[orderBy] || "created_at";
10167
+ const orderCol = getColumn(this.usersTable, orderBy);
10168
+ const orderColumn = orderCol ? orderCol.name : "created_at";
9760
10169
  const direction = orderDir === "asc" ? drizzleOrm.sql`ASC` : drizzleOrm.sql`DESC`;
10170
+ const emailCol = getColumn(this.usersTable, "email");
10171
+ const emailColumn = emailCol ? emailCol.name : "email";
10172
+ const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
10173
+ const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
10174
+ const idCol = getColumn(this.usersTable, "id");
10175
+ const idColumn = idCol ? idCol.name : "id";
10176
+ const usersTableName = this.getQualifiedUsersTableName();
10177
+ const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
9761
10178
  const conditions = [];
9762
10179
  if (roleId) {
9763
- conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM rebase.user_roles ur WHERE ur.user_id = users.id AND ur.role_id = ${roleId})`);
10180
+ conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
9764
10181
  }
9765
10182
  if (search) {
9766
10183
  const pattern = `%${search}%`;
9767
- conditions.push(drizzleOrm.sql`(email ILIKE ${pattern} OR display_name ILIKE ${pattern})`);
10184
+ conditions.push(drizzleOrm.sql`(${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(emailColumn)} ILIKE ${pattern} OR ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(displayNameColumn)} ILIKE ${pattern})`);
9768
10185
  }
9769
10186
  const whereClause = conditions.length > 0 ? drizzleOrm.sql`WHERE ${drizzleOrm.sql.join(conditions, drizzleOrm.sql` AND `)}` : drizzleOrm.sql``;
9770
- const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY (SELECT count(*) FROM rebase.user_roles ur WHERE ur.user_id = users.id) DESC, ${drizzleOrm.sql.raw(orderColumn)} ${direction}`;
10187
+ const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY (SELECT count(*) FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)}) DESC, ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}`;
9771
10188
  const countResult = await this.db.execute(drizzleOrm.sql`
9772
- SELECT count(*)::int as total FROM rebase.users
10189
+ SELECT count(*)::int as total FROM ${drizzleOrm.sql.raw(usersTableName)}
9773
10190
  ${whereClause}
9774
10191
  `);
9775
10192
  const total = countResult.rows[0].total;
9776
10193
  const dataResult = await this.db.execute(drizzleOrm.sql`
9777
- SELECT * FROM rebase.users
10194
+ SELECT * FROM ${drizzleOrm.sql.raw(usersTableName)}
9778
10195
  ${whereClause}
9779
10196
  ${orderByClause}
9780
10197
  LIMIT ${limit} OFFSET ${offset}
9781
10198
  `);
9782
10199
  const rows = dataResult.rows;
9783
- const mappedUsers = rows.map((row) => ({
9784
- id: row.id,
9785
- email: row.email,
9786
- passwordHash: row.password_hash ?? row.passwordHash ?? null,
9787
- displayName: row.display_name ?? row.displayName ?? null,
9788
- photoUrl: row.photo_url ?? row.photoUrl ?? null,
9789
- emailVerified: row.email_verified ?? row.emailVerified ?? false,
9790
- emailVerificationToken: row.email_verification_token ?? row.emailVerificationToken ?? null,
9791
- emailVerificationSentAt: row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null,
9792
- createdAt: row.created_at ?? row.createdAt,
9793
- updatedAt: row.updated_at ?? row.updatedAt
9794
- }));
10200
+ const mappedUsers = rows.map((row) => this.mapRowToUser(row));
9795
10201
  return {
9796
10202
  users: mappedUsers,
9797
10203
  total,
@@ -9803,46 +10209,63 @@ ${tableRelations.join(",\n")}
9803
10209
  * Update user's password hash
9804
10210
  */
9805
10211
  async updatePassword(id, passwordHash) {
9806
- await this.db.update(users).set({
9807
- passwordHash,
9808
- updatedAt: /* @__PURE__ */ new Date()
9809
- }).where(drizzleOrm.eq(users.id, id));
10212
+ const idCol = getColumn(this.usersTable, "id");
10213
+ if (!idCol) return;
10214
+ const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10215
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10216
+ await this.db.update(this.usersTable).set({
10217
+ [passwordHashColKey]: passwordHash,
10218
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10219
+ }).where(drizzleOrm.eq(idCol, id));
9810
10220
  }
9811
10221
  /**
9812
10222
  * Set email verification status
9813
10223
  */
9814
10224
  async setEmailVerified(id, verified) {
9815
- await this.db.update(users).set({
9816
- emailVerified: verified,
9817
- emailVerificationToken: null,
9818
- updatedAt: /* @__PURE__ */ new Date()
9819
- }).where(drizzleOrm.eq(users.id, id));
10225
+ const idCol = getColumn(this.usersTable, "id");
10226
+ if (!idCol) return;
10227
+ const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10228
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10229
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10230
+ await this.db.update(this.usersTable).set({
10231
+ [emailVerifiedColKey]: verified,
10232
+ [emailVerificationTokenColKey]: null,
10233
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10234
+ }).where(drizzleOrm.eq(idCol, id));
9820
10235
  }
9821
10236
  /**
9822
10237
  * Set email verification token
9823
10238
  */
9824
10239
  async setVerificationToken(id, token) {
9825
- await this.db.update(users).set({
9826
- emailVerificationToken: token,
9827
- emailVerificationSentAt: token ? /* @__PURE__ */ new Date() : null,
9828
- updatedAt: /* @__PURE__ */ new Date()
9829
- }).where(drizzleOrm.eq(users.id, id));
10240
+ const idCol = getColumn(this.usersTable, "id");
10241
+ if (!idCol) return;
10242
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10243
+ const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10244
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10245
+ await this.db.update(this.usersTable).set({
10246
+ [emailVerificationTokenColKey]: token,
10247
+ [emailVerificationSentAtColKey]: token ? /* @__PURE__ */ new Date() : null,
10248
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10249
+ }).where(drizzleOrm.eq(idCol, id));
9830
10250
  }
9831
10251
  /**
9832
10252
  * Find user by email verification token
9833
10253
  */
9834
10254
  async getUserByVerificationToken(token) {
9835
- const [user] = await this.db.select().from(users).where(drizzleOrm.eq(users.emailVerificationToken, token));
9836
- return user || null;
10255
+ const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
10256
+ if (!tokenCol) return null;
10257
+ const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(tokenCol, token));
10258
+ return row ? this.mapRowToUser(row) : null;
9837
10259
  }
9838
10260
  /**
9839
10261
  * Get roles for a user from database
9840
10262
  */
9841
10263
  async getUserRoles(userId) {
10264
+ const rolesSchema = pgCore.getTableConfig(this.rolesTable).schema || "public";
9842
10265
  const result = await this.db.execute(drizzleOrm.sql`
9843
10266
  SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
9844
- FROM rebase.roles r
9845
- INNER JOIN rebase.user_roles ur ON r.id = ur.role_id
10267
+ FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."roles"`)} r
10268
+ INNER JOIN ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
9846
10269
  WHERE ur.user_id = ${userId}
9847
10270
  `);
9848
10271
  return result.rows.map((row) => ({
@@ -9865,10 +10288,11 @@ ${tableRelations.join(",\n")}
9865
10288
  * Set roles for a user
9866
10289
  */
9867
10290
  async setUserRoles(userId, roleIds) {
9868
- await this.db.execute(drizzleOrm.sql`DELETE FROM rebase.user_roles WHERE user_id = ${userId}`);
10291
+ const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
10292
+ await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
9869
10293
  for (const roleId of roleIds) {
9870
10294
  await this.db.execute(drizzleOrm.sql`
9871
- INSERT INTO rebase.user_roles (user_id, role_id)
10295
+ INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9872
10296
  VALUES (${userId}, ${roleId})
9873
10297
  ON CONFLICT DO NOTHING
9874
10298
  `);
@@ -9878,8 +10302,9 @@ ${tableRelations.join(",\n")}
9878
10302
  * Assign a specific role to new user
9879
10303
  */
9880
10304
  async assignDefaultRole(userId, roleId) {
10305
+ const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
9881
10306
  await this.db.execute(drizzleOrm.sql`
9882
- INSERT INTO rebase.user_roles (user_id, role_id)
10307
+ INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9883
10308
  VALUES (${userId}, ${roleId})
9884
10309
  ON CONFLICT DO NOTHING
9885
10310
  `);
@@ -9898,13 +10323,25 @@ ${tableRelations.join(",\n")}
9898
10323
  }
9899
10324
  }
9900
10325
  class RoleService {
9901
- constructor(db) {
10326
+ constructor(db, tableOrTables) {
9902
10327
  this.db = db;
10328
+ if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
10329
+ this.rolesTable = tableOrTables.roles || roles;
10330
+ } else {
10331
+ this.rolesTable = tableOrTables || roles;
10332
+ }
10333
+ }
10334
+ rolesTable;
10335
+ getQualifiedRolesTableName() {
10336
+ const name = drizzleOrm.getTableName(this.rolesTable);
10337
+ const schema = pgCore.getTableConfig(this.rolesTable).schema || "public";
10338
+ return `"${schema}"."${name}"`;
9903
10339
  }
9904
10340
  async getRoleById(id) {
10341
+ const tableName = this.getQualifiedRolesTableName();
9905
10342
  const result = await this.db.execute(drizzleOrm.sql`
9906
10343
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9907
- FROM rebase.roles
10344
+ FROM ${drizzleOrm.sql.raw(tableName)}
9908
10345
  WHERE id = ${id}
9909
10346
  `);
9910
10347
  if (result.rows.length === 0) return null;
@@ -9919,9 +10356,10 @@ ${tableRelations.join(",\n")}
9919
10356
  };
9920
10357
  }
9921
10358
  async listRoles() {
10359
+ const tableName = this.getQualifiedRolesTableName();
9922
10360
  const result = await this.db.execute(drizzleOrm.sql`
9923
10361
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9924
- FROM rebase.roles
10362
+ FROM ${drizzleOrm.sql.raw(tableName)}
9925
10363
  ORDER BY name
9926
10364
  `);
9927
10365
  return result.rows.map((row) => ({
@@ -9934,8 +10372,9 @@ ${tableRelations.join(",\n")}
9934
10372
  }));
9935
10373
  }
9936
10374
  async createRole(data) {
10375
+ const tableName = this.getQualifiedRolesTableName();
9937
10376
  const result = await this.db.execute(drizzleOrm.sql`
9938
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, collection_permissions, config)
10377
+ INSERT INTO ${drizzleOrm.sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
9939
10378
  VALUES (
9940
10379
  ${data.id},
9941
10380
  ${data.name},
@@ -9959,8 +10398,9 @@ ${tableRelations.join(",\n")}
9959
10398
  async updateRole(id, data) {
9960
10399
  const existing = await this.getRoleById(id);
9961
10400
  if (!existing) return null;
10401
+ const tableName = this.getQualifiedRolesTableName();
9962
10402
  await this.db.execute(drizzleOrm.sql`
9963
- UPDATE rebase.roles
10403
+ UPDATE ${drizzleOrm.sql.raw(tableName)}
9964
10404
  SET
9965
10405
  name = ${data.name ?? existing.name},
9966
10406
  is_admin = ${data.isAdmin ?? existing.isAdmin},
@@ -9972,23 +10412,36 @@ ${tableRelations.join(",\n")}
9972
10412
  return this.getRoleById(id);
9973
10413
  }
9974
10414
  async deleteRole(id) {
9975
- await this.db.execute(drizzleOrm.sql`DELETE FROM rebase.roles WHERE id = ${id}`);
10415
+ const tableName = this.getQualifiedRolesTableName();
10416
+ await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(tableName)} WHERE id = ${id}`);
9976
10417
  }
9977
10418
  }
9978
10419
  class RefreshTokenService {
9979
- constructor(db) {
10420
+ constructor(db, tableOrTables) {
9980
10421
  this.db = db;
10422
+ if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) {
10423
+ this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
10424
+ } else {
10425
+ this.refreshTokensTable = tableOrTables || refreshTokens;
10426
+ }
10427
+ }
10428
+ refreshTokensTable;
10429
+ getQualifiedRefreshTokensTableName() {
10430
+ const name = drizzleOrm.getTableName(this.refreshTokensTable);
10431
+ const schema = pgCore.getTableConfig(this.refreshTokensTable).schema || "public";
10432
+ return `"${schema}"."${name}"`;
9981
10433
  }
9982
10434
  async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
9983
10435
  const safeUserAgent = userAgent || "";
9984
10436
  const safeIpAddress = ipAddress || "";
10437
+ const tableName = this.getQualifiedRefreshTokensTableName();
9985
10438
  await this.db.execute(drizzleOrm.sql`
9986
- DELETE FROM rebase.refresh_tokens
10439
+ DELETE FROM ${drizzleOrm.sql.raw(tableName)}
9987
10440
  WHERE user_id = ${userId}
9988
10441
  AND user_agent = ${safeUserAgent}
9989
10442
  AND ip_address = ${safeIpAddress}
9990
10443
  `);
9991
- await this.db.insert(refreshTokens).values({
10444
+ await this.db.insert(this.refreshTokensTable).values({
9992
10445
  userId,
9993
10446
  tokenHash,
9994
10447
  expiresAt,
@@ -9998,51 +10451,63 @@ ${tableRelations.join(",\n")}
9998
10451
  }
9999
10452
  async findByHash(tokenHash) {
10000
10453
  const [token] = await this.db.select({
10001
- id: refreshTokens.id,
10002
- userId: refreshTokens.userId,
10003
- tokenHash: refreshTokens.tokenHash,
10004
- expiresAt: refreshTokens.expiresAt,
10005
- createdAt: refreshTokens.createdAt,
10006
- userAgent: refreshTokens.userAgent,
10007
- ipAddress: refreshTokens.ipAddress
10008
- }).from(refreshTokens).where(drizzleOrm.eq(refreshTokens.tokenHash, tokenHash));
10454
+ id: this.refreshTokensTable.id,
10455
+ userId: this.refreshTokensTable.userId,
10456
+ tokenHash: this.refreshTokensTable.tokenHash,
10457
+ expiresAt: this.refreshTokensTable.expiresAt,
10458
+ createdAt: this.refreshTokensTable.createdAt,
10459
+ userAgent: this.refreshTokensTable.userAgent,
10460
+ ipAddress: this.refreshTokensTable.ipAddress
10461
+ }).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
10009
10462
  return token || null;
10010
10463
  }
10011
10464
  async deleteByHash(tokenHash) {
10012
- await this.db.delete(refreshTokens).where(drizzleOrm.eq(refreshTokens.tokenHash, tokenHash));
10465
+ await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
10013
10466
  }
10014
10467
  async deleteAllForUser(userId) {
10015
- await this.db.delete(refreshTokens).where(drizzleOrm.eq(refreshTokens.userId, userId));
10468
+ await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId));
10016
10469
  }
10017
10470
  async listForUser(userId) {
10018
10471
  const tokens = await this.db.select({
10019
- id: refreshTokens.id,
10020
- userId: refreshTokens.userId,
10021
- tokenHash: refreshTokens.tokenHash,
10022
- expiresAt: refreshTokens.expiresAt,
10023
- createdAt: refreshTokens.createdAt,
10024
- userAgent: refreshTokens.userAgent,
10025
- ipAddress: refreshTokens.ipAddress
10026
- }).from(refreshTokens).where(drizzleOrm.eq(refreshTokens.userId, userId)).orderBy(refreshTokens.createdAt);
10472
+ id: this.refreshTokensTable.id,
10473
+ userId: this.refreshTokensTable.userId,
10474
+ tokenHash: this.refreshTokensTable.tokenHash,
10475
+ expiresAt: this.refreshTokensTable.expiresAt,
10476
+ createdAt: this.refreshTokensTable.createdAt,
10477
+ userAgent: this.refreshTokensTable.userAgent,
10478
+ ipAddress: this.refreshTokensTable.ipAddress
10479
+ }).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId)).orderBy(this.refreshTokensTable.createdAt);
10027
10480
  return tokens;
10028
10481
  }
10029
10482
  async deleteById(id, userId) {
10030
- await this.db.delete(refreshTokens).where(drizzleOrm.sql`${refreshTokens.id} = ${id} AND ${refreshTokens.userId} = ${userId}`);
10483
+ await this.db.delete(this.refreshTokensTable).where(drizzleOrm.sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
10031
10484
  }
10032
10485
  }
10033
10486
  class PasswordResetTokenService {
10034
- constructor(db) {
10487
+ constructor(db, tableOrTables) {
10035
10488
  this.db = db;
10489
+ if (tableOrTables && (tableOrTables.passwordResetTokens || tableOrTables.users)) {
10490
+ this.passwordResetTokensTable = tableOrTables.passwordResetTokens || passwordResetTokens;
10491
+ } else {
10492
+ this.passwordResetTokensTable = tableOrTables || passwordResetTokens;
10493
+ }
10494
+ }
10495
+ passwordResetTokensTable;
10496
+ getQualifiedPasswordResetTokensTableName() {
10497
+ const name = drizzleOrm.getTableName(this.passwordResetTokensTable);
10498
+ const schema = pgCore.getTableConfig(this.passwordResetTokensTable).schema || "public";
10499
+ return `"${schema}"."${name}"`;
10036
10500
  }
10037
10501
  /**
10038
10502
  * Create a password reset token
10039
10503
  */
10040
10504
  async createToken(userId, tokenHash, expiresAt) {
10505
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10041
10506
  await this.db.execute(drizzleOrm.sql`
10042
- DELETE FROM rebase.password_reset_tokens
10507
+ DELETE FROM ${drizzleOrm.sql.raw(tableName)}
10043
10508
  WHERE user_id = ${userId} AND used_at IS NULL
10044
10509
  `);
10045
- await this.db.insert(passwordResetTokens).values({
10510
+ await this.db.insert(this.passwordResetTokensTable).values({
10046
10511
  userId,
10047
10512
  tokenHash,
10048
10513
  expiresAt
@@ -10053,13 +10518,14 @@ ${tableRelations.join(",\n")}
10053
10518
  */
10054
10519
  async findValidByHash(tokenHash) {
10055
10520
  const [token] = await this.db.select({
10056
- userId: passwordResetTokens.userId,
10057
- expiresAt: passwordResetTokens.expiresAt
10058
- }).from(passwordResetTokens).where(drizzleOrm.eq(passwordResetTokens.tokenHash, tokenHash));
10521
+ userId: this.passwordResetTokensTable.userId,
10522
+ expiresAt: this.passwordResetTokensTable.expiresAt
10523
+ }).from(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10059
10524
  if (!token) return null;
10525
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10060
10526
  const result = await this.db.execute(drizzleOrm.sql`
10061
10527
  SELECT user_id, expires_at
10062
- FROM rebase.password_reset_tokens
10528
+ FROM ${drizzleOrm.sql.raw(tableName)}
10063
10529
  WHERE token_hash = ${tokenHash}
10064
10530
  AND used_at IS NULL
10065
10531
  AND expires_at > NOW()
@@ -10075,31 +10541,32 @@ ${tableRelations.join(",\n")}
10075
10541
  * Mark token as used
10076
10542
  */
10077
10543
  async markAsUsed(tokenHash) {
10078
- await this.db.update(passwordResetTokens).set({
10544
+ await this.db.update(this.passwordResetTokensTable).set({
10079
10545
  usedAt: /* @__PURE__ */ new Date()
10080
- }).where(drizzleOrm.eq(passwordResetTokens.tokenHash, tokenHash));
10546
+ }).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10081
10547
  }
10082
10548
  /**
10083
10549
  * Delete all tokens for a user
10084
10550
  */
10085
10551
  async deleteAllForUser(userId) {
10086
- await this.db.delete(passwordResetTokens).where(drizzleOrm.eq(passwordResetTokens.userId, userId));
10552
+ await this.db.delete(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.userId, userId));
10087
10553
  }
10088
10554
  /**
10089
10555
  * Clean up expired tokens
10090
10556
  */
10091
10557
  async deleteExpired() {
10558
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10092
10559
  await this.db.execute(drizzleOrm.sql`
10093
- DELETE FROM rebase.password_reset_tokens
10560
+ DELETE FROM ${drizzleOrm.sql.raw(tableName)}
10094
10561
  WHERE expires_at < NOW()
10095
10562
  `);
10096
10563
  }
10097
10564
  }
10098
10565
  class PostgresTokenRepository {
10099
- constructor(db) {
10566
+ constructor(db, tableOrTables) {
10100
10567
  this.db = db;
10101
- this.refreshTokenService = new RefreshTokenService(db);
10102
- this.passwordResetTokenService = new PasswordResetTokenService(db);
10568
+ this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
10569
+ this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
10103
10570
  }
10104
10571
  refreshTokenService;
10105
10572
  passwordResetTokenService;
@@ -10140,11 +10607,11 @@ ${tableRelations.join(",\n")}
10140
10607
  }
10141
10608
  }
10142
10609
  class PostgresAuthRepository {
10143
- constructor(db) {
10610
+ constructor(db, tableOrTables) {
10144
10611
  this.db = db;
10145
- this.userService = new UserService(db);
10146
- this.roleService = new RoleService(db);
10147
- this.tokenRepository = new PostgresTokenRepository(db);
10612
+ this.userService = new UserService(db, tableOrTables);
10613
+ this.roleService = new RoleService(db, tableOrTables);
10614
+ this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
10148
10615
  }
10149
10616
  userService;
10150
10617
  roleService;
@@ -10205,8 +10672,7 @@ ${tableRelations.join(",\n")}
10205
10672
  await this.userService.assignDefaultRole(userId, roleId);
10206
10673
  }
10207
10674
  async getUserWithRoles(userId) {
10208
- const result = await this.userService.getUserWithRoles(userId);
10209
- return result;
10675
+ return this.userService.getUserWithRoles(userId);
10210
10676
  }
10211
10677
  // Role operations (delegate to RoleService)
10212
10678
  async getRoleById(id) {
@@ -10394,6 +10860,24 @@ ${tableRelations.join(",\n")}
10394
10860
  return result.rowCount ?? 0;
10395
10861
  }
10396
10862
  }
10863
+ function deepEqual(a, b) {
10864
+ if (a === b) return true;
10865
+ if (a == null || b == null) return false;
10866
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
10867
+ if (Array.isArray(a) && Array.isArray(b)) {
10868
+ if (a.length !== b.length) return false;
10869
+ return a.every((v, i) => deepEqual(v, b[i]));
10870
+ }
10871
+ if (typeof a === "object" && typeof b === "object") {
10872
+ const aObj = a;
10873
+ const bObj = b;
10874
+ const aKeys = Object.keys(aObj);
10875
+ const bKeys = Object.keys(bObj);
10876
+ if (aKeys.length !== bKeys.length) return false;
10877
+ return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
10878
+ }
10879
+ return false;
10880
+ }
10397
10881
  function findChangedFields(oldValues, newValues) {
10398
10882
  const changed = [];
10399
10883
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
@@ -10403,7 +10887,7 @@ ${tableRelations.join(",\n")}
10403
10887
  if (key.startsWith("__")) continue;
10404
10888
  if (oldVal !== newVal) {
10405
10889
  if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
10406
- if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
10890
+ if (!deepEqual(oldVal, newVal)) {
10407
10891
  changed.push(key);
10408
10892
  }
10409
10893
  } else {
@@ -10521,14 +11005,32 @@ ${tableRelations.join(",\n")}
10521
11005
  if (!authConfig) return void 0;
10522
11006
  const internals = driverResult.internals;
10523
11007
  const db = internals.db;
10524
- await ensureAuthTablesExist(db);
11008
+ const registry = internals.registry;
11009
+ await ensureAuthTablesExist(db, registry);
10525
11010
  let emailService;
10526
11011
  if (authConfig.email) {
10527
11012
  emailService = serverCore.createEmailService(authConfig.email);
10528
11013
  }
10529
- const userService = new UserService(db);
10530
- const roleService = new RoleService(db);
10531
- const authRepository = new PostgresAuthRepository(db);
11014
+ const customUsersTable = registry?.getTable("users");
11015
+ const customRolesTable = registry?.getTable("roles");
11016
+ let usersSchemaName = "rebase";
11017
+ let rolesSchemaName = "rebase";
11018
+ if (customUsersTable) {
11019
+ usersSchemaName = pgCore.getTableConfig(customUsersTable).schema || "public";
11020
+ }
11021
+ if (customRolesTable) {
11022
+ rolesSchemaName = pgCore.getTableConfig(customRolesTable).schema || "public";
11023
+ }
11024
+ const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
11025
+ if (customUsersTable) {
11026
+ authTables.users = customUsersTable;
11027
+ }
11028
+ if (customRolesTable) {
11029
+ authTables.roles = customRolesTable;
11030
+ }
11031
+ const userService = new UserService(db, authTables);
11032
+ const roleService = new RoleService(db, authTables);
11033
+ const authRepository = new PostgresAuthRepository(db, authTables);
10532
11034
  return {
10533
11035
  userService,
10534
11036
  roleService,
@@ -10560,11 +11062,54 @@ ${tableRelations.join(",\n")}
10560
11062
  },
10561
11063
  mountRoutes(app, basePath, driverResult) {
10562
11064
  },
10563
- async initializeWebsockets(server, realtimeService, driver, config) {
11065
+ async initializeWebsockets(server, realtimeService, driver, config, adapter) {
10564
11066
  const {
10565
11067
  createPostgresWebSocket: createPostgresWebSocket2
10566
11068
  } = await Promise.resolve().then(() => websocket);
10567
- createPostgresWebSocket2(server, realtimeService, driver, config);
11069
+ createPostgresWebSocket2(server, realtimeService, driver, config, adapter);
11070
+ }
11071
+ };
11072
+ }
11073
+ function createPostgresAdapter(pgConfig) {
11074
+ const bootstrapper = createPostgresBootstrapper(pgConfig);
11075
+ return {
11076
+ type: bootstrapper.type,
11077
+ async initializeDriver(config) {
11078
+ return bootstrapper.initializeDriver(config);
11079
+ },
11080
+ async initializeRealtime(driverResult) {
11081
+ if (bootstrapper.initializeRealtime) {
11082
+ return bootstrapper.initializeRealtime({}, driverResult);
11083
+ }
11084
+ return void 0;
11085
+ },
11086
+ async initializeAuth(config, driverResult) {
11087
+ if (bootstrapper.initializeAuth) {
11088
+ return bootstrapper.initializeAuth(config, driverResult);
11089
+ }
11090
+ return void 0;
11091
+ },
11092
+ async initializeHistory(config, driverResult) {
11093
+ if (bootstrapper.initializeHistory) {
11094
+ return bootstrapper.initializeHistory(config, driverResult);
11095
+ }
11096
+ return void 0;
11097
+ },
11098
+ initializeWebsockets(server, realtimeService, driver, config) {
11099
+ if (bootstrapper.initializeWebsockets) {
11100
+ return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
11101
+ }
11102
+ },
11103
+ getAdmin(driverResult) {
11104
+ if (bootstrapper.getAdmin) {
11105
+ return bootstrapper.getAdmin(driverResult);
11106
+ }
11107
+ return void 0;
11108
+ },
11109
+ mountRoutes(app, basePath, driverResult) {
11110
+ if (bootstrapper.mountRoutes) {
11111
+ bootstrapper.mountRoutes(app, basePath, driverResult);
11112
+ }
10568
11113
  }
10569
11114
  };
10570
11115
  }
@@ -10578,6 +11123,8 @@ ${tableRelations.join(",\n")}
10578
11123
  exports2.PostgresRealtimeProvider = PostgresRealtimeProvider;
10579
11124
  exports2.RealtimeService = RealtimeService;
10580
11125
  exports2.appConfig = appConfig;
11126
+ exports2.createAuthSchema = createAuthSchema;
11127
+ exports2.createPostgresAdapter = createPostgresAdapter;
10581
11128
  exports2.createPostgresBootstrapper = createPostgresBootstrapper;
10582
11129
  exports2.createPostgresDatabaseConnection = createPostgresDatabaseConnection;
10583
11130
  exports2.createPostgresWebSocket = createPostgresWebSocket;
@@ -10595,6 +11142,7 @@ ${tableRelations.join(",\n")}
10595
11142
  exports2.userRolesRelations = userRolesRelations;
10596
11143
  exports2.users = users;
10597
11144
  exports2.usersRelations = usersRelations;
11145
+ exports2.usersSchema = usersSchema;
10598
11146
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
10599
11147
  });
10600
11148
  //# sourceMappingURL=index.umd.js.map