@rebasepro/server-postgresql 0.1.2 → 0.2.3

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 (71) hide show
  1. package/LICENSE +22 -6
  2. package/dist/common/src/data/query_builder.d.ts +51 -0
  3. package/dist/common/src/index.d.ts +1 -0
  4. package/dist/common/src/util/entities.d.ts +2 -2
  5. package/dist/common/src/util/relations.d.ts +1 -1
  6. package/dist/index.es.js +1435 -738
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/index.umd.js +1433 -736
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/server-postgresql/src/PostgresAdapter.d.ts +6 -0
  11. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
  12. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +0 -5
  13. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +2 -1
  14. package/dist/server-postgresql/src/auth/services.d.ts +37 -15
  15. package/dist/server-postgresql/src/index.d.ts +1 -0
  16. package/dist/server-postgresql/src/schema/auth-schema.d.ts +43 -856
  17. package/dist/server-postgresql/src/schema/default-collections.d.ts +2 -0
  18. package/dist/server-postgresql/src/schema/doctor.d.ts +10 -1
  19. package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +1 -0
  20. package/dist/server-postgresql/src/services/entity-helpers.d.ts +1 -1
  21. package/dist/server-postgresql/src/services/realtimeService.d.ts +12 -0
  22. package/dist/server-postgresql/src/websocket.d.ts +2 -1
  23. package/dist/types/src/controllers/auth.d.ts +9 -8
  24. package/dist/types/src/controllers/client.d.ts +3 -0
  25. package/dist/types/src/controllers/data.d.ts +21 -0
  26. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  27. package/dist/types/src/types/collections.d.ts +67 -2
  28. package/dist/types/src/types/database_adapter.d.ts +94 -0
  29. package/dist/types/src/types/entity_actions.d.ts +7 -1
  30. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  31. package/dist/types/src/types/entity_views.d.ts +36 -1
  32. package/dist/types/src/types/index.d.ts +2 -0
  33. package/dist/types/src/types/plugins.d.ts +1 -1
  34. package/dist/types/src/types/properties.d.ts +24 -5
  35. package/dist/types/src/types/property_config.d.ts +6 -2
  36. package/dist/types/src/types/relations.d.ts +1 -1
  37. package/dist/types/src/types/translations.d.ts +8 -0
  38. package/dist/types/src/users/user.d.ts +5 -0
  39. package/package.json +22 -15
  40. package/src/PostgresAdapter.ts +59 -0
  41. package/src/PostgresBackendDriver.ts +66 -13
  42. package/src/PostgresBootstrapper.ts +35 -15
  43. package/src/auth/ensure-tables.ts +82 -189
  44. package/src/auth/services.ts +421 -170
  45. package/src/cli.ts +49 -13
  46. package/src/data-transformer.ts +78 -8
  47. package/src/history/HistoryService.ts +25 -2
  48. package/src/index.ts +1 -0
  49. package/src/schema/auth-schema.ts +130 -98
  50. package/src/schema/default-collections.ts +69 -0
  51. package/src/schema/doctor-cli.ts +5 -1
  52. package/src/schema/doctor.ts +166 -48
  53. package/src/schema/generate-drizzle-schema-logic.ts +74 -27
  54. package/src/schema/generate-drizzle-schema.ts +13 -3
  55. package/src/schema/introspect-db-inference.ts +5 -5
  56. package/src/schema/introspect-db-logic.ts +9 -2
  57. package/src/schema/introspect-db.ts +14 -3
  58. package/src/services/EntityFetchService.ts +5 -5
  59. package/src/services/RelationService.ts +2 -2
  60. package/src/services/entity-helpers.ts +1 -1
  61. package/src/services/realtimeService.ts +145 -136
  62. package/src/utils/drizzle-conditions.ts +16 -2
  63. package/src/websocket.ts +113 -37
  64. package/test/auth-services.test.ts +163 -74
  65. package/test/data-transformer-hardening.test.ts +57 -0
  66. package/test/data-transformer.test.ts +43 -0
  67. package/test/generate-drizzle-schema.test.ts +7 -5
  68. package/test/introspect-db-utils.test.ts +4 -1
  69. package/test/postgresDataDriver.test.ts +147 -1
  70. package/test/realtimeService.test.ts +7 -7
  71. package/test/websocket.test.ts +139 -0
package/dist/index.es.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Pool, Client } from "pg";
2
2
  import { drizzle } from "drizzle-orm/node-postgres";
3
3
  import { sql, inArray, eq, and, or, ilike, asc, desc, gt, lt, getTableName as getTableName$1, count, relations, isTable } from "drizzle-orm";
4
- import { pgSchema, timestamp, varchar, boolean, uuid, jsonb, primaryKey, unique } from "drizzle-orm/pg-core";
4
+ import { PgVarchar, PgText, PgChar, pgSchema, pgTable, timestamp, jsonb, varchar, boolean, uuid, primaryKey, unique, getTableConfig } from "drizzle-orm/pg-core";
5
5
  import { createHash, randomUUID } from "crypto";
6
6
  import * as fs from "fs";
7
7
  import { promises } from "fs";
@@ -51,6 +51,12 @@ function createPostgresDatabaseConnection(connectionString, schema, poolConfig)
51
51
  connectionString
52
52
  };
53
53
  }
54
+ class Vector {
55
+ value;
56
+ constructor(value) {
57
+ this.value = value;
58
+ }
59
+ }
54
60
  function isPostgresCollection(collection) {
55
61
  return !collection.driver || collection.driver === "postgres";
56
62
  }
@@ -127,16 +133,25 @@ const DEFAULT_ONE_OF_VALUE = "value";
127
133
  const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
128
134
  const snakeCaseRegex = tokenizeRegex;
129
135
  const toSnakeCase = (str) => {
136
+ if (!str || typeof str !== "string") return "";
130
137
  const regExpMatchArray = str.match(snakeCaseRegex);
131
138
  if (!regExpMatchArray) return "";
132
139
  return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
133
140
  };
141
+ function camelCase(str) {
142
+ if (!str) return "";
143
+ if (str.length === 1) return str.toLowerCase();
144
+ const parts = str.split(/[-_ ]+/).filter(Boolean);
145
+ if (parts.length === 0) return "";
146
+ return parts[0].toLowerCase() + // Transform remaining parts to have first letter uppercase
147
+ parts.slice(1).map((part) => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase()).join("");
148
+ }
134
149
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
135
150
  function commonjsRequire(path2) {
136
151
  throw new Error('Could not dynamically require "' + path2 + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
137
152
  }
138
153
  var object_hash = { exports: {} };
139
- (function(module, exports$1) {
154
+ (function(module, exports) {
140
155
  !function(e) {
141
156
  module.exports = e();
142
157
  }(function() {
@@ -1143,126 +1158,58 @@ function enumToObjectEntries(enumValues) {
1143
1158
  });
1144
1159
  }
1145
1160
  }
1146
- function getSubcollections(collection) {
1147
- if (collection.childCollections) {
1148
- return collection.childCollections() ?? [];
1149
- }
1150
- if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1151
- return collection.subcollections() ?? [];
1161
+ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1162
+ if (!relation.target) {
1163
+ throw new Error("Relation is missing a `target` collection.");
1152
1164
  }
1153
- if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
1154
- const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
1155
- return manyRelations.map((r) => {
1156
- const target = r.target();
1157
- if (!target) return void 0;
1158
- const relationKey = r.relationName || target.slug;
1159
- let customName;
1160
- if (collection.properties) {
1161
- const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1162
- if (prop && prop[1].name) {
1163
- customName = prop[1].name;
1164
- }
1165
- }
1166
- const baseOverrides = {
1167
- slug: relationKey
1168
- };
1169
- if (customName) {
1170
- baseOverrides.name = customName;
1171
- baseOverrides.singularName = customName;
1172
- }
1173
- const targetWithOverrides = {
1174
- ...target,
1175
- ...baseOverrides
1165
+ const rawTarget = relation.target;
1166
+ let targetCollection;
1167
+ if (typeof rawTarget === "string") {
1168
+ if (resolveCollection) {
1169
+ targetCollection = resolveCollection(rawTarget);
1170
+ }
1171
+ if (!targetCollection) {
1172
+ targetCollection = {
1173
+ slug: rawTarget,
1174
+ name: rawTarget
1176
1175
  };
1177
- return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1178
- }).filter((c) => Boolean(c));
1179
- }
1180
- return [];
1181
- }
1182
- function hasPropertyCallbacks(properties, callbackName) {
1183
- if (!properties) return false;
1184
- for (const property of Object.values(properties)) {
1185
- if (property.callbacks?.[callbackName]) return true;
1186
- if (property.type === "map" && property.properties) {
1187
- if (hasPropertyCallbacks(property.properties, callbackName)) return true;
1188
- } else if (property.type === "array" && property.of) {
1189
- const ofs = Array.isArray(property.of) ? property.of : [property.of];
1190
- for (const of of ofs) {
1191
- if (of.callbacks?.[callbackName]) return true;
1192
- if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
1193
- }
1194
1176
  }
1195
- }
1196
- return false;
1197
- }
1198
- async function processProperties(properties, values, previousValues, propsContext, callbackName) {
1199
- if (!values || typeof values !== "object") return values;
1200
- const result = {
1201
- ...values
1202
- };
1203
- for (const [key, property] of Object.entries(properties)) {
1204
- if (result[key] === void 0) continue;
1205
- let currentValue = result[key];
1206
- const previousValue = previousValues?.[key];
1207
- if (property.type === "array" && Array.isArray(currentValue)) {
1208
- if (property.of && !Array.isArray(property.of)) {
1209
- currentValue = await Promise.all(currentValue.map(async (item, index) => {
1210
- const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
1211
- const singlePropData = {
1212
- "_tmp": property.of
1213
- };
1214
- const res = await processProperties(singlePropData, {
1215
- "_tmp": item
1216
- }, {
1217
- "_tmp": prevItem
1218
- }, propsContext, callbackName);
1219
- return res["_tmp"];
1220
- }));
1177
+ } else if (typeof rawTarget === "function") {
1178
+ const evaluated = rawTarget();
1179
+ if (typeof evaluated === "string") {
1180
+ if (resolveCollection) {
1181
+ targetCollection = resolveCollection(evaluated);
1221
1182
  }
1222
- } else if (property.type === "map" && property.properties && typeof currentValue === "object") {
1223
- currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
1224
- }
1225
- if (property.callbacks?.[callbackName]) {
1226
- const cbRes = await Promise.resolve(property.callbacks[callbackName]({
1227
- ...propsContext,
1228
- value: currentValue,
1229
- previousValue
1230
- }));
1231
- if (cbRes !== void 0) {
1232
- currentValue = cbRes;
1183
+ if (!targetCollection) {
1184
+ targetCollection = {
1185
+ slug: evaluated,
1186
+ name: evaluated
1187
+ };
1233
1188
  }
1189
+ } else {
1190
+ targetCollection = evaluated;
1234
1191
  }
1235
- result[key] = currentValue;
1236
- }
1237
- return result;
1238
- }
1239
- const buildPropertyCallbacks = (properties) => {
1240
- if (!properties) return void 0;
1241
- const propertyCallbacks = {};
1242
- if (hasPropertyCallbacks(properties, "afterRead")) {
1243
- propertyCallbacks.afterRead = async (props) => {
1244
- const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
1245
- return {
1246
- ...props.entity,
1247
- values: processedValues
1248
- };
1249
- };
1192
+ } else if (rawTarget && typeof rawTarget === "object") {
1193
+ targetCollection = rawTarget;
1250
1194
  }
1251
- if (hasPropertyCallbacks(properties, "beforeSave")) {
1252
- propertyCallbacks.beforeSave = async (props) => {
1253
- return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
1254
- };
1255
- }
1256
- return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1257
- };
1258
- function sanitizeRelation(relation, sourceCollection) {
1259
- if (!relation.target) {
1260
- throw new Error("Relation is missing a `target` collection.");
1195
+ if (!targetCollection) {
1196
+ throw new Error("Relation is missing a valid `target` collection.");
1261
1197
  }
1262
- const targetCollection = relation.target();
1263
1198
  const newRelation = {
1264
1199
  ...relation
1265
1200
  };
1201
+ newRelation.target = () => {
1202
+ if (typeof rawTarget === "string") {
1203
+ return resolveCollection && resolveCollection(rawTarget) || targetCollection;
1204
+ } else if (typeof rawTarget === "function") {
1205
+ const evaluated = rawTarget();
1206
+ if (typeof evaluated === "string") {
1207
+ return resolveCollection && resolveCollection(evaluated) || targetCollection;
1208
+ }
1209
+ return evaluated;
1210
+ }
1211
+ return targetCollection;
1212
+ };
1266
1213
  if (!newRelation.relationName) {
1267
1214
  newRelation.relationName = toSnakeCase(targetCollection.slug);
1268
1215
  }
@@ -1315,6 +1262,17 @@ function sanitizeRelation(relation, sourceCollection) {
1315
1262
  break;
1316
1263
  }
1317
1264
  }
1265
+ if (!isManyToManyInverse && targetCollection.properties) {
1266
+ for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
1267
+ if (prop.type !== "relation") continue;
1268
+ const relProp = prop;
1269
+ const relName = relProp.relationName || propKey;
1270
+ if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
1271
+ isManyToManyInverse = true;
1272
+ break;
1273
+ }
1274
+ }
1275
+ }
1318
1276
  } catch (e) {
1319
1277
  }
1320
1278
  }
@@ -1352,11 +1310,14 @@ function resolveCollectionRelations(collection) {
1352
1310
  const registeredRelationNames = /* @__PURE__ */ new Set();
1353
1311
  if (relCollection.relations) {
1354
1312
  relCollection.relations.forEach((relation) => {
1355
- const normalizedRelation = sanitizeRelation(relation, collection);
1356
- const relationKey = normalizedRelation.relationName;
1357
- if (relationKey) {
1358
- relations2[relationKey] = normalizedRelation;
1359
- registeredRelationNames.add(relationKey);
1313
+ try {
1314
+ const normalizedRelation = sanitizeRelation(relation, collection);
1315
+ const relationKey = normalizedRelation.relationName;
1316
+ if (relationKey) {
1317
+ relations2[relationKey] = normalizedRelation;
1318
+ registeredRelationNames.add(relationKey);
1319
+ }
1320
+ } catch (e) {
1360
1321
  }
1361
1322
  });
1362
1323
  }
@@ -1404,12 +1365,8 @@ function resolvePropertyRelation({
1404
1365
  overrides: relProp.overrides
1405
1366
  };
1406
1367
  }
1407
- const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === relProp.relationName);
1408
- if (!relation) {
1409
- console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1410
- return void 0;
1411
- }
1412
- return relation;
1368
+ console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1369
+ return void 0;
1413
1370
  }
1414
1371
  function getTableName(collection) {
1415
1372
  if (getDataSourceCapabilities(collection.driver).supportsRelations) {
@@ -1436,8 +1393,121 @@ function findRelation(resolvedRelations, key) {
1436
1393
  if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
1437
1394
  return void 0;
1438
1395
  }
1396
+ function getSubcollections(collection) {
1397
+ if (collection.childCollections) {
1398
+ return collection.childCollections() ?? [];
1399
+ }
1400
+ if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1401
+ return collection.subcollections() ?? [];
1402
+ }
1403
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
1404
+ const resolvedRelations = resolveCollectionRelations(collection);
1405
+ const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
1406
+ return manyRelations.map((r) => {
1407
+ const target = r.target();
1408
+ if (!target) return void 0;
1409
+ const relationKey = r.relationName || target.slug;
1410
+ let customName;
1411
+ if (collection.properties) {
1412
+ const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1413
+ if (prop && prop[1].name) {
1414
+ customName = prop[1].name;
1415
+ }
1416
+ }
1417
+ const baseOverrides = {
1418
+ slug: relationKey
1419
+ };
1420
+ if (customName) {
1421
+ baseOverrides.name = customName;
1422
+ baseOverrides.singularName = customName;
1423
+ }
1424
+ const targetWithOverrides = {
1425
+ ...target,
1426
+ ...baseOverrides
1427
+ };
1428
+ return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1429
+ }).filter((c) => Boolean(c));
1430
+ }
1431
+ return [];
1432
+ }
1433
+ function hasPropertyCallbacks(properties, callbackName) {
1434
+ if (!properties) return false;
1435
+ for (const property of Object.values(properties)) {
1436
+ if (property.callbacks?.[callbackName]) return true;
1437
+ if (property.type === "map" && property.properties) {
1438
+ if (hasPropertyCallbacks(property.properties, callbackName)) return true;
1439
+ } else if (property.type === "array" && property.of) {
1440
+ const ofs = Array.isArray(property.of) ? property.of : [property.of];
1441
+ for (const of of ofs) {
1442
+ if (of.callbacks?.[callbackName]) return true;
1443
+ if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
1444
+ }
1445
+ }
1446
+ }
1447
+ return false;
1448
+ }
1449
+ async function processProperties(properties, values, previousValues, propsContext, callbackName) {
1450
+ if (!values || typeof values !== "object") return values;
1451
+ const result = {
1452
+ ...values
1453
+ };
1454
+ for (const [key, property] of Object.entries(properties)) {
1455
+ if (result[key] === void 0) continue;
1456
+ let currentValue = result[key];
1457
+ const previousValue = previousValues?.[key];
1458
+ if (property.type === "array" && Array.isArray(currentValue)) {
1459
+ if (property.of && !Array.isArray(property.of)) {
1460
+ currentValue = await Promise.all(currentValue.map(async (item, index) => {
1461
+ const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
1462
+ const singlePropData = {
1463
+ "_tmp": property.of
1464
+ };
1465
+ const res = await processProperties(singlePropData, {
1466
+ "_tmp": item
1467
+ }, {
1468
+ "_tmp": prevItem
1469
+ }, propsContext, callbackName);
1470
+ return res["_tmp"];
1471
+ }));
1472
+ }
1473
+ } else if (property.type === "map" && property.properties && typeof currentValue === "object") {
1474
+ currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
1475
+ }
1476
+ if (property.callbacks?.[callbackName]) {
1477
+ const cbRes = await Promise.resolve(property.callbacks[callbackName]({
1478
+ ...propsContext,
1479
+ value: currentValue,
1480
+ previousValue
1481
+ }));
1482
+ if (cbRes !== void 0) {
1483
+ currentValue = cbRes;
1484
+ }
1485
+ }
1486
+ result[key] = currentValue;
1487
+ }
1488
+ return result;
1489
+ }
1490
+ const buildPropertyCallbacks = (properties) => {
1491
+ if (!properties) return void 0;
1492
+ const propertyCallbacks = {};
1493
+ if (hasPropertyCallbacks(properties, "afterRead")) {
1494
+ propertyCallbacks.afterRead = async (props) => {
1495
+ const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
1496
+ return {
1497
+ ...props.entity,
1498
+ values: processedValues
1499
+ };
1500
+ };
1501
+ }
1502
+ if (hasPropertyCallbacks(properties, "beforeSave")) {
1503
+ propertyCallbacks.beforeSave = async (props) => {
1504
+ return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
1505
+ };
1506
+ }
1507
+ return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1508
+ };
1439
1509
  var logic = { exports: {} };
1440
- (function(module, exports$1) {
1510
+ (function(module, exports) {
1441
1511
  (function(root, factory) {
1442
1512
  {
1443
1513
  module.exports = factory();
@@ -2194,7 +2264,7 @@ function createSupportedComparatorMap({ areArrayBuffersEqual: areArrayBuffersEqu
2194
2264
  "[object Uint32Array]": areTypedArraysEqual2
2195
2265
  };
2196
2266
  }
2197
- const deepEqual = createCustomEqual();
2267
+ const deepEqual$1 = createCustomEqual();
2198
2268
  createCustomEqual({ strict: true });
2199
2269
  createCustomEqual({ circular: true });
2200
2270
  createCustomEqual({
@@ -2263,10 +2333,16 @@ class CollectionRegistry {
2263
2333
  */
2264
2334
  registerMultiple(collections) {
2265
2335
  const rawSnapshot = collections.map((c) => removeFunctions(c));
2266
- if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
2336
+ if (this.lastRawInputSnapshot && deepEqual$1(this.lastRawInputSnapshot, rawSnapshot)) {
2267
2337
  return false;
2268
2338
  }
2269
2339
  this.reset();
2340
+ collections.forEach((c) => {
2341
+ if (c.slug) {
2342
+ this.collectionsBySlug.set(c.slug, c);
2343
+ }
2344
+ this.collectionsByTableName.set(getTableName(c), c);
2345
+ });
2270
2346
  const normalizedCollections = collections.map((c) => this.normalizeCollection({
2271
2347
  ...c
2272
2348
  }));
@@ -2337,15 +2413,25 @@ class CollectionRegistry {
2337
2413
  const mergedRelationsRaw = [...extractedRelations];
2338
2414
  for (const manual of manualRelations) {
2339
2415
  const name = manual.relationName;
2340
- if (!name || !mergedRelationsRaw.find((r) => r.relationName === name)) {
2416
+ if (!name) {
2341
2417
  mergedRelationsRaw.push(manual);
2418
+ } else {
2419
+ const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
2420
+ if (existingIndex === -1) {
2421
+ mergedRelationsRaw.push(manual);
2422
+ } else {
2423
+ mergedRelationsRaw[existingIndex] = {
2424
+ ...manual,
2425
+ ...mergedRelationsRaw[existingIndex]
2426
+ };
2427
+ }
2342
2428
  }
2343
2429
  }
2344
2430
  let mergedRelations = mergedRelationsRaw;
2345
2431
  if (getDataSourceCapabilities(result.driver).supportsRelations) {
2346
2432
  mergedRelations = mergedRelationsRaw.map((r) => {
2347
2433
  try {
2348
- return sanitizeRelation(r, result);
2434
+ return sanitizeRelation(r, result, (slug) => this.get(slug));
2349
2435
  } catch {
2350
2436
  return r;
2351
2437
  }
@@ -2558,6 +2644,118 @@ class CollectionRegistry {
2558
2644
  };
2559
2645
  }
2560
2646
  }
2647
+ function mapOperator(op) {
2648
+ switch (op) {
2649
+ case "==":
2650
+ return "eq";
2651
+ case "!=":
2652
+ return "neq";
2653
+ case ">":
2654
+ return "gt";
2655
+ case ">=":
2656
+ return "gte";
2657
+ case "<":
2658
+ return "lt";
2659
+ case "<=":
2660
+ return "lte";
2661
+ case "array-contains":
2662
+ return "cs";
2663
+ case "array-contains-any":
2664
+ return "csa";
2665
+ case "not-in":
2666
+ return "nin";
2667
+ default:
2668
+ return op;
2669
+ }
2670
+ }
2671
+ class QueryBuilder {
2672
+ constructor(collection) {
2673
+ this.collection = collection;
2674
+ }
2675
+ params = {
2676
+ where: {}
2677
+ };
2678
+ /**
2679
+ * Add a filter condition to your query.
2680
+ * @example
2681
+ * client.collection('users').where('age', '>=', 18).find()
2682
+ */
2683
+ where(column, operator, value) {
2684
+ if (!this.params.where) {
2685
+ this.params.where = {};
2686
+ }
2687
+ const mappedOp = mapOperator(operator);
2688
+ let formattedValue = value;
2689
+ if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2690
+ formattedValue = `(${value.join(",")})`;
2691
+ } else if (value === null) {
2692
+ formattedValue = "null";
2693
+ }
2694
+ this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2695
+ return this;
2696
+ }
2697
+ /**
2698
+ * Order the results by a specific column.
2699
+ * @example
2700
+ * client.collection('users').orderBy('createdAt', 'desc').find()
2701
+ */
2702
+ orderBy(column, ascending = "asc") {
2703
+ this.params.orderBy = `${column}:${ascending}`;
2704
+ return this;
2705
+ }
2706
+ /**
2707
+ * Limit the number of results returned.
2708
+ */
2709
+ limit(count2) {
2710
+ this.params.limit = count2;
2711
+ return this;
2712
+ }
2713
+ /**
2714
+ * Skip the first N results.
2715
+ */
2716
+ offset(count2) {
2717
+ this.params.offset = count2;
2718
+ return this;
2719
+ }
2720
+ /**
2721
+ * Set a free-text search string if supported by the backend.
2722
+ */
2723
+ search(searchString) {
2724
+ this.params.searchString = searchString;
2725
+ return this;
2726
+ }
2727
+ /**
2728
+ * Include related entities in the response.
2729
+ * Relations will be populated with full entity data instead of just IDs.
2730
+ *
2731
+ * @param relations - Relation names to include, or "*" for all.
2732
+ * @example
2733
+ * // Include specific relations
2734
+ * client.data.posts.include("tags", "author").find()
2735
+ *
2736
+ * // Include all relations
2737
+ * client.data.posts.include("*").find()
2738
+ */
2739
+ include(...relations2) {
2740
+ this.params.include = relations2;
2741
+ return this;
2742
+ }
2743
+ /**
2744
+ * Execute the find query and return the results.
2745
+ */
2746
+ async find() {
2747
+ return this.collection.find(this.params);
2748
+ }
2749
+ /**
2750
+ * Listen to realtime updates matching this query.
2751
+ */
2752
+ listen(onUpdate, onError) {
2753
+ if (!this.collection.listen) {
2754
+ throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
2755
+ }
2756
+ return this.collection.listen(this.params, onUpdate, onError);
2757
+ }
2758
+ }
2561
2759
  function convertWhereToFilter(where) {
2562
2760
  if (!where) return void 0;
2563
2761
  const operatorMap = {
@@ -2637,7 +2835,7 @@ function parseOrderBy(orderBy) {
2637
2835
  return [field, direction];
2638
2836
  }
2639
2837
  function createDriverAccessor(driver, slug) {
2640
- return {
2838
+ const accessor = {
2641
2839
  async find(params) {
2642
2840
  const orderParsed = parseOrderBy(params?.orderBy);
2643
2841
  const entities = await driver.fetchCollection({
@@ -2731,8 +2929,28 @@ function createDriverAccessor(driver, slug) {
2731
2929
  onUpdate: (entity) => onUpdate(entity ?? void 0),
2732
2930
  onError
2733
2931
  });
2734
- } : void 0
2932
+ } : void 0,
2933
+ // Fluent Query Builder
2934
+ where(column, operator, value) {
2935
+ return new QueryBuilder(accessor).where(column, operator, value);
2936
+ },
2937
+ orderBy(column, ascending) {
2938
+ return new QueryBuilder(accessor).orderBy(column, ascending);
2939
+ },
2940
+ limit(count2) {
2941
+ return new QueryBuilder(accessor).limit(count2);
2942
+ },
2943
+ offset(count2) {
2944
+ return new QueryBuilder(accessor).offset(count2);
2945
+ },
2946
+ search(searchString) {
2947
+ return new QueryBuilder(accessor).search(searchString);
2948
+ },
2949
+ include(...relations2) {
2950
+ return new QueryBuilder(accessor).include(...relations2);
2951
+ }
2735
2952
  };
2953
+ return accessor;
2736
2954
  }
2737
2955
  function buildRebaseData(driver) {
2738
2956
  const cache = /* @__PURE__ */ new Map();
@@ -2784,8 +3002,14 @@ class DrizzleConditionBuilder {
2784
3002
  static buildSingleFilterCondition(column, op, value) {
2785
3003
  switch (op) {
2786
3004
  case "==":
3005
+ if (value === null || value === void 0) {
3006
+ return sql`${column} IS NULL`;
3007
+ }
2787
3008
  return eq(column, value);
2788
3009
  case "!=":
3010
+ if (value === null || value === void 0) {
3011
+ return sql`${column} IS NOT NULL`;
3012
+ }
2789
3013
  return sql`${column} != ${value}`;
2790
3014
  case ">":
2791
3015
  return sql`${column} > ${value}`;
@@ -3116,7 +3340,10 @@ class DrizzleConditionBuilder {
3116
3340
  if (p.type === "string" && !p.enum && p.isId !== "uuid") {
3117
3341
  const fieldColumn = table[key];
3118
3342
  if (fieldColumn) {
3119
- searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
3343
+ const supportsILike = fieldColumn instanceof PgVarchar || fieldColumn instanceof PgText || fieldColumn instanceof PgChar || fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn);
3344
+ if (supportsILike) {
3345
+ searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
3346
+ }
3120
3347
  }
3121
3348
  }
3122
3349
  }
@@ -3589,6 +3816,31 @@ function serializePropertyToServer(value, property) {
3589
3816
  return result;
3590
3817
  }
3591
3818
  return value;
3819
+ case "vector": {
3820
+ if (value instanceof Vector) {
3821
+ return value.value;
3822
+ }
3823
+ if (value && typeof value === "object" && "value" in value && Array.isArray(value.value)) {
3824
+ return value.value.map(Number);
3825
+ }
3826
+ if (Array.isArray(value)) {
3827
+ return value.map(Number);
3828
+ }
3829
+ return value;
3830
+ }
3831
+ case "binary":
3832
+ if (typeof value === "string") {
3833
+ if (value.startsWith("data:application/octet-stream;base64,")) {
3834
+ const base64Data = value.split(",")[1];
3835
+ if (base64Data) {
3836
+ return Buffer.from(base64Data, "base64");
3837
+ }
3838
+ }
3839
+ }
3840
+ if (Buffer.isBuffer(value)) {
3841
+ return value;
3842
+ }
3843
+ return value;
3592
3844
  case "string":
3593
3845
  if (typeof value === "string") {
3594
3846
  if (value.startsWith("data:application/octet-stream;base64,")) {
@@ -3733,6 +3985,21 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3733
3985
  return value;
3734
3986
  }
3735
3987
  switch (property.type) {
3988
+ case "binary": {
3989
+ let buf = null;
3990
+ if (Buffer.isBuffer(value)) {
3991
+ buf = value;
3992
+ } else if (typeof value === "object" && value !== null) {
3993
+ const rawVal = value;
3994
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
3995
+ buf = Buffer.from(rawVal.data);
3996
+ }
3997
+ }
3998
+ if (buf) {
3999
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
4000
+ }
4001
+ return value;
4002
+ }
3736
4003
  case "string": {
3737
4004
  if (typeof value === "string") return value;
3738
4005
  let isBuffer = false;
@@ -3740,9 +4007,12 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3740
4007
  if (Buffer.isBuffer(value)) {
3741
4008
  isBuffer = true;
3742
4009
  buf = value;
3743
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3744
- isBuffer = true;
3745
- buf = Buffer.from(value.data);
4010
+ } else if (typeof value === "object" && value !== null) {
4011
+ const rawVal = value;
4012
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
4013
+ isBuffer = true;
4014
+ buf = Buffer.from(rawVal.data);
4015
+ }
3746
4016
  }
3747
4017
  if (isBuffer && buf) {
3748
4018
  let isPrintable = true;
@@ -3829,6 +4099,25 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3829
4099
  return isNaN(parsed) ? null : parsed;
3830
4100
  }
3831
4101
  return value;
4102
+ case "vector": {
4103
+ let nums = [];
4104
+ if (typeof value === "string") {
4105
+ nums = value.slice(1, -1).split(",").map(Number);
4106
+ } else if (Array.isArray(value)) {
4107
+ nums = value.map(Number);
4108
+ } else if (value instanceof Vector) {
4109
+ nums = value.value;
4110
+ } else if (typeof value === "object" && value !== null && "value" in value) {
4111
+ const valObj = value;
4112
+ if (Array.isArray(valObj.value)) {
4113
+ nums = valObj.value.map(Number);
4114
+ }
4115
+ }
4116
+ return {
4117
+ __type: "Vector",
4118
+ value: nums
4119
+ };
4120
+ }
3832
4121
  case "date": {
3833
4122
  let date;
3834
4123
  if (value instanceof Date) {
@@ -3853,9 +4142,12 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
3853
4142
  if (Buffer.isBuffer(value)) {
3854
4143
  isBuffer = true;
3855
4144
  buf = value;
3856
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3857
- isBuffer = true;
3858
- buf = Buffer.from(value.data);
4145
+ } else if (typeof value === "object" && value !== null) {
4146
+ const rawVal = value;
4147
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
4148
+ isBuffer = true;
4149
+ buf = Buffer.from(rawVal.data);
4150
+ }
3859
4151
  }
3860
4152
  if (isBuffer && buf) {
3861
4153
  let isPrintable = true;
@@ -4645,7 +4937,7 @@ class RelationService {
4645
4937
  if (parentFKValue !== null && parentFKValue !== void 0) {
4646
4938
  await tx.update(targetTable).set({
4647
4939
  [targetFKColName]: null
4648
- }).where(eq(targetFKCol, parentFKValue));
4940
+ }).where(eq(targetFKCol, String(parentFKValue)));
4649
4941
  }
4650
4942
  continue;
4651
4943
  }
@@ -4654,7 +4946,7 @@ class RelationService {
4654
4946
  if (parentFKValue !== null && parentFKValue !== void 0) {
4655
4947
  await tx.update(targetTable).set({
4656
4948
  [targetFKColName]: null
4657
- }).where(eq(targetFKCol, parentFKValue));
4949
+ }).where(eq(targetFKCol, String(parentFKValue)));
4658
4950
  } else {
4659
4951
  console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
4660
4952
  continue;
@@ -6366,7 +6658,7 @@ class PostgresBackendDriver {
6366
6658
  callbacks: void 0,
6367
6659
  propertyCallbacks: void 0
6368
6660
  };
6369
- const registryCollection = this.registry.getCollectionByPath(path2);
6661
+ const registryCollection = this.registry?.getCollectionByPath(path2);
6370
6662
  const resolvedCollection = registryCollection ? {
6371
6663
  ...collection,
6372
6664
  ...registryCollection
@@ -6414,7 +6706,8 @@ class PostgresBackendDriver {
6414
6706
  user: this.user,
6415
6707
  driver: this,
6416
6708
  data: this.data,
6417
- client: this.client
6709
+ client: this.client,
6710
+ storageSource: this.client?.storage
6418
6711
  };
6419
6712
  return Promise.all(entities.map(async (entity) => {
6420
6713
  let fetched = entity;
@@ -6509,7 +6802,8 @@ class PostgresBackendDriver {
6509
6802
  user: this.user,
6510
6803
  driver: this,
6511
6804
  data: this.data,
6512
- client: this.client
6805
+ client: this.client,
6806
+ storageSource: this.client?.storage
6513
6807
  };
6514
6808
  if (callbacks?.afterRead) {
6515
6809
  entity = await callbacks.afterRead({
@@ -6579,7 +6873,8 @@ class PostgresBackendDriver {
6579
6873
  user: this.user,
6580
6874
  driver: this,
6581
6875
  data: this.data,
6582
- client: this.client
6876
+ client: this.client,
6877
+ storageSource: this.client?.storage
6583
6878
  };
6584
6879
  let previousValuesForHistory;
6585
6880
  if (status === "existing" && entityId) {
@@ -6728,26 +7023,37 @@ class PostgresBackendDriver {
6728
7023
  user: this.user,
6729
7024
  driver: this,
6730
7025
  data: this.data,
6731
- client: this.client
7026
+ client: this.client,
7027
+ storageSource: this.client?.storage
6732
7028
  };
6733
7029
  if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
7030
+ let preventDefault = false;
6734
7031
  if (callbacks?.beforeDelete) {
6735
- await callbacks.beforeDelete({
7032
+ const result = await callbacks.beforeDelete({
6736
7033
  collection: resolvedCollection,
6737
7034
  path: entity.path,
6738
7035
  entityId: entity.id,
6739
7036
  entity,
6740
7037
  context: contextForCallback
6741
7038
  });
7039
+ if (result === false) {
7040
+ preventDefault = true;
7041
+ }
6742
7042
  }
6743
7043
  if (propertyCallbacks?.beforeDelete) {
6744
- await propertyCallbacks.beforeDelete({
7044
+ const result = await propertyCallbacks.beforeDelete({
6745
7045
  collection: resolvedCollection,
6746
7046
  path: entity.path,
6747
7047
  entityId: entity.id,
6748
7048
  entity,
6749
7049
  context: contextForCallback
6750
7050
  });
7051
+ if (result === false) {
7052
+ preventDefault = true;
7053
+ }
7054
+ }
7055
+ if (preventDefault) {
7056
+ return;
6751
7057
  }
6752
7058
  }
6753
7059
  await this.entityService.deleteEntity(entity.path, entity.id, entity.databaseId || resolvedCollection?.databaseId);
@@ -6820,7 +7126,17 @@ class PostgresBackendDriver {
6820
7126
  }
6821
7127
  const targetDb = this.getTargetDb(options?.database);
6822
7128
  try {
6823
- if (options?.role) {
7129
+ let needsRoleSwitch = false;
7130
+ if (options?.role && process.env.DISABLE_DB_ROLE_SWITCHING !== "true") {
7131
+ try {
7132
+ const currentRoleResult = await targetDb.execute(sql.raw("SELECT current_user AS role"));
7133
+ const currentRole = currentRoleResult.rows?.[0]?.role;
7134
+ needsRoleSwitch = !!currentRole && currentRole !== options.role;
7135
+ } catch {
7136
+ needsRoleSwitch = true;
7137
+ }
7138
+ }
7139
+ if (needsRoleSwitch && options?.role) {
6824
7140
  const safeRole = options.role.replace(/"/g, '""');
6825
7141
  return await targetDb.transaction(async (tx) => {
6826
7142
  await tx.execute(sql.raw(`SET LOCAL ROLE "${safeRole}"`));
@@ -6858,7 +7174,7 @@ class PostgresBackendDriver {
6858
7174
  return databases;
6859
7175
  }
6860
7176
  async fetchAvailableRoles() {
6861
- const result = await this.executeSql("SELECT rolname FROM pg_roles;");
7177
+ const result = await this.executeSql("SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;");
6862
7178
  return result.map((r) => r.rolname);
6863
7179
  }
6864
7180
  async fetchCurrentDatabase() {
@@ -7027,6 +7343,21 @@ class AuthenticatedPostgresBackendDriver {
7027
7343
  * Typed admin capabilities — delegates to the base driver.
7028
7344
  */
7029
7345
  admin;
7346
+ get restFetchService() {
7347
+ if (!this.delegate.restFetchService) return void 0;
7348
+ return {
7349
+ fetchCollectionForRest: async (collectionPath, options, include) => {
7350
+ return this.withTransaction(async (delegate) => {
7351
+ return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);
7352
+ });
7353
+ },
7354
+ fetchEntityForRest: async (collectionPath, entityId, include, databaseId) => {
7355
+ return this.withTransaction(async (delegate) => {
7356
+ return delegate.restFetchService.fetchEntityForRest(collectionPath, entityId, include, databaseId);
7357
+ });
7358
+ }
7359
+ };
7360
+ }
7030
7361
  async withTransaction(operation) {
7031
7362
  const pendingNotifications = [];
7032
7363
  const result = await this.delegate.db.transaction(async (tx) => {
@@ -7181,113 +7512,140 @@ class DatabasePoolManager {
7181
7512
  this.pools.clear();
7182
7513
  }
7183
7514
  }
7184
- const rebaseSchema = pgSchema("rebase");
7185
- const users = rebaseSchema.table("users", {
7186
- id: uuid("id").defaultRandom().primaryKey(),
7187
- email: varchar("email", {
7188
- length: 255
7189
- }).notNull().unique(),
7190
- passwordHash: varchar("password_hash", {
7191
- length: 255
7192
- }),
7193
- // NULL for OAuth-only users
7194
- displayName: varchar("display_name", {
7195
- length: 255
7196
- }),
7197
- photoUrl: varchar("photo_url", {
7198
- length: 500
7199
- }),
7200
- emailVerified: boolean("email_verified").default(false).notNull(),
7201
- emailVerificationToken: varchar("email_verification_token", {
7202
- length: 255
7203
- }),
7204
- emailVerificationSentAt: timestamp("email_verification_sent_at"),
7205
- createdAt: timestamp("created_at").defaultNow().notNull(),
7206
- updatedAt: timestamp("updated_at").defaultNow().notNull()
7207
- });
7208
- const roles = rebaseSchema.table("roles", {
7209
- id: varchar("id", {
7210
- length: 50
7211
- }).primaryKey(),
7212
- // 'admin', 'editor', 'viewer'
7213
- name: varchar("name", {
7214
- length: 100
7215
- }).notNull(),
7216
- isAdmin: boolean("is_admin").default(false).notNull(),
7217
- defaultPermissions: jsonb("default_permissions").$type(),
7218
- collectionPermissions: jsonb("collection_permissions").$type(),
7219
- config: jsonb("config").$type()
7220
- });
7221
- const userRoles = rebaseSchema.table("user_roles", {
7222
- userId: uuid("user_id").notNull().references(() => users.id, {
7223
- onDelete: "cascade"
7224
- }),
7225
- roleId: varchar("role_id", {
7226
- length: 50
7227
- }).notNull().references(() => roles.id, {
7228
- onDelete: "cascade"
7229
- })
7230
- }, (table) => ({
7231
- pk: primaryKey({
7232
- columns: [table.userId, table.roleId]
7233
- })
7234
- }));
7235
- const refreshTokens = rebaseSchema.table("refresh_tokens", {
7236
- id: uuid("id").defaultRandom().primaryKey(),
7237
- userId: uuid("user_id").notNull().references(() => users.id, {
7238
- onDelete: "cascade"
7239
- }),
7240
- tokenHash: varchar("token_hash", {
7241
- length: 255
7242
- }).notNull().unique(),
7243
- expiresAt: timestamp("expires_at").notNull(),
7244
- userAgent: varchar("user_agent", {
7245
- length: 500
7246
- }),
7247
- ipAddress: varchar("ip_address", {
7248
- length: 45
7249
- }),
7250
- createdAt: timestamp("created_at").defaultNow().notNull()
7251
- }, (table) => ({
7252
- uniqueDeviceSession: unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7253
- }));
7254
- const passwordResetTokens = rebaseSchema.table("password_reset_tokens", {
7255
- id: uuid("id").defaultRandom().primaryKey(),
7256
- userId: uuid("user_id").notNull().references(() => users.id, {
7257
- onDelete: "cascade"
7258
- }),
7259
- tokenHash: varchar("token_hash", {
7260
- length: 255
7261
- }).notNull().unique(),
7262
- expiresAt: timestamp("expires_at").notNull(),
7263
- usedAt: timestamp("used_at"),
7264
- createdAt: timestamp("created_at").defaultNow().notNull()
7265
- });
7266
- const appConfig = rebaseSchema.table("app_config", {
7267
- key: varchar("key", {
7268
- length: 100
7269
- }).primaryKey(),
7270
- value: jsonb("value").notNull(),
7271
- updatedAt: timestamp("updated_at").defaultNow().notNull()
7272
- });
7273
- const userIdentities = rebaseSchema.table("user_identities", {
7274
- id: uuid("id").defaultRandom().primaryKey(),
7275
- userId: uuid("user_id").notNull().references(() => users.id, {
7276
- onDelete: "cascade"
7277
- }),
7278
- provider: varchar("provider", {
7279
- length: 50
7280
- }).notNull(),
7281
- // e.g. 'google', 'linkedin'
7282
- providerId: varchar("provider_id", {
7283
- length: 255
7284
- }).notNull(),
7285
- profileData: jsonb("profile_data"),
7286
- createdAt: timestamp("created_at").defaultNow().notNull(),
7287
- updatedAt: timestamp("updated_at").defaultNow().notNull()
7288
- }, (table) => ({
7289
- uniqueProviderId: unique("unique_provider_id").on(table.provider, table.providerId)
7290
- }));
7515
+ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
7516
+ const rolesSchema = rolesSchemaName === "public" ? null : pgSchema(rolesSchemaName);
7517
+ const usersSchema2 = usersSchemaName === "public" ? null : pgSchema(usersSchemaName);
7518
+ const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgTable;
7519
+ const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgTable;
7520
+ const users2 = usersTableCreator("users", {
7521
+ id: uuid("id").defaultRandom().primaryKey(),
7522
+ email: varchar("email", {
7523
+ length: 255
7524
+ }).notNull().unique(),
7525
+ passwordHash: varchar("password_hash", {
7526
+ length: 255
7527
+ }),
7528
+ // NULL for OAuth-only users
7529
+ displayName: varchar("display_name", {
7530
+ length: 255
7531
+ }),
7532
+ photoUrl: varchar("photo_url", {
7533
+ length: 500
7534
+ }),
7535
+ emailVerified: boolean("email_verified").default(false).notNull(),
7536
+ emailVerificationToken: varchar("email_verification_token", {
7537
+ length: 255
7538
+ }),
7539
+ emailVerificationSentAt: timestamp("email_verification_sent_at"),
7540
+ metadata: jsonb("metadata").$type().default({}).notNull(),
7541
+ createdAt: timestamp("created_at").defaultNow().notNull(),
7542
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
7543
+ });
7544
+ const roles2 = rolesTableCreator("roles", {
7545
+ id: varchar("id", {
7546
+ length: 50
7547
+ }).primaryKey(),
7548
+ // 'admin', 'editor', 'viewer'
7549
+ name: varchar("name", {
7550
+ length: 100
7551
+ }).notNull(),
7552
+ isAdmin: boolean("is_admin").default(false).notNull(),
7553
+ defaultPermissions: jsonb("default_permissions").$type(),
7554
+ collectionPermissions: jsonb("collection_permissions").$type(),
7555
+ config: jsonb("config").$type()
7556
+ });
7557
+ const userRoles2 = rolesTableCreator("user_roles", {
7558
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7559
+ onDelete: "cascade"
7560
+ }),
7561
+ roleId: varchar("role_id", {
7562
+ length: 50
7563
+ }).notNull().references(() => roles2.id, {
7564
+ onDelete: "cascade"
7565
+ })
7566
+ }, (table) => ({
7567
+ pk: primaryKey({
7568
+ columns: [table.userId, table.roleId]
7569
+ })
7570
+ }));
7571
+ const refreshTokens2 = rolesTableCreator("refresh_tokens", {
7572
+ id: uuid("id").defaultRandom().primaryKey(),
7573
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7574
+ onDelete: "cascade"
7575
+ }),
7576
+ tokenHash: varchar("token_hash", {
7577
+ length: 255
7578
+ }).notNull().unique(),
7579
+ expiresAt: timestamp("expires_at").notNull(),
7580
+ userAgent: varchar("user_agent", {
7581
+ length: 500
7582
+ }),
7583
+ ipAddress: varchar("ip_address", {
7584
+ length: 45
7585
+ }),
7586
+ createdAt: timestamp("created_at").defaultNow().notNull()
7587
+ }, (table) => ({
7588
+ uniqueDeviceSession: unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7589
+ }));
7590
+ const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
7591
+ id: uuid("id").defaultRandom().primaryKey(),
7592
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7593
+ onDelete: "cascade"
7594
+ }),
7595
+ tokenHash: varchar("token_hash", {
7596
+ length: 255
7597
+ }).notNull().unique(),
7598
+ expiresAt: timestamp("expires_at").notNull(),
7599
+ usedAt: timestamp("used_at"),
7600
+ createdAt: timestamp("created_at").defaultNow().notNull()
7601
+ });
7602
+ const appConfig2 = rolesTableCreator("app_config", {
7603
+ key: varchar("key", {
7604
+ length: 100
7605
+ }).primaryKey(),
7606
+ value: jsonb("value").notNull(),
7607
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
7608
+ });
7609
+ const userIdentities2 = rolesTableCreator("user_identities", {
7610
+ id: uuid("id").defaultRandom().primaryKey(),
7611
+ userId: uuid("user_id").notNull().references(() => users2.id, {
7612
+ onDelete: "cascade"
7613
+ }),
7614
+ provider: varchar("provider", {
7615
+ length: 50
7616
+ }).notNull(),
7617
+ // e.g. 'google', 'linkedin'
7618
+ providerId: varchar("provider_id", {
7619
+ length: 255
7620
+ }).notNull(),
7621
+ profileData: jsonb("profile_data"),
7622
+ createdAt: timestamp("created_at").defaultNow().notNull(),
7623
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
7624
+ }, (table) => ({
7625
+ uniqueProviderId: unique("unique_provider_id").on(table.provider, table.providerId)
7626
+ }));
7627
+ return {
7628
+ rolesSchema,
7629
+ usersSchema: usersSchema2,
7630
+ users: users2,
7631
+ roles: roles2,
7632
+ userRoles: userRoles2,
7633
+ refreshTokens: refreshTokens2,
7634
+ passwordResetTokens: passwordResetTokens2,
7635
+ appConfig: appConfig2,
7636
+ userIdentities: userIdentities2
7637
+ };
7638
+ }
7639
+ const defaultAuthSchema = createAuthSchema("rebase", "rebase");
7640
+ const rebaseSchema = defaultAuthSchema.rolesSchema;
7641
+ const usersSchema = defaultAuthSchema.usersSchema;
7642
+ const users = defaultAuthSchema.users;
7643
+ const roles = defaultAuthSchema.roles;
7644
+ const userRoles = defaultAuthSchema.userRoles;
7645
+ const refreshTokens = defaultAuthSchema.refreshTokens;
7646
+ const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
7647
+ const appConfig = defaultAuthSchema.appConfig;
7648
+ const userIdentities = defaultAuthSchema.userIdentities;
7291
7649
  const usersRelations = relations(users, ({
7292
7650
  many
7293
7651
  }) => ({
@@ -7470,6 +7828,15 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
7470
7828
  }
7471
7829
  break;
7472
7830
  }
7831
+ case "vector": {
7832
+ const vp = prop;
7833
+ columnDefinition = `vector("${colName}", { dimensions: ${vp.dimensions} })`;
7834
+ break;
7835
+ }
7836
+ case "binary": {
7837
+ columnDefinition = `customType({ dataType() { return 'bytea'; } })("${colName}")`;
7838
+ break;
7839
+ }
7473
7840
  case "relation": {
7474
7841
  const refProp = prop;
7475
7842
  const resolvedRelations = resolveCollectionRelations(collection);
@@ -7536,7 +7903,7 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
7536
7903
  return ` ${propName}: ${columnDefinition}`;
7537
7904
  };
7538
7905
  const resolveRawSql = (expression) => {
7539
- const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => `\${table.${col}}`);
7906
+ const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
7540
7907
  return `sql\`${resolved}\``;
7541
7908
  };
7542
7909
  const wrapWithRoleCheck = (clause, roles2) => {
@@ -7548,7 +7915,7 @@ const unwrapSql = (sqlExpr) => {
7548
7915
  const match = sqlExpr.match(/^sql`(.*)`$/s);
7549
7916
  return match ? match[1] : sqlExpr;
7550
7917
  };
7551
- const buildUsingClause = (rule) => {
7918
+ const buildUsingClause = (rule, collection) => {
7552
7919
  if (rule.using) {
7553
7920
  return resolveRawSql(rule.using);
7554
7921
  }
@@ -7556,15 +7923,17 @@ const buildUsingClause = (rule) => {
7556
7923
  return "sql`true`";
7557
7924
  }
7558
7925
  if (rule.ownerField) {
7559
- return `sql\`\${table.${rule.ownerField}} = auth.uid()\``;
7926
+ const prop = collection.properties?.[rule.ownerField];
7927
+ const colName = resolveColumnName(rule.ownerField, prop);
7928
+ return `sql\`${colName} = auth.uid()\``;
7560
7929
  }
7561
7930
  return null;
7562
7931
  };
7563
- const buildWithCheckClause = (rule) => {
7932
+ const buildWithCheckClause = (rule, collection) => {
7564
7933
  if (rule.withCheck) {
7565
7934
  return resolveRawSql(rule.withCheck);
7566
7935
  }
7567
- return buildUsingClause(rule);
7936
+ return buildUsingClause(rule, collection);
7568
7937
  };
7569
7938
  const getPolicyNameHash = (rule) => {
7570
7939
  const data = JSON.stringify({
@@ -7580,21 +7949,22 @@ const getPolicyNameHash = (rule) => {
7580
7949
  });
7581
7950
  return createHash("sha1").update(data).digest("hex").substring(0, 7);
7582
7951
  };
7583
- const generatePolicyCode = (tableName, rule, index) => {
7952
+ const generatePolicyCode = (collection, rule, index) => {
7953
+ const tableName = getTableName(collection);
7584
7954
  const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
7585
7955
  const ruleHash = getPolicyNameHash(rule);
7586
7956
  return ops.map((op, opIdx) => {
7587
7957
  const policyName = rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
7588
- return generateSinglePolicyCode(tableName, rule, op, policyName);
7958
+ return generateSinglePolicyCode(collection, rule, op, policyName);
7589
7959
  }).join("");
7590
7960
  };
7591
- const generateSinglePolicyCode = (tableName, rule, operation, policyName) => {
7961
+ const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
7592
7962
  const mode = rule.mode ?? "permissive";
7593
7963
  const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
7594
7964
  const needsUsing = operation !== "insert";
7595
7965
  const needsWithCheck = operation !== "select" && operation !== "delete";
7596
- let usingClause = needsUsing ? buildUsingClause(rule) : null;
7597
- let withCheckClause = needsWithCheck ? buildWithCheckClause(rule) : null;
7966
+ let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
7967
+ let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
7598
7968
  if (roles2 && roles2.length > 0) {
7599
7969
  if (usingClause) {
7600
7970
  usingClause = wrapWithRoleCheck(usingClause, roles2);
@@ -7663,12 +8033,26 @@ const computeSharedRelationName = (rel, sourceCollection, _collections) => {
7663
8033
  const generateSchema = async (collections, stripPolicies = false) => {
7664
8034
  let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
7665
8035
  const hasUuid = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "string" && (p.autoValue === "uuid" || p.isId === "uuid")));
7666
- collections.some((c) => c.properties && Object.values(c.properties).some((p) => (p.type === "map" || p.type === "array") && p.columnType === "json"));
8036
+ const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
8037
+ const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
7667
8038
  const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
7668
8039
  if (hasUuid) pgCoreImports.push("uuid");
8040
+ if (hasVector) pgCoreImports.push("vector");
8041
+ if (hasBinary) pgCoreImports.push("customType");
8042
+ const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollection(c) ? c.schema : void 0).filter(Boolean)));
8043
+ if (uniqueSchemas.length > 0) {
8044
+ pgCoreImports.push("pgSchema");
8045
+ }
7669
8046
  schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';
7670
8047
  `;
7671
8048
  schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
8049
+ uniqueSchemas.forEach((schema) => {
8050
+ schemaContent += `export const ${schema}Schema = pgSchema("${schema}");
8051
+ `;
8052
+ });
8053
+ if (uniqueSchemas.length > 0) {
8054
+ schemaContent += "\n";
8055
+ }
7672
8056
  const exportedTableVars = [];
7673
8057
  const exportedEnumVars = [];
7674
8058
  const exportedRelationVars = [];
@@ -7723,6 +8107,9 @@ const generateSchema = async (collections, stripPolicies = false) => {
7723
8107
  const tableVarName = getTableVarName(tableName);
7724
8108
  if (isJunction && relation && sourceCollection && relation.through) {
7725
8109
  const targetCollection = relation.target();
8110
+ const schema = (isPostgresCollection(targetCollection) ? targetCollection.schema : void 0) || (isPostgresCollection(sourceCollection) ? sourceCollection.schema : void 0);
8111
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
8112
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
7726
8113
  const {
7727
8114
  sourceColumn,
7728
8115
  targetColumn
@@ -7733,7 +8120,7 @@ const generateSchema = async (collections, stripPolicies = false) => {
7733
8120
  const targetColType = isNumericId(targetCollection) ? "integer" : getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar";
7734
8121
  const sourceId = getPrimaryKeyName(sourceCollection);
7735
8122
  const targetId = getPrimaryKeyName(targetCollection);
7736
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
8123
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7737
8124
  `;
7738
8125
  schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
7739
8126
  `;
@@ -7744,7 +8131,10 @@ const generateSchema = async (collections, stripPolicies = false) => {
7744
8131
  `;
7745
8132
  schemaContent += "}));\n\n";
7746
8133
  } else if (!isJunction) {
7747
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
8134
+ const schema = isPostgresCollection(collection) ? collection.schema : void 0;
8135
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
8136
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
8137
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7748
8138
  `;
7749
8139
  const columns = /* @__PURE__ */ new Set();
7750
8140
  Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
@@ -7760,7 +8150,7 @@ const generateSchema = async (collections, stripPolicies = false) => {
7760
8150
  if (!stripPolicies && securityRules && securityRules.length > 0) {
7761
8151
  schemaContent += "\n}, (table) => ([\n";
7762
8152
  securityRules.forEach((rule, idx) => {
7763
- schemaContent += generatePolicyCode(tableName, rule);
8153
+ schemaContent += generatePolicyCode(collection, rule);
7764
8154
  });
7765
8155
  schemaContent += "])).enableRLS();\n\n";
7766
8156
  } else {
@@ -7805,11 +8195,11 @@ const generateSchema = async (collections, stripPolicies = false) => {
7805
8195
  references: [${sourceTableVar}.${sourceId}],
7806
8196
  relationName: "${owningRelationName}"
7807
8197
  })`);
7808
- const targetRelName = inverseRelationName ?? owningRelationName;
8198
+ const targetRelationName = inverseRelationName ? inverseRelationName : `${tableName}_${relation.through.targetColumn}`;
7809
8199
  tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {
7810
8200
  fields: [${tableVarName}.${relation.through.targetColumn}],
7811
8201
  references: [${targetTableVar}.${targetId}],
7812
- relationName: "${targetRelName}"
8202
+ relationName: "${targetRelationName}"
7813
8203
  })`);
7814
8204
  }
7815
8205
  } else {
@@ -7908,6 +8298,90 @@ ${tableRelations.join(",\n")}
7908
8298
  schemaContent += tablesExport + enumsExport + relationsExport;
7909
8299
  return schemaContent;
7910
8300
  };
8301
+ const defaultUsersCollection = {
8302
+ name: "Users",
8303
+ singularName: "User",
8304
+ slug: "users",
8305
+ table: "users",
8306
+ schema: "rebase",
8307
+ icon: "Users",
8308
+ group: "Settings",
8309
+ properties: {
8310
+ id: {
8311
+ name: "ID",
8312
+ type: "string",
8313
+ isId: "uuid"
8314
+ },
8315
+ email: {
8316
+ name: "Email",
8317
+ type: "string",
8318
+ validation: {
8319
+ required: true,
8320
+ unique: true
8321
+ }
8322
+ },
8323
+ password_hash: {
8324
+ name: "Password Hash",
8325
+ type: "string",
8326
+ ui: {
8327
+ hideFromCollection: true
8328
+ }
8329
+ },
8330
+ display_name: {
8331
+ name: "Display Name",
8332
+ type: "string"
8333
+ },
8334
+ photo_url: {
8335
+ name: "Photo URL",
8336
+ type: "string"
8337
+ },
8338
+ email_verified: {
8339
+ name: "Email Verified",
8340
+ type: "boolean",
8341
+ defaultValue: false
8342
+ },
8343
+ email_verification_token: {
8344
+ name: "Email Verification Token",
8345
+ type: "string",
8346
+ ui: {
8347
+ hideFromCollection: true
8348
+ }
8349
+ },
8350
+ email_verification_sent_at: {
8351
+ name: "Email Verification Sent At",
8352
+ type: "date",
8353
+ ui: {
8354
+ hideFromCollection: true
8355
+ }
8356
+ },
8357
+ metadata: {
8358
+ name: "Metadata",
8359
+ type: "map",
8360
+ defaultValue: {},
8361
+ ui: {
8362
+ hideFromCollection: true
8363
+ }
8364
+ },
8365
+ created_at: {
8366
+ name: "Created At",
8367
+ type: "date",
8368
+ autoValue: "on_create",
8369
+ ui: {
8370
+ readOnly: true,
8371
+ hideFromCollection: true
8372
+ }
8373
+ },
8374
+ updated_at: {
8375
+ name: "Updated At",
8376
+ type: "date",
8377
+ autoValue: "on_update",
8378
+ ui: {
8379
+ readOnly: true,
8380
+ hideFromCollection: true
8381
+ }
8382
+ }
8383
+ }
8384
+ };
7911
8385
  const formatTerminalText = (text, options = {}) => {
7912
8386
  let codes = "";
7913
8387
  if (options.bold) codes += "\x1B[1m";
@@ -7970,10 +8444,14 @@ const runGeneration = async (collectionsFilePath, outputPath) => {
7970
8444
  const imported = await dynamicImport(fileUrl);
7971
8445
  collections = imported.backendCollections || imported.collections;
7972
8446
  }
7973
- if (!collections || !Array.isArray(collections) || collections.length === 0) {
7974
- console.error("Error: Could not find collections array or failed to load directory.");
7975
- return;
8447
+ if (!collections || !Array.isArray(collections)) {
8448
+ collections = [];
7976
8449
  }
8450
+ const hasUsersCollection = collections.some((c) => c.slug === "users");
8451
+ if (!hasUsersCollection) {
8452
+ collections.push(defaultUsersCollection);
8453
+ }
8454
+ collections.sort((a, b) => a.slug.localeCompare(b.slug));
7977
8455
  const schemaContent = await generateSchema(collections);
7978
8456
  if (outputPath) {
7979
8457
  const outputDir = path.dirname(outputPath);
@@ -8201,29 +8679,14 @@ class RealtimeService extends EventEmitter {
8201
8679
  },
8202
8680
  authContext
8203
8681
  });
8204
- let entities;
8205
- if (this.driver) {
8206
- entities = await this.driver.fetchCollection({
8207
- path: request.path,
8208
- collection,
8209
- filter: request.filter,
8210
- orderBy: request.orderBy,
8211
- order: request.order,
8212
- limit: request.limit,
8213
- startAfter: request.startAfter,
8214
- searchString: request.searchString
8215
- });
8216
- } else {
8217
- entities = await this.entityService.fetchCollection(request.path, {
8218
- filter: request.filter,
8219
- orderBy: request.orderBy,
8220
- order: request.order,
8221
- limit: request.limit,
8222
- startAfter: request.startAfter,
8223
- databaseId: request.collection?.databaseId,
8224
- searchString: request.searchString
8225
- });
8226
- }
8682
+ const entities = await this.fetchCollectionWithAuth(request.path, {
8683
+ filter: request.filter,
8684
+ orderBy: request.orderBy,
8685
+ order: request.order,
8686
+ limit: request.limit,
8687
+ startAfter: request.startAfter,
8688
+ searchString: request.searchString
8689
+ }, authContext);
8227
8690
  this.sendCollectionUpdate(clientId, subscriptionId, entities);
8228
8691
  } catch (error) {
8229
8692
  this.sendError(clientId, `Failed to subscribe to collection: ${error}`, subscriptionId);
@@ -8247,16 +8710,7 @@ class RealtimeService extends EventEmitter {
8247
8710
  entityId: request.entityId,
8248
8711
  authContext
8249
8712
  });
8250
- let entity;
8251
- if (this.driver) {
8252
- entity = await this.driver.fetchEntity({
8253
- path: request.path,
8254
- entityId: request.entityId,
8255
- collection
8256
- });
8257
- } else {
8258
- entity = await this.entityService.fetchEntity(request.path, request.entityId, request.collection?.databaseId);
8259
- }
8713
+ const entity = await this.fetchEntityWithAuth(request.path, String(request.entityId), authContext);
8260
8714
  this.sendEntityUpdate(clientId, subscriptionId, entity || null);
8261
8715
  } catch (error) {
8262
8716
  this.sendError(clientId, `Failed to subscribe to entity: ${request.path} ${request.entityId} ${error}`, subscriptionId);
@@ -8401,87 +8855,77 @@ class RealtimeService extends EventEmitter {
8401
8855
  async fetchCollectionWithAuth(notifyPath, collectionRequest, authContext) {
8402
8856
  if (this.driver) {
8403
8857
  const collection = this.registry.getCollectionByPath(notifyPath);
8404
- const fetchFn = async () => this.driver.fetchCollection({
8405
- path: notifyPath,
8406
- collection,
8407
- filter: collectionRequest.filter,
8408
- orderBy: collectionRequest.orderBy,
8409
- order: collectionRequest.order,
8410
- limit: collectionRequest.limit,
8411
- offset: collectionRequest.offset,
8412
- startAfter: collectionRequest.startAfter,
8413
- searchString: collectionRequest.searchString
8858
+ const activeAuth = authContext || {
8859
+ userId: "anon",
8860
+ roles: ["anon"]
8861
+ };
8862
+ return await this.db.transaction(async (tx) => {
8863
+ await tx.execute(sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
8864
+ await tx.execute(sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
8865
+ await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
8866
+ sub: activeAuth.userId,
8867
+ roles: activeAuth.roles
8868
+ })}, true)`);
8869
+ const txEntityService = new EntityService(tx, this.registry);
8870
+ let fetchedEntities;
8871
+ if (collectionRequest.searchString) {
8872
+ fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8873
+ filter: collectionRequest.filter,
8874
+ orderBy: collectionRequest.orderBy,
8875
+ order: collectionRequest.order,
8876
+ limit: collectionRequest.limit,
8877
+ databaseId: collectionRequest.databaseId
8878
+ });
8879
+ } else {
8880
+ fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8881
+ filter: collectionRequest.filter,
8882
+ orderBy: collectionRequest.orderBy,
8883
+ order: collectionRequest.order,
8884
+ limit: collectionRequest.limit,
8885
+ offset: collectionRequest.offset,
8886
+ startAfter: collectionRequest.startAfter,
8887
+ databaseId: collectionRequest.databaseId
8888
+ });
8889
+ }
8890
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
8891
+ const resolvedCollection = collection ? {
8892
+ ...collection,
8893
+ ...registryCollection
8894
+ } : registryCollection;
8895
+ const callbacks = resolvedCollection?.callbacks;
8896
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8897
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8898
+ const contextForCallback = {
8899
+ user: {
8900
+ uid: activeAuth.userId,
8901
+ roles: activeAuth.roles
8902
+ },
8903
+ driver: this.driver,
8904
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
8905
+ };
8906
+ return await Promise.all(fetchedEntities.map(async (entity) => {
8907
+ let processedEntity = entity;
8908
+ if (callbacks?.afterRead) {
8909
+ processedEntity = await callbacks.afterRead({
8910
+ collection: resolvedCollection,
8911
+ path: notifyPath,
8912
+ entity: processedEntity,
8913
+ context: contextForCallback
8914
+ }) ?? processedEntity;
8915
+ }
8916
+ if (propertyCallbacks?.afterRead) {
8917
+ processedEntity = await propertyCallbacks.afterRead({
8918
+ collection: resolvedCollection,
8919
+ path: notifyPath,
8920
+ entity: processedEntity,
8921
+ context: contextForCallback
8922
+ }) ?? processedEntity;
8923
+ }
8924
+ return processedEntity;
8925
+ }));
8926
+ }
8927
+ return fetchedEntities;
8414
8928
  });
8415
- if (authContext) {
8416
- return await this.db.transaction(async (tx) => {
8417
- await tx.execute(sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8418
- await tx.execute(sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8419
- await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
8420
- sub: authContext.userId,
8421
- roles: authContext.roles
8422
- })}, true)`);
8423
- const txEntityService = new EntityService(tx, this.registry);
8424
- let fetchedEntities;
8425
- if (collectionRequest.searchString) {
8426
- fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8427
- filter: collectionRequest.filter,
8428
- orderBy: collectionRequest.orderBy,
8429
- order: collectionRequest.order,
8430
- limit: collectionRequest.limit,
8431
- databaseId: collectionRequest.databaseId
8432
- });
8433
- } else {
8434
- fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8435
- filter: collectionRequest.filter,
8436
- orderBy: collectionRequest.orderBy,
8437
- order: collectionRequest.order,
8438
- limit: collectionRequest.limit,
8439
- offset: collectionRequest.offset,
8440
- startAfter: collectionRequest.startAfter,
8441
- databaseId: collectionRequest.databaseId
8442
- });
8443
- }
8444
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8445
- const resolvedCollection = collection ? {
8446
- ...collection,
8447
- ...registryCollection
8448
- } : registryCollection;
8449
- const callbacks = resolvedCollection?.callbacks;
8450
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8451
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8452
- const contextForCallback = {
8453
- user: {
8454
- uid: authContext.userId,
8455
- roles: authContext.roles
8456
- },
8457
- driver: this.driver,
8458
- data: this.driver ? this.driver.data : void 0
8459
- };
8460
- return await Promise.all(fetchedEntities.map(async (entity) => {
8461
- let processedEntity = entity;
8462
- if (callbacks?.afterRead) {
8463
- processedEntity = await callbacks.afterRead({
8464
- collection: resolvedCollection,
8465
- path: notifyPath,
8466
- entity: processedEntity,
8467
- context: contextForCallback
8468
- }) ?? processedEntity;
8469
- }
8470
- if (propertyCallbacks?.afterRead) {
8471
- processedEntity = await propertyCallbacks.afterRead({
8472
- collection: resolvedCollection,
8473
- path: notifyPath,
8474
- entity: processedEntity,
8475
- context: contextForCallback
8476
- }) ?? processedEntity;
8477
- }
8478
- return processedEntity;
8479
- }));
8480
- }
8481
- return fetchedEntities;
8482
- });
8483
- }
8484
- return fetchFn();
8485
8929
  }
8486
8930
  if (collectionRequest.searchString) {
8487
8931
  return await this.entityService.searchEntities(notifyPath, collectionRequest.searchString, {
@@ -8545,60 +8989,56 @@ class RealtimeService extends EventEmitter {
8545
8989
  async fetchEntityWithAuth(notifyPath, entityId, authContext) {
8546
8990
  if (this.driver) {
8547
8991
  const collection = this.registry.getCollectionByPath(notifyPath);
8548
- const fetchFn = async () => this.driver.fetchEntity({
8549
- path: notifyPath,
8550
- entityId,
8551
- collection
8552
- });
8553
- if (authContext) {
8554
- return await this.db.transaction(async (tx) => {
8555
- await tx.execute(sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8556
- await tx.execute(sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8557
- await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
8558
- sub: authContext.userId,
8559
- roles: authContext.roles
8560
- })}, true)`);
8561
- const txEntityService = new EntityService(tx, this.registry);
8562
- let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
8563
- if (processedEntity) {
8564
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8565
- const resolvedCollection = collection ? {
8566
- ...collection,
8567
- ...registryCollection
8568
- } : registryCollection;
8569
- const callbacks = resolvedCollection?.callbacks;
8570
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8571
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8572
- const contextForCallback = {
8573
- user: {
8574
- uid: authContext.userId,
8575
- roles: authContext.roles
8576
- },
8577
- driver: this.driver,
8578
- data: this.driver ? this.driver.data : void 0
8579
- };
8580
- if (callbacks?.afterRead) {
8581
- processedEntity = await callbacks.afterRead({
8582
- collection: resolvedCollection,
8583
- path: notifyPath,
8584
- entity: processedEntity,
8585
- context: contextForCallback
8586
- }) ?? processedEntity;
8587
- }
8588
- if (propertyCallbacks?.afterRead) {
8589
- processedEntity = await propertyCallbacks.afterRead({
8590
- collection: resolvedCollection,
8591
- path: notifyPath,
8592
- entity: processedEntity,
8593
- context: contextForCallback
8594
- }) ?? processedEntity;
8595
- }
8992
+ const activeAuth = authContext || {
8993
+ userId: "anon",
8994
+ roles: ["anon"]
8995
+ };
8996
+ return await this.db.transaction(async (tx) => {
8997
+ await tx.execute(sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
8998
+ await tx.execute(sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
8999
+ await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
9000
+ sub: activeAuth.userId,
9001
+ roles: activeAuth.roles
9002
+ })}, true)`);
9003
+ const txEntityService = new EntityService(tx, this.registry);
9004
+ let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
9005
+ if (processedEntity) {
9006
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
9007
+ const resolvedCollection = collection ? {
9008
+ ...collection,
9009
+ ...registryCollection
9010
+ } : registryCollection;
9011
+ const callbacks = resolvedCollection?.callbacks;
9012
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
9013
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
9014
+ const contextForCallback = {
9015
+ user: {
9016
+ uid: activeAuth.userId,
9017
+ roles: activeAuth.roles
9018
+ },
9019
+ driver: this.driver,
9020
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
9021
+ };
9022
+ if (callbacks?.afterRead) {
9023
+ processedEntity = await callbacks.afterRead({
9024
+ collection: resolvedCollection,
9025
+ path: notifyPath,
9026
+ entity: processedEntity,
9027
+ context: contextForCallback
9028
+ }) ?? processedEntity;
9029
+ }
9030
+ if (propertyCallbacks?.afterRead) {
9031
+ processedEntity = await propertyCallbacks.afterRead({
9032
+ collection: resolvedCollection,
9033
+ path: notifyPath,
9034
+ entity: processedEntity,
9035
+ context: contextForCallback
9036
+ }) ?? processedEntity;
8596
9037
  }
8597
9038
  }
8598
- return processedEntity;
8599
- });
8600
- }
8601
- return fetchFn();
9039
+ }
9040
+ return processedEntity;
9041
+ });
8602
9042
  }
8603
9043
  return await this.entityService.fetchEntity(notifyPath, entityId);
8604
9044
  }
@@ -8666,6 +9106,31 @@ class RealtimeService extends EventEmitter {
8666
9106
  return parentPaths;
8667
9107
  }
8668
9108
  // =============================================================================
9109
+ // Lifecycle / Cleanup
9110
+ // =============================================================================
9111
+ /**
9112
+ * Gracefully tear down all realtime resources.
9113
+ *
9114
+ * This MUST be called during process shutdown, **before** `pool.end()`.
9115
+ * It ensures:
9116
+ * 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
9117
+ * 2. All subscription state and callbacks are cleared.
9118
+ * 3. The dedicated LISTEN client (outside the pool) is disconnected.
9119
+ * 4. All WebSocket clients are removed (but not forcefully closed — the
9120
+ * HTTP server close will handle that).
9121
+ */
9122
+ async destroy() {
9123
+ for (const [key, timer] of this.refetchTimers) {
9124
+ clearTimeout(timer);
9125
+ this.refetchTimers.delete(key);
9126
+ }
9127
+ this._subscriptions.clear();
9128
+ this.subscriptionCallbacks.clear();
9129
+ await this.stopListening();
9130
+ this.clients.clear();
9131
+ this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
9132
+ }
9133
+ // =============================================================================
8669
9134
  // Cross-Instance LISTEN/NOTIFY
8670
9135
  // =============================================================================
8671
9136
  /**
@@ -8806,8 +9271,23 @@ const clientSessions = /* @__PURE__ */ new Map();
8806
9271
  const WS_RATE_LIMIT = 2e3;
8807
9272
  const WS_RATE_WINDOW_MS = 6e4;
8808
9273
  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"]);
9274
+ function extractErrorMessage(error) {
9275
+ if (!error) return "Unknown error";
9276
+ if (typeof error === "object") {
9277
+ const err = error;
9278
+ if (err.cause) {
9279
+ return extractErrorMessage(err.cause);
9280
+ }
9281
+ if (typeof err.message === "string") {
9282
+ return err.message;
9283
+ }
9284
+ }
9285
+ return String(error);
9286
+ }
8809
9287
  function isAdminSession(session) {
8810
- if (!session?.user?.roles) return false;
9288
+ if (!session?.user) return false;
9289
+ if (session.user.isAdmin) return true;
9290
+ if (!session.user.roles) return false;
8811
9291
  return session.user.roles.some((r) => {
8812
9292
  if (typeof r === "string") return r === "admin";
8813
9293
  if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
@@ -8815,7 +9295,7 @@ function isAdminSession(session) {
8815
9295
  return false;
8816
9296
  });
8817
9297
  }
8818
- function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9298
+ function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
8819
9299
  const isProduction = process.env.NODE_ENV === "production";
8820
9300
  const wsDebug = (...args) => {
8821
9301
  if (!isProduction) console.debug(...args);
@@ -8829,7 +9309,7 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8829
9309
  }
8830
9310
  console.error("❌ [WebSocket Server] Error:", err);
8831
9311
  });
8832
- const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
9312
+ const requireAuth = authAdapter ? true : authConfig?.requireAuth !== false && !!authConfig?.jwtSecret;
8833
9313
  wss.on("connection", (ws) => {
8834
9314
  const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
8835
9315
  wsDebug(`WebSocket client connected: ${clientId}`);
@@ -8874,11 +9354,37 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8874
9354
  sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
8875
9355
  return;
8876
9356
  }
8877
- const user = extractUserFromToken(token);
8878
- if (user) {
9357
+ let verifiedUser = null;
9358
+ if (authAdapter) {
9359
+ try {
9360
+ const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", {
9361
+ headers: {
9362
+ Authorization: `Bearer ${token}`
9363
+ }
9364
+ }));
9365
+ if (adapterUser) {
9366
+ verifiedUser = {
9367
+ userId: adapterUser.uid,
9368
+ roles: adapterUser.roles,
9369
+ isAdmin: adapterUser.isAdmin
9370
+ };
9371
+ }
9372
+ } catch {
9373
+ }
9374
+ } else {
9375
+ const jwtPayload = extractUserFromToken(token);
9376
+ if (jwtPayload) {
9377
+ verifiedUser = {
9378
+ userId: jwtPayload.userId,
9379
+ roles: jwtPayload.roles ?? [],
9380
+ isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
9381
+ };
9382
+ }
9383
+ }
9384
+ if (verifiedUser) {
8879
9385
  const session = clientSessions.get(clientId);
8880
9386
  if (session) {
8881
- session.user = user;
9387
+ session.user = verifiedUser;
8882
9388
  session.authenticated = true;
8883
9389
  }
8884
9390
  wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
@@ -8886,11 +9392,11 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8886
9392
  type: "AUTH_SUCCESS",
8887
9393
  requestId,
8888
9394
  payload: {
8889
- userId: user.userId,
8890
- roles: user.roles
9395
+ userId: verifiedUser.userId,
9396
+ roles: verifiedUser.roles
8891
9397
  }
8892
9398
  }));
8893
- wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${user.userId}`);
9399
+ wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.userId}`);
8894
9400
  } else {
8895
9401
  wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
8896
9402
  sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
@@ -8928,16 +9434,19 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
8928
9434
  }
8929
9435
  const getScopedDelegate = async () => {
8930
9436
  const session = clientSessions.get(clientId);
8931
- if (session?.user && "withAuth" in driver && typeof driver.withAuth === "function") {
9437
+ if ("withAuth" in driver && typeof driver.withAuth === "function") {
8932
9438
  try {
8933
- const userForAuth = {
9439
+ const userForAuth = session?.user ? {
8934
9440
  uid: session.user.userId,
8935
9441
  roles: session.user.roles ?? []
9442
+ } : {
9443
+ uid: "anon",
9444
+ roles: ["anon"]
8936
9445
  };
8937
9446
  return await driver.withAuth(userForAuth);
8938
9447
  } catch (e) {
8939
- console.error("Failed to create authenticated delegate for WS request", e);
8940
- return driver;
9448
+ console.error("Failed to create RLS scoped delegate for WS request", e);
9449
+ throw new Error("Internal authentication error");
8941
9450
  }
8942
9451
  }
8943
9452
  return driver;
@@ -9068,24 +9577,29 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9068
9577
  sql: sql2,
9069
9578
  options
9070
9579
  } = payload;
9071
- const delegate = await getScopedDelegate();
9072
- const admin = delegate.admin;
9073
- if (!isSQLAdmin(admin)) {
9074
- sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9075
- break;
9076
- }
9077
- const result = await admin.executeSql(sql2, options);
9078
- if (process.env.NODE_ENV !== "production") {
9079
- wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9580
+ try {
9581
+ const delegate = await getScopedDelegate();
9582
+ const admin = delegate.admin;
9583
+ if (!isSQLAdmin(admin)) {
9584
+ sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9585
+ break;
9586
+ }
9587
+ const result = await admin.executeSql(sql2, options);
9588
+ if (process.env.NODE_ENV !== "production") {
9589
+ wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9590
+ }
9591
+ const response = {
9592
+ type: "EXECUTE_SQL_SUCCESS",
9593
+ payload: {
9594
+ result
9595
+ },
9596
+ requestId
9597
+ };
9598
+ ws.send(JSON.stringify(response));
9599
+ } catch (sqlError) {
9600
+ const errMsg = extractErrorMessage(sqlError);
9601
+ sendError("ERROR", "SQL_ERROR", errMsg);
9080
9602
  }
9081
- const response = {
9082
- type: "EXECUTE_SQL_SUCCESS",
9083
- payload: {
9084
- result
9085
- },
9086
- requestId
9087
- };
9088
- ws.send(JSON.stringify(response));
9089
9603
  }
9090
9604
  break;
9091
9605
  case "FETCH_DATABASES":
@@ -9264,7 +9778,10 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9264
9778
  const authContext = session?.user ? {
9265
9779
  userId: session.user.userId,
9266
9780
  roles: session.user.roles ?? []
9267
- } : void 0;
9781
+ } : {
9782
+ userId: "anon",
9783
+ roles: ["anon"]
9784
+ };
9268
9785
  await realtimeService.handleClientMessage(clientId, {
9269
9786
  type,
9270
9787
  payload,
@@ -9408,29 +9925,58 @@ const DEFAULT_ROLES = [{
9408
9925
  },
9409
9926
  config: null
9410
9927
  }];
9411
- async function ensureAuthTablesExist(db) {
9928
+ async function ensureAuthTablesExist(db, registry) {
9412
9929
  console.log("🔍 Checking auth tables...");
9413
9930
  try {
9931
+ let usersTableName = '"users"';
9932
+ let userIdType = "TEXT";
9933
+ let usersSchema2 = "public";
9934
+ if (registry) {
9935
+ const usersTable = registry.getTable("users");
9936
+ if (usersTable) {
9937
+ const {
9938
+ getTableName: getTableName2
9939
+ } = await import("drizzle-orm");
9940
+ usersSchema2 = getTableConfig(usersTable).schema || "public";
9941
+ usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
9942
+ if (usersTable.id) {
9943
+ const col = usersTable.id;
9944
+ const meta = getColumnMeta(col);
9945
+ const columnType = meta.columnType;
9946
+ if (columnType === "PgUUID") {
9947
+ userIdType = "UUID";
9948
+ } else if (columnType === "PgSerial" || columnType === "PgInteger") {
9949
+ userIdType = "INTEGER";
9950
+ } else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
9951
+ userIdType = "BIGINT";
9952
+ }
9953
+ }
9954
+ }
9955
+ }
9956
+ let rolesSchema = "rebase";
9957
+ if (registry) {
9958
+ const rolesTable = registry.getTable("roles");
9959
+ if (rolesTable) {
9960
+ rolesSchema = getTableConfig(rolesTable).schema || "public";
9961
+ }
9962
+ }
9963
+ if (usersSchema2 !== "public") {
9964
+ await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(usersSchema2)}`);
9965
+ }
9966
+ if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
9967
+ await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(rolesSchema)}`);
9968
+ }
9414
9969
  await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);
9970
+ const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
9971
+ const rolesTableName = `"${rolesSchema}"."roles"`;
9972
+ const userRolesTableName = `"${rolesSchema}"."user_roles"`;
9973
+ const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
9974
+ const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
9975
+ const appConfigTableName = `"${rolesSchema}"."app_config"`;
9415
9976
  await db.execute(sql`
9416
- CREATE TABLE IF NOT EXISTS rebase.users (
9417
- id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9418
- email TEXT NOT NULL UNIQUE,
9419
- password_hash TEXT,
9420
- display_name TEXT,
9421
- photo_url TEXT,
9422
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
9423
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9424
- )
9425
- `);
9426
- await db.execute(sql`
9427
- CREATE INDEX IF NOT EXISTS idx_users_email
9428
- ON rebase.users(email)
9429
- `);
9430
- await db.execute(sql`
9431
- CREATE TABLE IF NOT EXISTS rebase.user_identities (
9977
+ CREATE TABLE IF NOT EXISTS ${sql.raw(userIdentitiesTable)} (
9432
9978
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9433
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9979
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9434
9980
  provider TEXT NOT NULL,
9435
9981
  provider_id TEXT NOT NULL,
9436
9982
  profile_data JSONB,
@@ -9441,10 +9987,10 @@ async function ensureAuthTablesExist(db) {
9441
9987
  `);
9442
9988
  await db.execute(sql`
9443
9989
  CREATE INDEX IF NOT EXISTS idx_user_identities_user
9444
- ON rebase.user_identities(user_id)
9990
+ ON ${sql.raw(userIdentitiesTable)}(user_id)
9445
9991
  `);
9446
9992
  await db.execute(sql`
9447
- CREATE TABLE IF NOT EXISTS rebase.roles (
9993
+ CREATE TABLE IF NOT EXISTS ${sql.raw(rolesTableName)} (
9448
9994
  id TEXT PRIMARY KEY,
9449
9995
  name TEXT NOT NULL,
9450
9996
  is_admin BOOLEAN DEFAULT FALSE,
@@ -9455,20 +10001,20 @@ async function ensureAuthTablesExist(db) {
9455
10001
  )
9456
10002
  `);
9457
10003
  await db.execute(sql`
9458
- CREATE TABLE IF NOT EXISTS rebase.user_roles (
9459
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9460
- role_id TEXT NOT NULL REFERENCES rebase.roles(id) ON DELETE CASCADE,
10004
+ CREATE TABLE IF NOT EXISTS ${sql.raw(userRolesTableName)} (
10005
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
10006
+ role_id TEXT NOT NULL REFERENCES ${sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
9461
10007
  PRIMARY KEY (user_id, role_id)
9462
10008
  )
9463
10009
  `);
9464
10010
  await db.execute(sql`
9465
10011
  CREATE INDEX IF NOT EXISTS idx_user_roles_user
9466
- ON rebase.user_roles(user_id)
10012
+ ON ${sql.raw(userRolesTableName)}(user_id)
9467
10013
  `);
9468
10014
  await db.execute(sql`
9469
- CREATE TABLE IF NOT EXISTS rebase.refresh_tokens (
10015
+ CREATE TABLE IF NOT EXISTS ${sql.raw(refreshTokensTableName)} (
9470
10016
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9471
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
10017
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9472
10018
  token_hash TEXT NOT NULL UNIQUE,
9473
10019
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9474
10020
  user_agent TEXT,
@@ -9479,16 +10025,16 @@ async function ensureAuthTablesExist(db) {
9479
10025
  `);
9480
10026
  await db.execute(sql`
9481
10027
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash
9482
- ON rebase.refresh_tokens(token_hash)
10028
+ ON ${sql.raw(refreshTokensTableName)}(token_hash)
9483
10029
  `);
9484
10030
  await db.execute(sql`
9485
10031
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user
9486
- ON rebase.refresh_tokens(user_id)
10032
+ ON ${sql.raw(refreshTokensTableName)}(user_id)
9487
10033
  `);
9488
10034
  await db.execute(sql`
9489
- CREATE TABLE IF NOT EXISTS rebase.password_reset_tokens (
10035
+ CREATE TABLE IF NOT EXISTS ${sql.raw(passwordResetTokensTableName)} (
9490
10036
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9491
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
10037
+ user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9492
10038
  token_hash TEXT NOT NULL UNIQUE,
9493
10039
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9494
10040
  used_at TIMESTAMP WITH TIME ZONE,
@@ -9497,20 +10043,19 @@ async function ensureAuthTablesExist(db) {
9497
10043
  `);
9498
10044
  await db.execute(sql`
9499
10045
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_hash
9500
- ON rebase.password_reset_tokens(token_hash)
10046
+ ON ${sql.raw(passwordResetTokensTableName)}(token_hash)
9501
10047
  `);
9502
10048
  await db.execute(sql`
9503
10049
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
9504
- ON rebase.password_reset_tokens(user_id)
10050
+ ON ${sql.raw(passwordResetTokensTableName)}(user_id)
9505
10051
  `);
9506
10052
  await db.execute(sql`
9507
- CREATE TABLE IF NOT EXISTS rebase.app_config (
10053
+ CREATE TABLE IF NOT EXISTS ${sql.raw(appConfigTableName)} (
9508
10054
  key TEXT PRIMARY KEY,
9509
10055
  value JSONB NOT NULL,
9510
10056
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9511
10057
  )
9512
10058
  `);
9513
- await applyInternalMigrations(db);
9514
10059
  await db.execute(sql`CREATE SCHEMA IF NOT EXISTS auth`);
9515
10060
  await db.transaction(async (tx) => {
9516
10061
  await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);
@@ -9533,15 +10078,15 @@ async function ensureAuthTablesExist(db) {
9533
10078
  $$ LANGUAGE sql STABLE
9534
10079
  `);
9535
10080
  });
9536
- await seedDefaultRoles(db);
10081
+ await seedDefaultRoles(db, rolesTableName);
9537
10082
  console.log("✅ Auth tables ready");
9538
10083
  } catch (error) {
9539
10084
  console.error("❌ Failed to create auth tables:", error);
9540
10085
  console.warn("⚠️ Continuing without creating auth tables.");
9541
10086
  }
9542
10087
  }
9543
- async function seedDefaultRoles(db) {
9544
- const result = await db.execute(sql`SELECT COUNT(*) as count FROM rebase.roles`);
10088
+ async function seedDefaultRoles(db, rolesTableName) {
10089
+ const result = await db.execute(sql`SELECT COUNT(*) as count FROM ${sql.raw(rolesTableName)}`);
9545
10090
  const count2 = parseInt(result.rows[0]?.count || "0", 10);
9546
10091
  if (count2 > 0) {
9547
10092
  console.log(`📋 Found ${count2} existing roles`);
@@ -9550,7 +10095,7 @@ async function seedDefaultRoles(db) {
9550
10095
  console.log("🌱 Seeding default roles...");
9551
10096
  for (const role of DEFAULT_ROLES) {
9552
10097
  await db.execute(sql`
9553
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, config)
10098
+ INSERT INTO ${sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
9554
10099
  VALUES (
9555
10100
  ${role.id},
9556
10101
  ${role.name},
@@ -9563,142 +10108,156 @@ async function seedDefaultRoles(db) {
9563
10108
  }
9564
10109
  console.log("✅ Default roles created: admin, editor, viewer");
9565
10110
  }
9566
- async function applyInternalMigrations(db) {
9567
- try {
9568
- await db.execute(sql`
9569
- ALTER TABLE rebase.users
9570
- ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE,
9571
- ADD COLUMN IF NOT EXISTS email_verification_token TEXT,
9572
- ADD COLUMN IF NOT EXISTS email_verification_sent_at TIMESTAMP WITH TIME ZONE
9573
- `);
9574
- const columnsCheck = await db.execute(sql`
9575
- SELECT column_name
9576
- FROM information_schema.columns
9577
- WHERE table_schema='rebase' AND table_name='users' AND column_name IN ('google_id', 'linkedin_id', 'provider')
9578
- `);
9579
- const existingColumns = columnsCheck.rows.map((r) => r.column_name);
9580
- if (existingColumns.includes("google_id")) {
9581
- await db.execute(sql`
9582
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9583
- SELECT id, 'google', google_id
9584
- FROM rebase.users
9585
- WHERE google_id IS NOT NULL
9586
- ON CONFLICT (provider, provider_id) DO NOTHING
9587
- `);
9588
- }
9589
- if (existingColumns.includes("linkedin_id")) {
9590
- await db.execute(sql`
9591
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9592
- SELECT id, 'linkedin', linkedin_id
9593
- FROM rebase.users
9594
- WHERE linkedin_id IS NOT NULL
9595
- ON CONFLICT (provider, provider_id) DO NOTHING
9596
- `);
9597
- }
9598
- if (existingColumns.length > 0) {
9599
- await db.execute(sql`
9600
- ALTER TABLE rebase.users
9601
- DROP COLUMN IF EXISTS provider,
9602
- DROP COLUMN IF EXISTS google_id,
9603
- DROP COLUMN IF EXISTS linkedin_id
9604
- `);
9605
- await db.execute(sql`DROP INDEX IF EXISTS rebase.idx_users_google_id`);
9606
- await db.execute(sql`DROP INDEX IF EXISTS rebase.idx_users_linkedin_id`);
9607
- console.log("✅ Migrated to user_identities and dropped legacy columns.");
9608
- }
9609
- await db.execute(sql`
9610
- ALTER TABLE rebase.roles
9611
- ADD COLUMN IF NOT EXISTS collection_permissions JSONB
9612
- `);
9613
- await db.execute(sql`
9614
- ALTER TABLE rebase.refresh_tokens
9615
- ADD COLUMN IF NOT EXISTS user_agent TEXT,
9616
- ADD COLUMN IF NOT EXISTS ip_address TEXT
9617
- `);
9618
- const constraintCheck = await db.execute(sql`
9619
- SELECT 1 FROM information_schema.table_constraints
9620
- WHERE constraint_name = 'unique_device_session'
9621
- AND table_schema = 'rebase'
9622
- AND table_name = 'refresh_tokens'
9623
- `);
9624
- if (constraintCheck.rows.length === 0) {
9625
- try {
9626
- await db.execute(sql`
9627
- ALTER TABLE rebase.refresh_tokens
9628
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9629
- `);
9630
- console.log("✅ Added unique_device_session constraint");
9631
- } catch (e) {
9632
- const errorMessage = e instanceof Error ? e.message : String(e);
9633
- if (errorMessage.includes("could not create unique index")) {
9634
- console.warn("⚠️ Duplicate sessions found, cleaning up before adding constraint...");
9635
- await db.execute(sql`
9636
- DELETE FROM rebase.refresh_tokens a
9637
- USING rebase.refresh_tokens b
9638
- WHERE a.user_id = b.user_id
9639
- AND COALESCE(a.user_agent, '') = COALESCE(b.user_agent, '')
9640
- AND COALESCE(a.ip_address, '') = COALESCE(b.ip_address, '')
9641
- AND a.created_at < b.created_at
9642
- `);
9643
- await db.execute(sql`
9644
- ALTER TABLE rebase.refresh_tokens
9645
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9646
- `).catch((retryErr) => {
9647
- const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
9648
- console.error("Failed to add unique_device_session constraint after cleanup:", retryMessage);
9649
- });
9650
- } else {
9651
- console.error("Constraint migration issue:", errorMessage);
9652
- }
9653
- }
9654
- }
9655
- } catch (error) {
9656
- console.error("❌ Failed to run internal migrations:", error);
10111
+ function getColumnKey(table, ...keys2) {
10112
+ if (!table) return void 0;
10113
+ for (const key of keys2) {
10114
+ if (key in table) return key;
10115
+ const snake = toSnakeCase(key);
10116
+ if (snake in table) return snake;
10117
+ const camel = camelCase(key);
10118
+ if (camel in table) return camel;
9657
10119
  }
10120
+ return void 0;
10121
+ }
10122
+ function getColumn(table, ...keys2) {
10123
+ if (!table) return void 0;
10124
+ const key = getColumnKey(table, ...keys2);
10125
+ return key ? table[key] : void 0;
9658
10126
  }
9659
10127
  class UserService {
9660
- constructor(db) {
10128
+ constructor(db, tableOrTables) {
9661
10129
  this.db = db;
10130
+ if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
10131
+ const tables = tableOrTables;
10132
+ this.usersTable = tables.users || users;
10133
+ this.userIdentitiesTable = tables.userIdentities || userIdentities;
10134
+ this.userRolesTable = tables.userRoles || userRoles;
10135
+ this.rolesTable = tables.roles || roles;
10136
+ } else {
10137
+ const table = tableOrTables;
10138
+ this.usersTable = table || users;
10139
+ this.userIdentitiesTable = userIdentities;
10140
+ this.userRolesTable = userRoles;
10141
+ this.rolesTable = roles;
10142
+ }
10143
+ }
10144
+ usersTable;
10145
+ userIdentitiesTable;
10146
+ userRolesTable;
10147
+ rolesTable;
10148
+ getQualifiedUsersTableName() {
10149
+ const name = getTableName$1(this.usersTable);
10150
+ const schema = getTableConfig(this.usersTable).schema || "public";
10151
+ return `"${schema}"."${name}"`;
10152
+ }
10153
+ mapRowToUser(row) {
10154
+ if (!row) return row;
10155
+ const id = row.id ?? row.uid;
10156
+ const email = row.email;
10157
+ const passwordHash = row.password_hash ?? row.passwordHash ?? null;
10158
+ const displayName = row.display_name ?? row.displayName ?? null;
10159
+ const photoUrl = row.photo_url ?? row.photoUrl ?? row.photoURL ?? null;
10160
+ const emailVerified = row.email_verified ?? row.emailVerified ?? false;
10161
+ const emailVerificationToken = row.email_verification_token ?? row.emailVerificationToken ?? null;
10162
+ const emailVerificationSentAt = row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null;
10163
+ const createdAt = row.created_at ?? row.createdAt;
10164
+ const updatedAt = row.updated_at ?? row.updatedAt;
10165
+ const metadata = {
10166
+ ...row.metadata || {}
10167
+ };
10168
+ 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"]);
10169
+ for (const [key, val] of Object.entries(row)) {
10170
+ if (!knownKeys.has(key)) {
10171
+ const camelKey = camelCase(key);
10172
+ metadata[camelKey] = val;
10173
+ }
10174
+ }
10175
+ return {
10176
+ id,
10177
+ email,
10178
+ passwordHash,
10179
+ displayName,
10180
+ photoUrl,
10181
+ emailVerified,
10182
+ emailVerificationToken,
10183
+ emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
10184
+ createdAt: createdAt ? new Date(createdAt) : /* @__PURE__ */ new Date(),
10185
+ updatedAt: updatedAt ? new Date(updatedAt) : /* @__PURE__ */ new Date(),
10186
+ metadata
10187
+ };
10188
+ }
10189
+ mapPayload(data) {
10190
+ if (!data) return {};
10191
+ const payload = {};
10192
+ const idKey = getColumnKey(this.usersTable, "id") || "id";
10193
+ const emailKey = getColumnKey(this.usersTable, "email") || "email";
10194
+ const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10195
+ const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
10196
+ const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
10197
+ const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10198
+ const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10199
+ const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10200
+ const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
10201
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10202
+ const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
10203
+ if ("id" in data) payload[idKey] = data.id;
10204
+ if ("email" in data) payload[emailKey] = data.email;
10205
+ if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
10206
+ if ("displayName" in data) payload[displayNameKey] = data.displayName;
10207
+ if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
10208
+ if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
10209
+ if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
10210
+ if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
10211
+ if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
10212
+ if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
10213
+ const metadata = {
10214
+ ...data.metadata || {}
10215
+ };
10216
+ const remainingMetadata = {};
10217
+ for (const [key, val] of Object.entries(metadata)) {
10218
+ const tableColKey = getColumnKey(this.usersTable, key);
10219
+ if (tableColKey && tableColKey !== idKey && tableColKey !== emailKey && tableColKey !== passwordHashKey && tableColKey !== displayNameKey && tableColKey !== photoUrlKey && tableColKey !== emailVerifiedKey && tableColKey !== emailVerificationTokenKey && tableColKey !== emailVerificationSentAtKey && tableColKey !== createdAtKey && tableColKey !== updatedAtKey && tableColKey !== metadataKey) {
10220
+ payload[tableColKey] = val;
10221
+ } else {
10222
+ remainingMetadata[key] = val;
10223
+ }
10224
+ }
10225
+ if (metadataKey in this.usersTable) {
10226
+ payload[metadataKey] = remainingMetadata;
10227
+ }
10228
+ return payload;
9662
10229
  }
9663
10230
  async createUser(data) {
9664
- const [user] = await this.db.insert(users).values(data).returning();
9665
- return user;
10231
+ const payload = this.mapPayload(data);
10232
+ const [row] = await this.db.insert(this.usersTable).values(payload).returning();
10233
+ return this.mapRowToUser(row);
9666
10234
  }
9667
10235
  async getUserById(id) {
9668
- const [user] = await this.db.select().from(users).where(eq(users.id, id));
9669
- return user || null;
10236
+ const idCol = getColumn(this.usersTable, "id");
10237
+ if (!idCol) return null;
10238
+ const [row] = await this.db.select().from(this.usersTable).where(eq(idCol, id));
10239
+ return row ? this.mapRowToUser(row) : null;
9670
10240
  }
9671
10241
  async getUserByEmail(email) {
9672
- const [user] = await this.db.select().from(users).where(eq(users.email, email.toLowerCase()));
9673
- return user || null;
10242
+ const emailCol = getColumn(this.usersTable, "email");
10243
+ if (!emailCol) return null;
10244
+ const [row] = await this.db.select().from(this.usersTable).where(eq(emailCol, email.toLowerCase()));
10245
+ return row ? this.mapRowToUser(row) : null;
9674
10246
  }
9675
10247
  async getUserByIdentity(provider, providerId) {
9676
- const result = await this.db.execute(sql`
9677
- SELECT u.*
9678
- FROM rebase.users u
9679
- INNER JOIN rebase.user_identities ui ON u.id = ui.user_id
9680
- WHERE ui.provider = ${provider} AND ui.provider_id = ${providerId}
9681
- LIMIT 1
9682
- `);
9683
- if (result.rows.length === 0) return null;
9684
- const row = result.rows[0];
9685
- return {
9686
- id: row.id,
9687
- email: row.email,
9688
- passwordHash: row.password_hash ?? null,
9689
- displayName: row.display_name ?? null,
9690
- photoUrl: row.photo_url ?? null,
9691
- emailVerified: row.email_verified ?? false,
9692
- emailVerificationToken: row.email_verification_token ?? null,
9693
- emailVerificationSentAt: row.email_verification_sent_at ?? null,
9694
- createdAt: row.created_at,
9695
- updatedAt: row.updated_at
9696
- };
10248
+ const userIdCol = getColumn(this.usersTable, "id");
10249
+ if (!userIdCol) return null;
10250
+ const result = await this.db.select({
10251
+ user: this.usersTable
10252
+ }).from(this.usersTable).innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.userId)).where(sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`).limit(1);
10253
+ if (result.length === 0) return null;
10254
+ return this.mapRowToUser(result[0].user);
9697
10255
  }
9698
10256
  async getUserIdentities(userId) {
10257
+ const schema = getTableConfig(this.userIdentitiesTable).schema || "public";
9699
10258
  const result = await this.db.execute(sql`
9700
10259
  SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
9701
- FROM rebase.user_identities
10260
+ FROM ${sql.raw(`"${schema}"."user_identities"`)}
9702
10261
  WHERE user_id = ${userId}
9703
10262
  `);
9704
10263
  return result.rows.map((row) => ({
@@ -9712,27 +10271,32 @@ class UserService {
9712
10271
  }));
9713
10272
  }
9714
10273
  async linkUserIdentity(userId, provider, providerId, profileData) {
9715
- await this.db.insert(userIdentities).values({
10274
+ await this.db.insert(this.userIdentitiesTable).values({
9716
10275
  userId,
9717
10276
  provider,
9718
10277
  providerId,
9719
10278
  profileData: profileData || null
9720
10279
  }).onConflictDoNothing({
9721
- target: [userIdentities.provider, userIdentities.providerId]
10280
+ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId]
9722
10281
  });
9723
10282
  }
9724
10283
  async updateUser(id, data) {
9725
- const [user] = await this.db.update(users).set({
9726
- ...data,
9727
- updatedAt: /* @__PURE__ */ new Date()
9728
- }).where(eq(users.id, id)).returning();
9729
- return user || null;
10284
+ const idCol = getColumn(this.usersTable, "id");
10285
+ if (!idCol) return null;
10286
+ const payload = this.mapPayload(data);
10287
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10288
+ payload[updatedAtKey] = /* @__PURE__ */ new Date();
10289
+ const [row] = await this.db.update(this.usersTable).set(payload).where(eq(idCol, id)).returning();
10290
+ return row ? this.mapRowToUser(row) : null;
9730
10291
  }
9731
10292
  async deleteUser(id) {
9732
- await this.db.delete(users).where(eq(users.id, id));
10293
+ const idCol = getColumn(this.usersTable, "id");
10294
+ if (!idCol) return;
10295
+ await this.db.delete(this.usersTable).where(eq(idCol, id));
9733
10296
  }
9734
10297
  async listUsers() {
9735
- return this.db.select().from(users);
10298
+ const rows = await this.db.select().from(this.usersTable);
10299
+ return rows.map((row) => this.mapRowToUser(row));
9736
10300
  }
9737
10301
  async listUsersPaginated(options) {
9738
10302
  const limit = options?.limit ?? 25;
@@ -9741,49 +10305,40 @@ class UserService {
9741
10305
  const orderBy = options?.orderBy || "createdAt";
9742
10306
  const orderDir = options?.orderDir || "desc";
9743
10307
  const roleId = options?.roleId;
9744
- const columnMap = {
9745
- email: "email",
9746
- displayName: "display_name",
9747
- createdAt: "created_at",
9748
- updatedAt: "updated_at",
9749
- provider: "provider"
9750
- };
9751
- const orderColumn = columnMap[orderBy] || "created_at";
10308
+ const orderCol = getColumn(this.usersTable, orderBy);
10309
+ const orderColumn = orderCol ? orderCol.name : "created_at";
9752
10310
  const direction = orderDir === "asc" ? sql`ASC` : sql`DESC`;
10311
+ const emailCol = getColumn(this.usersTable, "email");
10312
+ const emailColumn = emailCol ? emailCol.name : "email";
10313
+ const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
10314
+ const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
10315
+ const idCol = getColumn(this.usersTable, "id");
10316
+ const idColumn = idCol ? idCol.name : "id";
10317
+ const usersTableName = this.getQualifiedUsersTableName();
10318
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
9753
10319
  const conditions = [];
9754
10320
  if (roleId) {
9755
- conditions.push(sql`EXISTS (SELECT 1 FROM rebase.user_roles ur WHERE ur.user_id = users.id AND ur.role_id = ${roleId})`);
10321
+ conditions.push(sql`EXISTS (SELECT 1 FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
9756
10322
  }
9757
10323
  if (search) {
9758
10324
  const pattern = `%${search}%`;
9759
- conditions.push(sql`(email ILIKE ${pattern} OR display_name ILIKE ${pattern})`);
10325
+ conditions.push(sql`(${sql.raw(usersTableName)}.${sql.raw(emailColumn)} ILIKE ${pattern} OR ${sql.raw(usersTableName)}.${sql.raw(displayNameColumn)} ILIKE ${pattern})`);
9760
10326
  }
9761
10327
  const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;
9762
- const orderByClause = roleId ? sql`ORDER BY ${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY (SELECT count(*) FROM rebase.user_roles ur WHERE ur.user_id = users.id) DESC, ${sql.raw(orderColumn)} ${direction}`;
10328
+ const orderByClause = roleId ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY (SELECT count(*) FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)}) DESC, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;
9763
10329
  const countResult = await this.db.execute(sql`
9764
- SELECT count(*)::int as total FROM rebase.users
10330
+ SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}
9765
10331
  ${whereClause}
9766
10332
  `);
9767
10333
  const total = countResult.rows[0].total;
9768
10334
  const dataResult = await this.db.execute(sql`
9769
- SELECT * FROM rebase.users
10335
+ SELECT * FROM ${sql.raw(usersTableName)}
9770
10336
  ${whereClause}
9771
10337
  ${orderByClause}
9772
10338
  LIMIT ${limit} OFFSET ${offset}
9773
10339
  `);
9774
10340
  const rows = dataResult.rows;
9775
- const mappedUsers = rows.map((row) => ({
9776
- id: row.id,
9777
- email: row.email,
9778
- passwordHash: row.password_hash ?? row.passwordHash ?? null,
9779
- displayName: row.display_name ?? row.displayName ?? null,
9780
- photoUrl: row.photo_url ?? row.photoUrl ?? null,
9781
- emailVerified: row.email_verified ?? row.emailVerified ?? false,
9782
- emailVerificationToken: row.email_verification_token ?? row.emailVerificationToken ?? null,
9783
- emailVerificationSentAt: row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null,
9784
- createdAt: row.created_at ?? row.createdAt,
9785
- updatedAt: row.updated_at ?? row.updatedAt
9786
- }));
10341
+ const mappedUsers = rows.map((row) => this.mapRowToUser(row));
9787
10342
  return {
9788
10343
  users: mappedUsers,
9789
10344
  total,
@@ -9795,46 +10350,63 @@ class UserService {
9795
10350
  * Update user's password hash
9796
10351
  */
9797
10352
  async updatePassword(id, passwordHash) {
9798
- await this.db.update(users).set({
9799
- passwordHash,
9800
- updatedAt: /* @__PURE__ */ new Date()
9801
- }).where(eq(users.id, id));
10353
+ const idCol = getColumn(this.usersTable, "id");
10354
+ if (!idCol) return;
10355
+ const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10356
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10357
+ await this.db.update(this.usersTable).set({
10358
+ [passwordHashColKey]: passwordHash,
10359
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10360
+ }).where(eq(idCol, id));
9802
10361
  }
9803
10362
  /**
9804
10363
  * Set email verification status
9805
10364
  */
9806
10365
  async setEmailVerified(id, verified) {
9807
- await this.db.update(users).set({
9808
- emailVerified: verified,
9809
- emailVerificationToken: null,
9810
- updatedAt: /* @__PURE__ */ new Date()
9811
- }).where(eq(users.id, id));
10366
+ const idCol = getColumn(this.usersTable, "id");
10367
+ if (!idCol) return;
10368
+ const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10369
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10370
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10371
+ await this.db.update(this.usersTable).set({
10372
+ [emailVerifiedColKey]: verified,
10373
+ [emailVerificationTokenColKey]: null,
10374
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10375
+ }).where(eq(idCol, id));
9812
10376
  }
9813
10377
  /**
9814
10378
  * Set email verification token
9815
10379
  */
9816
10380
  async setVerificationToken(id, token) {
9817
- await this.db.update(users).set({
9818
- emailVerificationToken: token,
9819
- emailVerificationSentAt: token ? /* @__PURE__ */ new Date() : null,
9820
- updatedAt: /* @__PURE__ */ new Date()
9821
- }).where(eq(users.id, id));
10381
+ const idCol = getColumn(this.usersTable, "id");
10382
+ if (!idCol) return;
10383
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10384
+ const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10385
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10386
+ await this.db.update(this.usersTable).set({
10387
+ [emailVerificationTokenColKey]: token,
10388
+ [emailVerificationSentAtColKey]: token ? /* @__PURE__ */ new Date() : null,
10389
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10390
+ }).where(eq(idCol, id));
9822
10391
  }
9823
10392
  /**
9824
10393
  * Find user by email verification token
9825
10394
  */
9826
10395
  async getUserByVerificationToken(token) {
9827
- const [user] = await this.db.select().from(users).where(eq(users.emailVerificationToken, token));
9828
- return user || null;
10396
+ const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
10397
+ if (!tokenCol) return null;
10398
+ const [row] = await this.db.select().from(this.usersTable).where(eq(tokenCol, token));
10399
+ return row ? this.mapRowToUser(row) : null;
9829
10400
  }
9830
10401
  /**
9831
10402
  * Get roles for a user from database
9832
10403
  */
9833
10404
  async getUserRoles(userId) {
10405
+ const rolesSchema = getTableConfig(this.rolesTable).schema || "public";
9834
10406
  const result = await this.db.execute(sql`
9835
10407
  SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
9836
- FROM rebase.roles r
9837
- INNER JOIN rebase.user_roles ur ON r.id = ur.role_id
10408
+ FROM ${sql.raw(`"${rolesSchema}"."roles"`)} r
10409
+ INNER JOIN ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
9838
10410
  WHERE ur.user_id = ${userId}
9839
10411
  `);
9840
10412
  return result.rows.map((row) => ({
@@ -9857,10 +10429,11 @@ class UserService {
9857
10429
  * Set roles for a user
9858
10430
  */
9859
10431
  async setUserRoles(userId, roleIds) {
9860
- await this.db.execute(sql`DELETE FROM rebase.user_roles WHERE user_id = ${userId}`);
10432
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
10433
+ await this.db.execute(sql`DELETE FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
9861
10434
  for (const roleId of roleIds) {
9862
10435
  await this.db.execute(sql`
9863
- INSERT INTO rebase.user_roles (user_id, role_id)
10436
+ INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9864
10437
  VALUES (${userId}, ${roleId})
9865
10438
  ON CONFLICT DO NOTHING
9866
10439
  `);
@@ -9870,8 +10443,9 @@ class UserService {
9870
10443
  * Assign a specific role to new user
9871
10444
  */
9872
10445
  async assignDefaultRole(userId, roleId) {
10446
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
9873
10447
  await this.db.execute(sql`
9874
- INSERT INTO rebase.user_roles (user_id, role_id)
10448
+ INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9875
10449
  VALUES (${userId}, ${roleId})
9876
10450
  ON CONFLICT DO NOTHING
9877
10451
  `);
@@ -9890,13 +10464,25 @@ class UserService {
9890
10464
  }
9891
10465
  }
9892
10466
  class RoleService {
9893
- constructor(db) {
10467
+ constructor(db, tableOrTables) {
9894
10468
  this.db = db;
10469
+ if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
10470
+ this.rolesTable = tableOrTables.roles || roles;
10471
+ } else {
10472
+ this.rolesTable = tableOrTables || roles;
10473
+ }
10474
+ }
10475
+ rolesTable;
10476
+ getQualifiedRolesTableName() {
10477
+ const name = getTableName$1(this.rolesTable);
10478
+ const schema = getTableConfig(this.rolesTable).schema || "public";
10479
+ return `"${schema}"."${name}"`;
9895
10480
  }
9896
10481
  async getRoleById(id) {
10482
+ const tableName = this.getQualifiedRolesTableName();
9897
10483
  const result = await this.db.execute(sql`
9898
10484
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9899
- FROM rebase.roles
10485
+ FROM ${sql.raw(tableName)}
9900
10486
  WHERE id = ${id}
9901
10487
  `);
9902
10488
  if (result.rows.length === 0) return null;
@@ -9911,9 +10497,10 @@ class RoleService {
9911
10497
  };
9912
10498
  }
9913
10499
  async listRoles() {
10500
+ const tableName = this.getQualifiedRolesTableName();
9914
10501
  const result = await this.db.execute(sql`
9915
10502
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9916
- FROM rebase.roles
10503
+ FROM ${sql.raw(tableName)}
9917
10504
  ORDER BY name
9918
10505
  `);
9919
10506
  return result.rows.map((row) => ({
@@ -9926,8 +10513,9 @@ class RoleService {
9926
10513
  }));
9927
10514
  }
9928
10515
  async createRole(data) {
10516
+ const tableName = this.getQualifiedRolesTableName();
9929
10517
  const result = await this.db.execute(sql`
9930
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, collection_permissions, config)
10518
+ INSERT INTO ${sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
9931
10519
  VALUES (
9932
10520
  ${data.id},
9933
10521
  ${data.name},
@@ -9951,8 +10539,9 @@ class RoleService {
9951
10539
  async updateRole(id, data) {
9952
10540
  const existing = await this.getRoleById(id);
9953
10541
  if (!existing) return null;
10542
+ const tableName = this.getQualifiedRolesTableName();
9954
10543
  await this.db.execute(sql`
9955
- UPDATE rebase.roles
10544
+ UPDATE ${sql.raw(tableName)}
9956
10545
  SET
9957
10546
  name = ${data.name ?? existing.name},
9958
10547
  is_admin = ${data.isAdmin ?? existing.isAdmin},
@@ -9964,23 +10553,36 @@ class RoleService {
9964
10553
  return this.getRoleById(id);
9965
10554
  }
9966
10555
  async deleteRole(id) {
9967
- await this.db.execute(sql`DELETE FROM rebase.roles WHERE id = ${id}`);
10556
+ const tableName = this.getQualifiedRolesTableName();
10557
+ await this.db.execute(sql`DELETE FROM ${sql.raw(tableName)} WHERE id = ${id}`);
9968
10558
  }
9969
10559
  }
9970
10560
  class RefreshTokenService {
9971
- constructor(db) {
10561
+ constructor(db, tableOrTables) {
9972
10562
  this.db = db;
10563
+ if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) {
10564
+ this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
10565
+ } else {
10566
+ this.refreshTokensTable = tableOrTables || refreshTokens;
10567
+ }
10568
+ }
10569
+ refreshTokensTable;
10570
+ getQualifiedRefreshTokensTableName() {
10571
+ const name = getTableName$1(this.refreshTokensTable);
10572
+ const schema = getTableConfig(this.refreshTokensTable).schema || "public";
10573
+ return `"${schema}"."${name}"`;
9973
10574
  }
9974
10575
  async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
9975
10576
  const safeUserAgent = userAgent || "";
9976
10577
  const safeIpAddress = ipAddress || "";
10578
+ const tableName = this.getQualifiedRefreshTokensTableName();
9977
10579
  await this.db.execute(sql`
9978
- DELETE FROM rebase.refresh_tokens
10580
+ DELETE FROM ${sql.raw(tableName)}
9979
10581
  WHERE user_id = ${userId}
9980
10582
  AND user_agent = ${safeUserAgent}
9981
10583
  AND ip_address = ${safeIpAddress}
9982
10584
  `);
9983
- await this.db.insert(refreshTokens).values({
10585
+ await this.db.insert(this.refreshTokensTable).values({
9984
10586
  userId,
9985
10587
  tokenHash,
9986
10588
  expiresAt,
@@ -9990,51 +10592,63 @@ class RefreshTokenService {
9990
10592
  }
9991
10593
  async findByHash(tokenHash) {
9992
10594
  const [token] = await this.db.select({
9993
- id: refreshTokens.id,
9994
- userId: refreshTokens.userId,
9995
- tokenHash: refreshTokens.tokenHash,
9996
- expiresAt: refreshTokens.expiresAt,
9997
- createdAt: refreshTokens.createdAt,
9998
- userAgent: refreshTokens.userAgent,
9999
- ipAddress: refreshTokens.ipAddress
10000
- }).from(refreshTokens).where(eq(refreshTokens.tokenHash, tokenHash));
10595
+ id: this.refreshTokensTable.id,
10596
+ userId: this.refreshTokensTable.userId,
10597
+ tokenHash: this.refreshTokensTable.tokenHash,
10598
+ expiresAt: this.refreshTokensTable.expiresAt,
10599
+ createdAt: this.refreshTokensTable.createdAt,
10600
+ userAgent: this.refreshTokensTable.userAgent,
10601
+ ipAddress: this.refreshTokensTable.ipAddress
10602
+ }).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
10001
10603
  return token || null;
10002
10604
  }
10003
10605
  async deleteByHash(tokenHash) {
10004
- await this.db.delete(refreshTokens).where(eq(refreshTokens.tokenHash, tokenHash));
10606
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
10005
10607
  }
10006
10608
  async deleteAllForUser(userId) {
10007
- await this.db.delete(refreshTokens).where(eq(refreshTokens.userId, userId));
10609
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId));
10008
10610
  }
10009
10611
  async listForUser(userId) {
10010
10612
  const tokens = await this.db.select({
10011
- id: refreshTokens.id,
10012
- userId: refreshTokens.userId,
10013
- tokenHash: refreshTokens.tokenHash,
10014
- expiresAt: refreshTokens.expiresAt,
10015
- createdAt: refreshTokens.createdAt,
10016
- userAgent: refreshTokens.userAgent,
10017
- ipAddress: refreshTokens.ipAddress
10018
- }).from(refreshTokens).where(eq(refreshTokens.userId, userId)).orderBy(refreshTokens.createdAt);
10613
+ id: this.refreshTokensTable.id,
10614
+ userId: this.refreshTokensTable.userId,
10615
+ tokenHash: this.refreshTokensTable.tokenHash,
10616
+ expiresAt: this.refreshTokensTable.expiresAt,
10617
+ createdAt: this.refreshTokensTable.createdAt,
10618
+ userAgent: this.refreshTokensTable.userAgent,
10619
+ ipAddress: this.refreshTokensTable.ipAddress
10620
+ }).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId)).orderBy(this.refreshTokensTable.createdAt);
10019
10621
  return tokens;
10020
10622
  }
10021
10623
  async deleteById(id, userId) {
10022
- await this.db.delete(refreshTokens).where(sql`${refreshTokens.id} = ${id} AND ${refreshTokens.userId} = ${userId}`);
10624
+ await this.db.delete(this.refreshTokensTable).where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
10023
10625
  }
10024
10626
  }
10025
10627
  class PasswordResetTokenService {
10026
- constructor(db) {
10628
+ constructor(db, tableOrTables) {
10027
10629
  this.db = db;
10630
+ if (tableOrTables && (tableOrTables.passwordResetTokens || tableOrTables.users)) {
10631
+ this.passwordResetTokensTable = tableOrTables.passwordResetTokens || passwordResetTokens;
10632
+ } else {
10633
+ this.passwordResetTokensTable = tableOrTables || passwordResetTokens;
10634
+ }
10635
+ }
10636
+ passwordResetTokensTable;
10637
+ getQualifiedPasswordResetTokensTableName() {
10638
+ const name = getTableName$1(this.passwordResetTokensTable);
10639
+ const schema = getTableConfig(this.passwordResetTokensTable).schema || "public";
10640
+ return `"${schema}"."${name}"`;
10028
10641
  }
10029
10642
  /**
10030
10643
  * Create a password reset token
10031
10644
  */
10032
10645
  async createToken(userId, tokenHash, expiresAt) {
10646
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10033
10647
  await this.db.execute(sql`
10034
- DELETE FROM rebase.password_reset_tokens
10648
+ DELETE FROM ${sql.raw(tableName)}
10035
10649
  WHERE user_id = ${userId} AND used_at IS NULL
10036
10650
  `);
10037
- await this.db.insert(passwordResetTokens).values({
10651
+ await this.db.insert(this.passwordResetTokensTable).values({
10038
10652
  userId,
10039
10653
  tokenHash,
10040
10654
  expiresAt
@@ -10045,13 +10659,14 @@ class PasswordResetTokenService {
10045
10659
  */
10046
10660
  async findValidByHash(tokenHash) {
10047
10661
  const [token] = await this.db.select({
10048
- userId: passwordResetTokens.userId,
10049
- expiresAt: passwordResetTokens.expiresAt
10050
- }).from(passwordResetTokens).where(eq(passwordResetTokens.tokenHash, tokenHash));
10662
+ userId: this.passwordResetTokensTable.userId,
10663
+ expiresAt: this.passwordResetTokensTable.expiresAt
10664
+ }).from(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10051
10665
  if (!token) return null;
10666
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10052
10667
  const result = await this.db.execute(sql`
10053
10668
  SELECT user_id, expires_at
10054
- FROM rebase.password_reset_tokens
10669
+ FROM ${sql.raw(tableName)}
10055
10670
  WHERE token_hash = ${tokenHash}
10056
10671
  AND used_at IS NULL
10057
10672
  AND expires_at > NOW()
@@ -10067,31 +10682,32 @@ class PasswordResetTokenService {
10067
10682
  * Mark token as used
10068
10683
  */
10069
10684
  async markAsUsed(tokenHash) {
10070
- await this.db.update(passwordResetTokens).set({
10685
+ await this.db.update(this.passwordResetTokensTable).set({
10071
10686
  usedAt: /* @__PURE__ */ new Date()
10072
- }).where(eq(passwordResetTokens.tokenHash, tokenHash));
10687
+ }).where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10073
10688
  }
10074
10689
  /**
10075
10690
  * Delete all tokens for a user
10076
10691
  */
10077
10692
  async deleteAllForUser(userId) {
10078
- await this.db.delete(passwordResetTokens).where(eq(passwordResetTokens.userId, userId));
10693
+ await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.userId, userId));
10079
10694
  }
10080
10695
  /**
10081
10696
  * Clean up expired tokens
10082
10697
  */
10083
10698
  async deleteExpired() {
10699
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10084
10700
  await this.db.execute(sql`
10085
- DELETE FROM rebase.password_reset_tokens
10701
+ DELETE FROM ${sql.raw(tableName)}
10086
10702
  WHERE expires_at < NOW()
10087
10703
  `);
10088
10704
  }
10089
10705
  }
10090
10706
  class PostgresTokenRepository {
10091
- constructor(db) {
10707
+ constructor(db, tableOrTables) {
10092
10708
  this.db = db;
10093
- this.refreshTokenService = new RefreshTokenService(db);
10094
- this.passwordResetTokenService = new PasswordResetTokenService(db);
10709
+ this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
10710
+ this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
10095
10711
  }
10096
10712
  refreshTokenService;
10097
10713
  passwordResetTokenService;
@@ -10132,11 +10748,11 @@ class PostgresTokenRepository {
10132
10748
  }
10133
10749
  }
10134
10750
  class PostgresAuthRepository {
10135
- constructor(db) {
10751
+ constructor(db, tableOrTables) {
10136
10752
  this.db = db;
10137
- this.userService = new UserService(db);
10138
- this.roleService = new RoleService(db);
10139
- this.tokenRepository = new PostgresTokenRepository(db);
10753
+ this.userService = new UserService(db, tableOrTables);
10754
+ this.roleService = new RoleService(db, tableOrTables);
10755
+ this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
10140
10756
  }
10141
10757
  userService;
10142
10758
  roleService;
@@ -10197,8 +10813,7 @@ class PostgresAuthRepository {
10197
10813
  await this.userService.assignDefaultRole(userId, roleId);
10198
10814
  }
10199
10815
  async getUserWithRoles(userId) {
10200
- const result = await this.userService.getUserWithRoles(userId);
10201
- return result;
10816
+ return this.userService.getUserWithRoles(userId);
10202
10817
  }
10203
10818
  // Role operations (delegate to RoleService)
10204
10819
  async getRoleById(id) {
@@ -10386,6 +11001,24 @@ class HistoryService {
10386
11001
  return result.rowCount ?? 0;
10387
11002
  }
10388
11003
  }
11004
+ function deepEqual(a, b) {
11005
+ if (a === b) return true;
11006
+ if (a == null || b == null) return false;
11007
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
11008
+ if (Array.isArray(a) && Array.isArray(b)) {
11009
+ if (a.length !== b.length) return false;
11010
+ return a.every((v, i) => deepEqual(v, b[i]));
11011
+ }
11012
+ if (typeof a === "object" && typeof b === "object") {
11013
+ const aObj = a;
11014
+ const bObj = b;
11015
+ const aKeys = Object.keys(aObj);
11016
+ const bKeys = Object.keys(bObj);
11017
+ if (aKeys.length !== bKeys.length) return false;
11018
+ return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
11019
+ }
11020
+ return false;
11021
+ }
10389
11022
  function findChangedFields(oldValues, newValues) {
10390
11023
  const changed = [];
10391
11024
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
@@ -10395,7 +11028,7 @@ function findChangedFields(oldValues, newValues) {
10395
11028
  if (key.startsWith("__")) continue;
10396
11029
  if (oldVal !== newVal) {
10397
11030
  if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
10398
- if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
11031
+ if (!deepEqual(oldVal, newVal)) {
10399
11032
  changed.push(key);
10400
11033
  }
10401
11034
  } else {
@@ -10513,14 +11146,32 @@ function createPostgresBootstrapper(pgConfig) {
10513
11146
  if (!authConfig) return void 0;
10514
11147
  const internals = driverResult.internals;
10515
11148
  const db = internals.db;
10516
- await ensureAuthTablesExist(db);
11149
+ const registry = internals.registry;
11150
+ await ensureAuthTablesExist(db, registry);
10517
11151
  let emailService;
10518
11152
  if (authConfig.email) {
10519
11153
  emailService = createEmailService(authConfig.email);
10520
11154
  }
10521
- const userService = new UserService(db);
10522
- const roleService = new RoleService(db);
10523
- const authRepository = new PostgresAuthRepository(db);
11155
+ const customUsersTable = registry?.getTable("users");
11156
+ const customRolesTable = registry?.getTable("roles");
11157
+ let usersSchemaName = "rebase";
11158
+ let rolesSchemaName = "rebase";
11159
+ if (customUsersTable) {
11160
+ usersSchemaName = getTableConfig(customUsersTable).schema || "public";
11161
+ }
11162
+ if (customRolesTable) {
11163
+ rolesSchemaName = getTableConfig(customRolesTable).schema || "public";
11164
+ }
11165
+ const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
11166
+ if (customUsersTable) {
11167
+ authTables.users = customUsersTable;
11168
+ }
11169
+ if (customRolesTable) {
11170
+ authTables.roles = customRolesTable;
11171
+ }
11172
+ const userService = new UserService(db, authTables);
11173
+ const roleService = new RoleService(db, authTables);
11174
+ const authRepository = new PostgresAuthRepository(db, authTables);
10524
11175
  return {
10525
11176
  userService,
10526
11177
  roleService,
@@ -10552,11 +11203,54 @@ function createPostgresBootstrapper(pgConfig) {
10552
11203
  },
10553
11204
  mountRoutes(app, basePath, driverResult) {
10554
11205
  },
10555
- async initializeWebsockets(server, realtimeService, driver, config) {
11206
+ async initializeWebsockets(server, realtimeService, driver, config, adapter) {
10556
11207
  const {
10557
11208
  createPostgresWebSocket: createPostgresWebSocket2
10558
11209
  } = await Promise.resolve().then(() => websocket);
10559
- createPostgresWebSocket2(server, realtimeService, driver, config);
11210
+ createPostgresWebSocket2(server, realtimeService, driver, config, adapter);
11211
+ }
11212
+ };
11213
+ }
11214
+ function createPostgresAdapter(pgConfig) {
11215
+ const bootstrapper = createPostgresBootstrapper(pgConfig);
11216
+ return {
11217
+ type: bootstrapper.type,
11218
+ async initializeDriver(config) {
11219
+ return bootstrapper.initializeDriver(config);
11220
+ },
11221
+ async initializeRealtime(driverResult) {
11222
+ if (bootstrapper.initializeRealtime) {
11223
+ return bootstrapper.initializeRealtime({}, driverResult);
11224
+ }
11225
+ return void 0;
11226
+ },
11227
+ async initializeAuth(config, driverResult) {
11228
+ if (bootstrapper.initializeAuth) {
11229
+ return bootstrapper.initializeAuth(config, driverResult);
11230
+ }
11231
+ return void 0;
11232
+ },
11233
+ async initializeHistory(config, driverResult) {
11234
+ if (bootstrapper.initializeHistory) {
11235
+ return bootstrapper.initializeHistory(config, driverResult);
11236
+ }
11237
+ return void 0;
11238
+ },
11239
+ initializeWebsockets(server, realtimeService, driver, config) {
11240
+ if (bootstrapper.initializeWebsockets) {
11241
+ return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
11242
+ }
11243
+ },
11244
+ getAdmin(driverResult) {
11245
+ if (bootstrapper.getAdmin) {
11246
+ return bootstrapper.getAdmin(driverResult);
11247
+ }
11248
+ return void 0;
11249
+ },
11250
+ mountRoutes(app, basePath, driverResult) {
11251
+ if (bootstrapper.mountRoutes) {
11252
+ bootstrapper.mountRoutes(app, basePath, driverResult);
11253
+ }
10560
11254
  }
10561
11255
  };
10562
11256
  }
@@ -10571,6 +11265,8 @@ export {
10571
11265
  PostgresRealtimeProvider,
10572
11266
  RealtimeService,
10573
11267
  appConfig,
11268
+ createAuthSchema,
11269
+ createPostgresAdapter,
10574
11270
  createPostgresBootstrapper,
10575
11271
  createPostgresDatabaseConnection,
10576
11272
  createPostgresWebSocket,
@@ -10587,6 +11283,7 @@ export {
10587
11283
  userRoles,
10588
11284
  userRolesRelations,
10589
11285
  users,
10590
- usersRelations
11286
+ usersRelations,
11287
+ usersSchema
10591
11288
  };
10592
11289
  //# sourceMappingURL=index.es.js.map