@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.umd.js CHANGED
@@ -59,6 +59,12 @@
59
59
  connectionString
60
60
  };
61
61
  }
62
+ class Vector {
63
+ value;
64
+ constructor(value) {
65
+ this.value = value;
66
+ }
67
+ }
62
68
  function isPostgresCollection(collection) {
63
69
  return !collection.driver || collection.driver === "postgres";
64
70
  }
@@ -135,16 +141,25 @@
135
141
  const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
136
142
  const snakeCaseRegex = tokenizeRegex;
137
143
  const toSnakeCase = (str) => {
144
+ if (!str || typeof str !== "string") return "";
138
145
  const regExpMatchArray = str.match(snakeCaseRegex);
139
146
  if (!regExpMatchArray) return "";
140
147
  return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
141
148
  };
149
+ function camelCase(str) {
150
+ if (!str) return "";
151
+ if (str.length === 1) return str.toLowerCase();
152
+ const parts = str.split(/[-_ ]+/).filter(Boolean);
153
+ if (parts.length === 0) return "";
154
+ return parts[0].toLowerCase() + // Transform remaining parts to have first letter uppercase
155
+ parts.slice(1).map((part) => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase()).join("");
156
+ }
142
157
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
143
158
  function commonjsRequire(path2) {
144
159
  throw new Error('Could not dynamically require "' + path2 + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
145
160
  }
146
161
  var object_hash = { exports: {} };
147
- (function(module2, exports$1) {
162
+ (function(module2, exports3) {
148
163
  !function(e) {
149
164
  module2.exports = e();
150
165
  }(function() {
@@ -1151,126 +1166,58 @@
1151
1166
  });
1152
1167
  }
1153
1168
  }
1154
- function getSubcollections(collection) {
1155
- if (collection.childCollections) {
1156
- return collection.childCollections() ?? [];
1157
- }
1158
- if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1159
- return collection.subcollections() ?? [];
1169
+ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1170
+ if (!relation.target) {
1171
+ throw new Error("Relation is missing a `target` collection.");
1160
1172
  }
1161
- if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
1162
- const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
1163
- return manyRelations.map((r) => {
1164
- const target = r.target();
1165
- if (!target) return void 0;
1166
- const relationKey = r.relationName || target.slug;
1167
- let customName;
1168
- if (collection.properties) {
1169
- const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1170
- if (prop && prop[1].name) {
1171
- customName = prop[1].name;
1172
- }
1173
- }
1174
- const baseOverrides = {
1175
- slug: relationKey
1176
- };
1177
- if (customName) {
1178
- baseOverrides.name = customName;
1179
- baseOverrides.singularName = customName;
1180
- }
1181
- const targetWithOverrides = {
1182
- ...target,
1183
- ...baseOverrides
1173
+ const rawTarget = relation.target;
1174
+ let targetCollection;
1175
+ if (typeof rawTarget === "string") {
1176
+ if (resolveCollection) {
1177
+ targetCollection = resolveCollection(rawTarget);
1178
+ }
1179
+ if (!targetCollection) {
1180
+ targetCollection = {
1181
+ slug: rawTarget,
1182
+ name: rawTarget
1184
1183
  };
1185
- return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1186
- }).filter((c) => Boolean(c));
1187
- }
1188
- return [];
1189
- }
1190
- function hasPropertyCallbacks(properties, callbackName) {
1191
- if (!properties) return false;
1192
- for (const property of Object.values(properties)) {
1193
- if (property.callbacks?.[callbackName]) return true;
1194
- if (property.type === "map" && property.properties) {
1195
- if (hasPropertyCallbacks(property.properties, callbackName)) return true;
1196
- } else if (property.type === "array" && property.of) {
1197
- const ofs = Array.isArray(property.of) ? property.of : [property.of];
1198
- for (const of of ofs) {
1199
- if (of.callbacks?.[callbackName]) return true;
1200
- if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
1201
- }
1202
1184
  }
1203
- }
1204
- return false;
1205
- }
1206
- async function processProperties(properties, values, previousValues, propsContext, callbackName) {
1207
- if (!values || typeof values !== "object") return values;
1208
- const result = {
1209
- ...values
1210
- };
1211
- for (const [key, property] of Object.entries(properties)) {
1212
- if (result[key] === void 0) continue;
1213
- let currentValue = result[key];
1214
- const previousValue = previousValues?.[key];
1215
- if (property.type === "array" && Array.isArray(currentValue)) {
1216
- if (property.of && !Array.isArray(property.of)) {
1217
- currentValue = await Promise.all(currentValue.map(async (item, index) => {
1218
- const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
1219
- const singlePropData = {
1220
- "_tmp": property.of
1221
- };
1222
- const res = await processProperties(singlePropData, {
1223
- "_tmp": item
1224
- }, {
1225
- "_tmp": prevItem
1226
- }, propsContext, callbackName);
1227
- return res["_tmp"];
1228
- }));
1185
+ } else if (typeof rawTarget === "function") {
1186
+ const evaluated = rawTarget();
1187
+ if (typeof evaluated === "string") {
1188
+ if (resolveCollection) {
1189
+ targetCollection = resolveCollection(evaluated);
1229
1190
  }
1230
- } else if (property.type === "map" && property.properties && typeof currentValue === "object") {
1231
- currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
1232
- }
1233
- if (property.callbacks?.[callbackName]) {
1234
- const cbRes = await Promise.resolve(property.callbacks[callbackName]({
1235
- ...propsContext,
1236
- value: currentValue,
1237
- previousValue
1238
- }));
1239
- if (cbRes !== void 0) {
1240
- currentValue = cbRes;
1191
+ if (!targetCollection) {
1192
+ targetCollection = {
1193
+ slug: evaluated,
1194
+ name: evaluated
1195
+ };
1241
1196
  }
1197
+ } else {
1198
+ targetCollection = evaluated;
1242
1199
  }
1243
- result[key] = currentValue;
1244
- }
1245
- return result;
1246
- }
1247
- const buildPropertyCallbacks = (properties) => {
1248
- if (!properties) return void 0;
1249
- const propertyCallbacks = {};
1250
- if (hasPropertyCallbacks(properties, "afterRead")) {
1251
- propertyCallbacks.afterRead = async (props) => {
1252
- const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
1253
- return {
1254
- ...props.entity,
1255
- values: processedValues
1256
- };
1257
- };
1200
+ } else if (rawTarget && typeof rawTarget === "object") {
1201
+ targetCollection = rawTarget;
1258
1202
  }
1259
- if (hasPropertyCallbacks(properties, "beforeSave")) {
1260
- propertyCallbacks.beforeSave = async (props) => {
1261
- return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
1262
- };
1263
- }
1264
- return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1265
- };
1266
- function sanitizeRelation(relation, sourceCollection) {
1267
- if (!relation.target) {
1268
- throw new Error("Relation is missing a `target` collection.");
1203
+ if (!targetCollection) {
1204
+ throw new Error("Relation is missing a valid `target` collection.");
1269
1205
  }
1270
- const targetCollection = relation.target();
1271
1206
  const newRelation = {
1272
1207
  ...relation
1273
1208
  };
1209
+ newRelation.target = () => {
1210
+ if (typeof rawTarget === "string") {
1211
+ return resolveCollection && resolveCollection(rawTarget) || targetCollection;
1212
+ } else if (typeof rawTarget === "function") {
1213
+ const evaluated = rawTarget();
1214
+ if (typeof evaluated === "string") {
1215
+ return resolveCollection && resolveCollection(evaluated) || targetCollection;
1216
+ }
1217
+ return evaluated;
1218
+ }
1219
+ return targetCollection;
1220
+ };
1274
1221
  if (!newRelation.relationName) {
1275
1222
  newRelation.relationName = toSnakeCase(targetCollection.slug);
1276
1223
  }
@@ -1323,6 +1270,17 @@
1323
1270
  break;
1324
1271
  }
1325
1272
  }
1273
+ if (!isManyToManyInverse && targetCollection.properties) {
1274
+ for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
1275
+ if (prop.type !== "relation") continue;
1276
+ const relProp = prop;
1277
+ const relName = relProp.relationName || propKey;
1278
+ if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
1279
+ isManyToManyInverse = true;
1280
+ break;
1281
+ }
1282
+ }
1283
+ }
1326
1284
  } catch (e) {
1327
1285
  }
1328
1286
  }
@@ -1360,11 +1318,14 @@
1360
1318
  const registeredRelationNames = /* @__PURE__ */ new Set();
1361
1319
  if (relCollection.relations) {
1362
1320
  relCollection.relations.forEach((relation) => {
1363
- const normalizedRelation = sanitizeRelation(relation, collection);
1364
- const relationKey = normalizedRelation.relationName;
1365
- if (relationKey) {
1366
- relations[relationKey] = normalizedRelation;
1367
- registeredRelationNames.add(relationKey);
1321
+ try {
1322
+ const normalizedRelation = sanitizeRelation(relation, collection);
1323
+ const relationKey = normalizedRelation.relationName;
1324
+ if (relationKey) {
1325
+ relations[relationKey] = normalizedRelation;
1326
+ registeredRelationNames.add(relationKey);
1327
+ }
1328
+ } catch (e) {
1368
1329
  }
1369
1330
  });
1370
1331
  }
@@ -1412,12 +1373,8 @@
1412
1373
  overrides: relProp.overrides
1413
1374
  };
1414
1375
  }
1415
- const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === relProp.relationName);
1416
- if (!relation) {
1417
- console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1418
- return void 0;
1419
- }
1420
- return relation;
1376
+ console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1377
+ return void 0;
1421
1378
  }
1422
1379
  function getTableName(collection) {
1423
1380
  if (getDataSourceCapabilities(collection.driver).supportsRelations) {
@@ -1444,8 +1401,121 @@
1444
1401
  if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
1445
1402
  return void 0;
1446
1403
  }
1404
+ function getSubcollections(collection) {
1405
+ if (collection.childCollections) {
1406
+ return collection.childCollections() ?? [];
1407
+ }
1408
+ if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1409
+ return collection.subcollections() ?? [];
1410
+ }
1411
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
1412
+ const resolvedRelations = resolveCollectionRelations(collection);
1413
+ const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
1414
+ return manyRelations.map((r) => {
1415
+ const target = r.target();
1416
+ if (!target) return void 0;
1417
+ const relationKey = r.relationName || target.slug;
1418
+ let customName;
1419
+ if (collection.properties) {
1420
+ const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1421
+ if (prop && prop[1].name) {
1422
+ customName = prop[1].name;
1423
+ }
1424
+ }
1425
+ const baseOverrides = {
1426
+ slug: relationKey
1427
+ };
1428
+ if (customName) {
1429
+ baseOverrides.name = customName;
1430
+ baseOverrides.singularName = customName;
1431
+ }
1432
+ const targetWithOverrides = {
1433
+ ...target,
1434
+ ...baseOverrides
1435
+ };
1436
+ return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1437
+ }).filter((c) => Boolean(c));
1438
+ }
1439
+ return [];
1440
+ }
1441
+ function hasPropertyCallbacks(properties, callbackName) {
1442
+ if (!properties) return false;
1443
+ for (const property of Object.values(properties)) {
1444
+ if (property.callbacks?.[callbackName]) return true;
1445
+ if (property.type === "map" && property.properties) {
1446
+ if (hasPropertyCallbacks(property.properties, callbackName)) return true;
1447
+ } else if (property.type === "array" && property.of) {
1448
+ const ofs = Array.isArray(property.of) ? property.of : [property.of];
1449
+ for (const of of ofs) {
1450
+ if (of.callbacks?.[callbackName]) return true;
1451
+ if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
1452
+ }
1453
+ }
1454
+ }
1455
+ return false;
1456
+ }
1457
+ async function processProperties(properties, values, previousValues, propsContext, callbackName) {
1458
+ if (!values || typeof values !== "object") return values;
1459
+ const result = {
1460
+ ...values
1461
+ };
1462
+ for (const [key, property] of Object.entries(properties)) {
1463
+ if (result[key] === void 0) continue;
1464
+ let currentValue = result[key];
1465
+ const previousValue = previousValues?.[key];
1466
+ if (property.type === "array" && Array.isArray(currentValue)) {
1467
+ if (property.of && !Array.isArray(property.of)) {
1468
+ currentValue = await Promise.all(currentValue.map(async (item, index) => {
1469
+ const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
1470
+ const singlePropData = {
1471
+ "_tmp": property.of
1472
+ };
1473
+ const res = await processProperties(singlePropData, {
1474
+ "_tmp": item
1475
+ }, {
1476
+ "_tmp": prevItem
1477
+ }, propsContext, callbackName);
1478
+ return res["_tmp"];
1479
+ }));
1480
+ }
1481
+ } else if (property.type === "map" && property.properties && typeof currentValue === "object") {
1482
+ currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
1483
+ }
1484
+ if (property.callbacks?.[callbackName]) {
1485
+ const cbRes = await Promise.resolve(property.callbacks[callbackName]({
1486
+ ...propsContext,
1487
+ value: currentValue,
1488
+ previousValue
1489
+ }));
1490
+ if (cbRes !== void 0) {
1491
+ currentValue = cbRes;
1492
+ }
1493
+ }
1494
+ result[key] = currentValue;
1495
+ }
1496
+ return result;
1497
+ }
1498
+ const buildPropertyCallbacks = (properties) => {
1499
+ if (!properties) return void 0;
1500
+ const propertyCallbacks = {};
1501
+ if (hasPropertyCallbacks(properties, "afterRead")) {
1502
+ propertyCallbacks.afterRead = async (props) => {
1503
+ const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
1504
+ return {
1505
+ ...props.entity,
1506
+ values: processedValues
1507
+ };
1508
+ };
1509
+ }
1510
+ if (hasPropertyCallbacks(properties, "beforeSave")) {
1511
+ propertyCallbacks.beforeSave = async (props) => {
1512
+ return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
1513
+ };
1514
+ }
1515
+ return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1516
+ };
1447
1517
  var logic = { exports: {} };
1448
- (function(module2, exports$1) {
1518
+ (function(module2, exports3) {
1449
1519
  (function(root, factory) {
1450
1520
  {
1451
1521
  module2.exports = factory();
@@ -2202,7 +2272,7 @@
2202
2272
  "[object Uint32Array]": areTypedArraysEqual2
2203
2273
  };
2204
2274
  }
2205
- const deepEqual = createCustomEqual();
2275
+ const deepEqual$1 = createCustomEqual();
2206
2276
  createCustomEqual({ strict: true });
2207
2277
  createCustomEqual({ circular: true });
2208
2278
  createCustomEqual({
@@ -2271,10 +2341,16 @@
2271
2341
  */
2272
2342
  registerMultiple(collections) {
2273
2343
  const rawSnapshot = collections.map((c) => removeFunctions(c));
2274
- if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
2344
+ if (this.lastRawInputSnapshot && deepEqual$1(this.lastRawInputSnapshot, rawSnapshot)) {
2275
2345
  return false;
2276
2346
  }
2277
2347
  this.reset();
2348
+ collections.forEach((c) => {
2349
+ if (c.slug) {
2350
+ this.collectionsBySlug.set(c.slug, c);
2351
+ }
2352
+ this.collectionsByTableName.set(getTableName(c), c);
2353
+ });
2278
2354
  const normalizedCollections = collections.map((c) => this.normalizeCollection({
2279
2355
  ...c
2280
2356
  }));
@@ -2345,15 +2421,25 @@
2345
2421
  const mergedRelationsRaw = [...extractedRelations];
2346
2422
  for (const manual of manualRelations) {
2347
2423
  const name = manual.relationName;
2348
- if (!name || !mergedRelationsRaw.find((r) => r.relationName === name)) {
2424
+ if (!name) {
2349
2425
  mergedRelationsRaw.push(manual);
2426
+ } else {
2427
+ const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
2428
+ if (existingIndex === -1) {
2429
+ mergedRelationsRaw.push(manual);
2430
+ } else {
2431
+ mergedRelationsRaw[existingIndex] = {
2432
+ ...manual,
2433
+ ...mergedRelationsRaw[existingIndex]
2434
+ };
2435
+ }
2350
2436
  }
2351
2437
  }
2352
2438
  let mergedRelations = mergedRelationsRaw;
2353
2439
  if (getDataSourceCapabilities(result.driver).supportsRelations) {
2354
2440
  mergedRelations = mergedRelationsRaw.map((r) => {
2355
2441
  try {
2356
- return sanitizeRelation(r, result);
2442
+ return sanitizeRelation(r, result, (slug) => this.get(slug));
2357
2443
  } catch {
2358
2444
  return r;
2359
2445
  }
@@ -2566,6 +2652,118 @@
2566
2652
  };
2567
2653
  }
2568
2654
  }
2655
+ function mapOperator(op) {
2656
+ switch (op) {
2657
+ case "==":
2658
+ return "eq";
2659
+ case "!=":
2660
+ return "neq";
2661
+ case ">":
2662
+ return "gt";
2663
+ case ">=":
2664
+ return "gte";
2665
+ case "<":
2666
+ return "lt";
2667
+ case "<=":
2668
+ return "lte";
2669
+ case "array-contains":
2670
+ return "cs";
2671
+ case "array-contains-any":
2672
+ return "csa";
2673
+ case "not-in":
2674
+ return "nin";
2675
+ default:
2676
+ return op;
2677
+ }
2678
+ }
2679
+ class QueryBuilder {
2680
+ constructor(collection) {
2681
+ this.collection = collection;
2682
+ }
2683
+ params = {
2684
+ where: {}
2685
+ };
2686
+ /**
2687
+ * Add a filter condition to your query.
2688
+ * @example
2689
+ * client.collection('users').where('age', '>=', 18).find()
2690
+ */
2691
+ where(column, operator, value) {
2692
+ if (!this.params.where) {
2693
+ this.params.where = {};
2694
+ }
2695
+ const mappedOp = mapOperator(operator);
2696
+ let formattedValue = value;
2697
+ if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2698
+ formattedValue = `(${value.join(",")})`;
2699
+ } else if (value === null) {
2700
+ formattedValue = "null";
2701
+ }
2702
+ this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2703
+ return this;
2704
+ }
2705
+ /**
2706
+ * Order the results by a specific column.
2707
+ * @example
2708
+ * client.collection('users').orderBy('createdAt', 'desc').find()
2709
+ */
2710
+ orderBy(column, ascending = "asc") {
2711
+ this.params.orderBy = `${column}:${ascending}`;
2712
+ return this;
2713
+ }
2714
+ /**
2715
+ * Limit the number of results returned.
2716
+ */
2717
+ limit(count) {
2718
+ this.params.limit = count;
2719
+ return this;
2720
+ }
2721
+ /**
2722
+ * Skip the first N results.
2723
+ */
2724
+ offset(count) {
2725
+ this.params.offset = count;
2726
+ return this;
2727
+ }
2728
+ /**
2729
+ * Set a free-text search string if supported by the backend.
2730
+ */
2731
+ search(searchString) {
2732
+ this.params.searchString = searchString;
2733
+ return this;
2734
+ }
2735
+ /**
2736
+ * Include related entities in the response.
2737
+ * Relations will be populated with full entity data instead of just IDs.
2738
+ *
2739
+ * @param relations - Relation names to include, or "*" for all.
2740
+ * @example
2741
+ * // Include specific relations
2742
+ * client.data.posts.include("tags", "author").find()
2743
+ *
2744
+ * // Include all relations
2745
+ * client.data.posts.include("*").find()
2746
+ */
2747
+ include(...relations) {
2748
+ this.params.include = relations;
2749
+ return this;
2750
+ }
2751
+ /**
2752
+ * Execute the find query and return the results.
2753
+ */
2754
+ async find() {
2755
+ return this.collection.find(this.params);
2756
+ }
2757
+ /**
2758
+ * Listen to realtime updates matching this query.
2759
+ */
2760
+ listen(onUpdate, onError) {
2761
+ if (!this.collection.listen) {
2762
+ throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
2763
+ }
2764
+ return this.collection.listen(this.params, onUpdate, onError);
2765
+ }
2766
+ }
2569
2767
  function convertWhereToFilter(where) {
2570
2768
  if (!where) return void 0;
2571
2769
  const operatorMap = {
@@ -2645,7 +2843,7 @@
2645
2843
  return [field, direction];
2646
2844
  }
2647
2845
  function createDriverAccessor(driver, slug) {
2648
- return {
2846
+ const accessor = {
2649
2847
  async find(params) {
2650
2848
  const orderParsed = parseOrderBy(params?.orderBy);
2651
2849
  const entities = await driver.fetchCollection({
@@ -2739,8 +2937,28 @@
2739
2937
  onUpdate: (entity) => onUpdate(entity ?? void 0),
2740
2938
  onError
2741
2939
  });
2742
- } : void 0
2940
+ } : void 0,
2941
+ // Fluent Query Builder
2942
+ where(column, operator, value) {
2943
+ return new QueryBuilder(accessor).where(column, operator, value);
2944
+ },
2945
+ orderBy(column, ascending) {
2946
+ return new QueryBuilder(accessor).orderBy(column, ascending);
2947
+ },
2948
+ limit(count) {
2949
+ return new QueryBuilder(accessor).limit(count);
2950
+ },
2951
+ offset(count) {
2952
+ return new QueryBuilder(accessor).offset(count);
2953
+ },
2954
+ search(searchString) {
2955
+ return new QueryBuilder(accessor).search(searchString);
2956
+ },
2957
+ include(...relations) {
2958
+ return new QueryBuilder(accessor).include(...relations);
2959
+ }
2743
2960
  };
2961
+ return accessor;
2744
2962
  }
2745
2963
  function buildRebaseData(driver) {
2746
2964
  const cache = /* @__PURE__ */ new Map();
@@ -2792,8 +3010,14 @@
2792
3010
  static buildSingleFilterCondition(column, op, value) {
2793
3011
  switch (op) {
2794
3012
  case "==":
3013
+ if (value === null || value === void 0) {
3014
+ return drizzleOrm.sql`${column} IS NULL`;
3015
+ }
2795
3016
  return drizzleOrm.eq(column, value);
2796
3017
  case "!=":
3018
+ if (value === null || value === void 0) {
3019
+ return drizzleOrm.sql`${column} IS NOT NULL`;
3020
+ }
2797
3021
  return drizzleOrm.sql`${column} != ${value}`;
2798
3022
  case ">":
2799
3023
  return drizzleOrm.sql`${column} > ${value}`;
@@ -3124,7 +3348,10 @@
3124
3348
  if (p.type === "string" && !p.enum && p.isId !== "uuid") {
3125
3349
  const fieldColumn = table[key];
3126
3350
  if (fieldColumn) {
3127
- searchConditions.push(drizzleOrm.ilike(fieldColumn, `%${searchString}%`));
3351
+ const supportsILike = fieldColumn instanceof pgCore.PgVarchar || fieldColumn instanceof pgCore.PgText || fieldColumn instanceof pgCore.PgChar || fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn);
3352
+ if (supportsILike) {
3353
+ searchConditions.push(drizzleOrm.ilike(fieldColumn, `%${searchString}%`));
3354
+ }
3128
3355
  }
3129
3356
  }
3130
3357
  }
@@ -3597,6 +3824,31 @@
3597
3824
  return result;
3598
3825
  }
3599
3826
  return value;
3827
+ case "vector": {
3828
+ if (value instanceof Vector) {
3829
+ return value.value;
3830
+ }
3831
+ if (value && typeof value === "object" && "value" in value && Array.isArray(value.value)) {
3832
+ return value.value.map(Number);
3833
+ }
3834
+ if (Array.isArray(value)) {
3835
+ return value.map(Number);
3836
+ }
3837
+ return value;
3838
+ }
3839
+ case "binary":
3840
+ if (typeof value === "string") {
3841
+ if (value.startsWith("data:application/octet-stream;base64,")) {
3842
+ const base64Data = value.split(",")[1];
3843
+ if (base64Data) {
3844
+ return Buffer.from(base64Data, "base64");
3845
+ }
3846
+ }
3847
+ }
3848
+ if (Buffer.isBuffer(value)) {
3849
+ return value;
3850
+ }
3851
+ return value;
3600
3852
  case "string":
3601
3853
  if (typeof value === "string") {
3602
3854
  if (value.startsWith("data:application/octet-stream;base64,")) {
@@ -3741,6 +3993,21 @@
3741
3993
  return value;
3742
3994
  }
3743
3995
  switch (property.type) {
3996
+ case "binary": {
3997
+ let buf = null;
3998
+ if (Buffer.isBuffer(value)) {
3999
+ buf = value;
4000
+ } else if (typeof value === "object" && value !== null) {
4001
+ const rawVal = value;
4002
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
4003
+ buf = Buffer.from(rawVal.data);
4004
+ }
4005
+ }
4006
+ if (buf) {
4007
+ return `data:application/octet-stream;base64,${buf.toString("base64")}`;
4008
+ }
4009
+ return value;
4010
+ }
3744
4011
  case "string": {
3745
4012
  if (typeof value === "string") return value;
3746
4013
  let isBuffer = false;
@@ -3748,9 +4015,12 @@
3748
4015
  if (Buffer.isBuffer(value)) {
3749
4016
  isBuffer = true;
3750
4017
  buf = value;
3751
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3752
- isBuffer = true;
3753
- buf = Buffer.from(value.data);
4018
+ } else if (typeof value === "object" && value !== null) {
4019
+ const rawVal = value;
4020
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
4021
+ isBuffer = true;
4022
+ buf = Buffer.from(rawVal.data);
4023
+ }
3754
4024
  }
3755
4025
  if (isBuffer && buf) {
3756
4026
  let isPrintable = true;
@@ -3837,6 +4107,25 @@
3837
4107
  return isNaN(parsed) ? null : parsed;
3838
4108
  }
3839
4109
  return value;
4110
+ case "vector": {
4111
+ let nums = [];
4112
+ if (typeof value === "string") {
4113
+ nums = value.slice(1, -1).split(",").map(Number);
4114
+ } else if (Array.isArray(value)) {
4115
+ nums = value.map(Number);
4116
+ } else if (value instanceof Vector) {
4117
+ nums = value.value;
4118
+ } else if (typeof value === "object" && value !== null && "value" in value) {
4119
+ const valObj = value;
4120
+ if (Array.isArray(valObj.value)) {
4121
+ nums = valObj.value.map(Number);
4122
+ }
4123
+ }
4124
+ return {
4125
+ __type: "Vector",
4126
+ value: nums
4127
+ };
4128
+ }
3840
4129
  case "date": {
3841
4130
  let date;
3842
4131
  if (value instanceof Date) {
@@ -3861,9 +4150,12 @@
3861
4150
  if (Buffer.isBuffer(value)) {
3862
4151
  isBuffer = true;
3863
4152
  buf = value;
3864
- } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
3865
- isBuffer = true;
3866
- buf = Buffer.from(value.data);
4153
+ } else if (typeof value === "object" && value !== null) {
4154
+ const rawVal = value;
4155
+ if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
4156
+ isBuffer = true;
4157
+ buf = Buffer.from(rawVal.data);
4158
+ }
3867
4159
  }
3868
4160
  if (isBuffer && buf) {
3869
4161
  let isPrintable = true;
@@ -4653,7 +4945,7 @@
4653
4945
  if (parentFKValue !== null && parentFKValue !== void 0) {
4654
4946
  await tx.update(targetTable).set({
4655
4947
  [targetFKColName]: null
4656
- }).where(drizzleOrm.eq(targetFKCol, parentFKValue));
4948
+ }).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
4657
4949
  }
4658
4950
  continue;
4659
4951
  }
@@ -4662,7 +4954,7 @@
4662
4954
  if (parentFKValue !== null && parentFKValue !== void 0) {
4663
4955
  await tx.update(targetTable).set({
4664
4956
  [targetFKColName]: null
4665
- }).where(drizzleOrm.eq(targetFKCol, parentFKValue));
4957
+ }).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
4666
4958
  } else {
4667
4959
  console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
4668
4960
  continue;
@@ -6374,7 +6666,7 @@
6374
6666
  callbacks: void 0,
6375
6667
  propertyCallbacks: void 0
6376
6668
  };
6377
- const registryCollection = this.registry.getCollectionByPath(path2);
6669
+ const registryCollection = this.registry?.getCollectionByPath(path2);
6378
6670
  const resolvedCollection = registryCollection ? {
6379
6671
  ...collection,
6380
6672
  ...registryCollection
@@ -6422,7 +6714,8 @@
6422
6714
  user: this.user,
6423
6715
  driver: this,
6424
6716
  data: this.data,
6425
- client: this.client
6717
+ client: this.client,
6718
+ storageSource: this.client?.storage
6426
6719
  };
6427
6720
  return Promise.all(entities.map(async (entity) => {
6428
6721
  let fetched = entity;
@@ -6517,7 +6810,8 @@
6517
6810
  user: this.user,
6518
6811
  driver: this,
6519
6812
  data: this.data,
6520
- client: this.client
6813
+ client: this.client,
6814
+ storageSource: this.client?.storage
6521
6815
  };
6522
6816
  if (callbacks?.afterRead) {
6523
6817
  entity = await callbacks.afterRead({
@@ -6587,7 +6881,8 @@
6587
6881
  user: this.user,
6588
6882
  driver: this,
6589
6883
  data: this.data,
6590
- client: this.client
6884
+ client: this.client,
6885
+ storageSource: this.client?.storage
6591
6886
  };
6592
6887
  let previousValuesForHistory;
6593
6888
  if (status === "existing" && entityId) {
@@ -6736,26 +7031,37 @@
6736
7031
  user: this.user,
6737
7032
  driver: this,
6738
7033
  data: this.data,
6739
- client: this.client
7034
+ client: this.client,
7035
+ storageSource: this.client?.storage
6740
7036
  };
6741
7037
  if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
7038
+ let preventDefault = false;
6742
7039
  if (callbacks?.beforeDelete) {
6743
- await callbacks.beforeDelete({
7040
+ const result = await callbacks.beforeDelete({
6744
7041
  collection: resolvedCollection,
6745
7042
  path: entity.path,
6746
7043
  entityId: entity.id,
6747
7044
  entity,
6748
7045
  context: contextForCallback
6749
7046
  });
7047
+ if (result === false) {
7048
+ preventDefault = true;
7049
+ }
6750
7050
  }
6751
7051
  if (propertyCallbacks?.beforeDelete) {
6752
- await propertyCallbacks.beforeDelete({
7052
+ const result = await propertyCallbacks.beforeDelete({
6753
7053
  collection: resolvedCollection,
6754
7054
  path: entity.path,
6755
7055
  entityId: entity.id,
6756
7056
  entity,
6757
7057
  context: contextForCallback
6758
7058
  });
7059
+ if (result === false) {
7060
+ preventDefault = true;
7061
+ }
7062
+ }
7063
+ if (preventDefault) {
7064
+ return;
6759
7065
  }
6760
7066
  }
6761
7067
  await this.entityService.deleteEntity(entity.path, entity.id, entity.databaseId || resolvedCollection?.databaseId);
@@ -6828,7 +7134,17 @@
6828
7134
  }
6829
7135
  const targetDb = this.getTargetDb(options?.database);
6830
7136
  try {
6831
- if (options?.role) {
7137
+ let needsRoleSwitch = false;
7138
+ if (options?.role && process.env.DISABLE_DB_ROLE_SWITCHING !== "true") {
7139
+ try {
7140
+ const currentRoleResult = await targetDb.execute(drizzleOrm.sql.raw("SELECT current_user AS role"));
7141
+ const currentRole = currentRoleResult.rows?.[0]?.role;
7142
+ needsRoleSwitch = !!currentRole && currentRole !== options.role;
7143
+ } catch {
7144
+ needsRoleSwitch = true;
7145
+ }
7146
+ }
7147
+ if (needsRoleSwitch && options?.role) {
6832
7148
  const safeRole = options.role.replace(/"/g, '""');
6833
7149
  return await targetDb.transaction(async (tx) => {
6834
7150
  await tx.execute(drizzleOrm.sql.raw(`SET LOCAL ROLE "${safeRole}"`));
@@ -6866,7 +7182,7 @@
6866
7182
  return databases;
6867
7183
  }
6868
7184
  async fetchAvailableRoles() {
6869
- const result = await this.executeSql("SELECT rolname FROM pg_roles;");
7185
+ const result = await this.executeSql("SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;");
6870
7186
  return result.map((r) => r.rolname);
6871
7187
  }
6872
7188
  async fetchCurrentDatabase() {
@@ -7035,6 +7351,21 @@
7035
7351
  * Typed admin capabilities — delegates to the base driver.
7036
7352
  */
7037
7353
  admin;
7354
+ get restFetchService() {
7355
+ if (!this.delegate.restFetchService) return void 0;
7356
+ return {
7357
+ fetchCollectionForRest: async (collectionPath, options, include) => {
7358
+ return this.withTransaction(async (delegate) => {
7359
+ return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);
7360
+ });
7361
+ },
7362
+ fetchEntityForRest: async (collectionPath, entityId, include, databaseId) => {
7363
+ return this.withTransaction(async (delegate) => {
7364
+ return delegate.restFetchService.fetchEntityForRest(collectionPath, entityId, include, databaseId);
7365
+ });
7366
+ }
7367
+ };
7368
+ }
7038
7369
  async withTransaction(operation) {
7039
7370
  const pendingNotifications = [];
7040
7371
  const result = await this.delegate.db.transaction(async (tx) => {
@@ -7189,113 +7520,140 @@
7189
7520
  this.pools.clear();
7190
7521
  }
7191
7522
  }
7192
- const rebaseSchema = pgCore.pgSchema("rebase");
7193
- const users = rebaseSchema.table("users", {
7194
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7195
- email: pgCore.varchar("email", {
7196
- length: 255
7197
- }).notNull().unique(),
7198
- passwordHash: pgCore.varchar("password_hash", {
7199
- length: 255
7200
- }),
7201
- // NULL for OAuth-only users
7202
- displayName: pgCore.varchar("display_name", {
7203
- length: 255
7204
- }),
7205
- photoUrl: pgCore.varchar("photo_url", {
7206
- length: 500
7207
- }),
7208
- emailVerified: pgCore.boolean("email_verified").default(false).notNull(),
7209
- emailVerificationToken: pgCore.varchar("email_verification_token", {
7210
- length: 255
7211
- }),
7212
- emailVerificationSentAt: pgCore.timestamp("email_verification_sent_at"),
7213
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7214
- updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7215
- });
7216
- const roles = rebaseSchema.table("roles", {
7217
- id: pgCore.varchar("id", {
7218
- length: 50
7219
- }).primaryKey(),
7220
- // 'admin', 'editor', 'viewer'
7221
- name: pgCore.varchar("name", {
7222
- length: 100
7223
- }).notNull(),
7224
- isAdmin: pgCore.boolean("is_admin").default(false).notNull(),
7225
- defaultPermissions: pgCore.jsonb("default_permissions").$type(),
7226
- collectionPermissions: pgCore.jsonb("collection_permissions").$type(),
7227
- config: pgCore.jsonb("config").$type()
7228
- });
7229
- const userRoles = rebaseSchema.table("user_roles", {
7230
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7231
- onDelete: "cascade"
7232
- }),
7233
- roleId: pgCore.varchar("role_id", {
7234
- length: 50
7235
- }).notNull().references(() => roles.id, {
7236
- onDelete: "cascade"
7237
- })
7238
- }, (table) => ({
7239
- pk: pgCore.primaryKey({
7240
- columns: [table.userId, table.roleId]
7241
- })
7242
- }));
7243
- const refreshTokens = rebaseSchema.table("refresh_tokens", {
7244
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7245
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7246
- onDelete: "cascade"
7247
- }),
7248
- tokenHash: pgCore.varchar("token_hash", {
7249
- length: 255
7250
- }).notNull().unique(),
7251
- expiresAt: pgCore.timestamp("expires_at").notNull(),
7252
- userAgent: pgCore.varchar("user_agent", {
7253
- length: 500
7254
- }),
7255
- ipAddress: pgCore.varchar("ip_address", {
7256
- length: 45
7257
- }),
7258
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7259
- }, (table) => ({
7260
- uniqueDeviceSession: pgCore.unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7261
- }));
7262
- const passwordResetTokens = rebaseSchema.table("password_reset_tokens", {
7263
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7264
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7265
- onDelete: "cascade"
7266
- }),
7267
- tokenHash: pgCore.varchar("token_hash", {
7268
- length: 255
7269
- }).notNull().unique(),
7270
- expiresAt: pgCore.timestamp("expires_at").notNull(),
7271
- usedAt: pgCore.timestamp("used_at"),
7272
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7273
- });
7274
- const appConfig = rebaseSchema.table("app_config", {
7275
- key: pgCore.varchar("key", {
7276
- length: 100
7277
- }).primaryKey(),
7278
- value: pgCore.jsonb("value").notNull(),
7279
- updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7280
- });
7281
- const userIdentities = rebaseSchema.table("user_identities", {
7282
- id: pgCore.uuid("id").defaultRandom().primaryKey(),
7283
- userId: pgCore.uuid("user_id").notNull().references(() => users.id, {
7284
- onDelete: "cascade"
7285
- }),
7286
- provider: pgCore.varchar("provider", {
7287
- length: 50
7288
- }).notNull(),
7289
- // e.g. 'google', 'linkedin'
7290
- providerId: pgCore.varchar("provider_id", {
7291
- length: 255
7292
- }).notNull(),
7293
- profileData: pgCore.jsonb("profile_data"),
7294
- createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7295
- updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7296
- }, (table) => ({
7297
- uniqueProviderId: pgCore.unique("unique_provider_id").on(table.provider, table.providerId)
7298
- }));
7523
+ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
7524
+ const rolesSchema = rolesSchemaName === "public" ? null : pgCore.pgSchema(rolesSchemaName);
7525
+ const usersSchema2 = usersSchemaName === "public" ? null : pgCore.pgSchema(usersSchemaName);
7526
+ const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgCore.pgTable;
7527
+ const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgCore.pgTable;
7528
+ const users2 = usersTableCreator("users", {
7529
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7530
+ email: pgCore.varchar("email", {
7531
+ length: 255
7532
+ }).notNull().unique(),
7533
+ passwordHash: pgCore.varchar("password_hash", {
7534
+ length: 255
7535
+ }),
7536
+ // NULL for OAuth-only users
7537
+ displayName: pgCore.varchar("display_name", {
7538
+ length: 255
7539
+ }),
7540
+ photoUrl: pgCore.varchar("photo_url", {
7541
+ length: 500
7542
+ }),
7543
+ emailVerified: pgCore.boolean("email_verified").default(false).notNull(),
7544
+ emailVerificationToken: pgCore.varchar("email_verification_token", {
7545
+ length: 255
7546
+ }),
7547
+ emailVerificationSentAt: pgCore.timestamp("email_verification_sent_at"),
7548
+ metadata: pgCore.jsonb("metadata").$type().default({}).notNull(),
7549
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7550
+ updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7551
+ });
7552
+ const roles2 = rolesTableCreator("roles", {
7553
+ id: pgCore.varchar("id", {
7554
+ length: 50
7555
+ }).primaryKey(),
7556
+ // 'admin', 'editor', 'viewer'
7557
+ name: pgCore.varchar("name", {
7558
+ length: 100
7559
+ }).notNull(),
7560
+ isAdmin: pgCore.boolean("is_admin").default(false).notNull(),
7561
+ defaultPermissions: pgCore.jsonb("default_permissions").$type(),
7562
+ collectionPermissions: pgCore.jsonb("collection_permissions").$type(),
7563
+ config: pgCore.jsonb("config").$type()
7564
+ });
7565
+ const userRoles2 = rolesTableCreator("user_roles", {
7566
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7567
+ onDelete: "cascade"
7568
+ }),
7569
+ roleId: pgCore.varchar("role_id", {
7570
+ length: 50
7571
+ }).notNull().references(() => roles2.id, {
7572
+ onDelete: "cascade"
7573
+ })
7574
+ }, (table) => ({
7575
+ pk: pgCore.primaryKey({
7576
+ columns: [table.userId, table.roleId]
7577
+ })
7578
+ }));
7579
+ const refreshTokens2 = rolesTableCreator("refresh_tokens", {
7580
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7581
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7582
+ onDelete: "cascade"
7583
+ }),
7584
+ tokenHash: pgCore.varchar("token_hash", {
7585
+ length: 255
7586
+ }).notNull().unique(),
7587
+ expiresAt: pgCore.timestamp("expires_at").notNull(),
7588
+ userAgent: pgCore.varchar("user_agent", {
7589
+ length: 500
7590
+ }),
7591
+ ipAddress: pgCore.varchar("ip_address", {
7592
+ length: 45
7593
+ }),
7594
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7595
+ }, (table) => ({
7596
+ uniqueDeviceSession: pgCore.unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
7597
+ }));
7598
+ const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
7599
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7600
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7601
+ onDelete: "cascade"
7602
+ }),
7603
+ tokenHash: pgCore.varchar("token_hash", {
7604
+ length: 255
7605
+ }).notNull().unique(),
7606
+ expiresAt: pgCore.timestamp("expires_at").notNull(),
7607
+ usedAt: pgCore.timestamp("used_at"),
7608
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
7609
+ });
7610
+ const appConfig2 = rolesTableCreator("app_config", {
7611
+ key: pgCore.varchar("key", {
7612
+ length: 100
7613
+ }).primaryKey(),
7614
+ value: pgCore.jsonb("value").notNull(),
7615
+ updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7616
+ });
7617
+ const userIdentities2 = rolesTableCreator("user_identities", {
7618
+ id: pgCore.uuid("id").defaultRandom().primaryKey(),
7619
+ userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
7620
+ onDelete: "cascade"
7621
+ }),
7622
+ provider: pgCore.varchar("provider", {
7623
+ length: 50
7624
+ }).notNull(),
7625
+ // e.g. 'google', 'linkedin'
7626
+ providerId: pgCore.varchar("provider_id", {
7627
+ length: 255
7628
+ }).notNull(),
7629
+ profileData: pgCore.jsonb("profile_data"),
7630
+ createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
7631
+ updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
7632
+ }, (table) => ({
7633
+ uniqueProviderId: pgCore.unique("unique_provider_id").on(table.provider, table.providerId)
7634
+ }));
7635
+ return {
7636
+ rolesSchema,
7637
+ usersSchema: usersSchema2,
7638
+ users: users2,
7639
+ roles: roles2,
7640
+ userRoles: userRoles2,
7641
+ refreshTokens: refreshTokens2,
7642
+ passwordResetTokens: passwordResetTokens2,
7643
+ appConfig: appConfig2,
7644
+ userIdentities: userIdentities2
7645
+ };
7646
+ }
7647
+ const defaultAuthSchema = createAuthSchema("rebase", "rebase");
7648
+ const rebaseSchema = defaultAuthSchema.rolesSchema;
7649
+ const usersSchema = defaultAuthSchema.usersSchema;
7650
+ const users = defaultAuthSchema.users;
7651
+ const roles = defaultAuthSchema.roles;
7652
+ const userRoles = defaultAuthSchema.userRoles;
7653
+ const refreshTokens = defaultAuthSchema.refreshTokens;
7654
+ const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
7655
+ const appConfig = defaultAuthSchema.appConfig;
7656
+ const userIdentities = defaultAuthSchema.userIdentities;
7299
7657
  const usersRelations = drizzleOrm.relations(users, ({
7300
7658
  many
7301
7659
  }) => ({
@@ -7478,6 +7836,15 @@
7478
7836
  }
7479
7837
  break;
7480
7838
  }
7839
+ case "vector": {
7840
+ const vp = prop;
7841
+ columnDefinition = `vector("${colName}", { dimensions: ${vp.dimensions} })`;
7842
+ break;
7843
+ }
7844
+ case "binary": {
7845
+ columnDefinition = `customType({ dataType() { return 'bytea'; } })("${colName}")`;
7846
+ break;
7847
+ }
7481
7848
  case "relation": {
7482
7849
  const refProp = prop;
7483
7850
  const resolvedRelations = resolveCollectionRelations(collection);
@@ -7544,7 +7911,7 @@
7544
7911
  return ` ${propName}: ${columnDefinition}`;
7545
7912
  };
7546
7913
  const resolveRawSql = (expression) => {
7547
- const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => `\${table.${col}}`);
7914
+ const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
7548
7915
  return `sql\`${resolved}\``;
7549
7916
  };
7550
7917
  const wrapWithRoleCheck = (clause, roles2) => {
@@ -7556,7 +7923,7 @@
7556
7923
  const match = sqlExpr.match(/^sql`(.*)`$/s);
7557
7924
  return match ? match[1] : sqlExpr;
7558
7925
  };
7559
- const buildUsingClause = (rule) => {
7926
+ const buildUsingClause = (rule, collection) => {
7560
7927
  if (rule.using) {
7561
7928
  return resolveRawSql(rule.using);
7562
7929
  }
@@ -7564,15 +7931,17 @@
7564
7931
  return "sql`true`";
7565
7932
  }
7566
7933
  if (rule.ownerField) {
7567
- return `sql\`\${table.${rule.ownerField}} = auth.uid()\``;
7934
+ const prop = collection.properties?.[rule.ownerField];
7935
+ const colName = resolveColumnName(rule.ownerField, prop);
7936
+ return `sql\`${colName} = auth.uid()\``;
7568
7937
  }
7569
7938
  return null;
7570
7939
  };
7571
- const buildWithCheckClause = (rule) => {
7940
+ const buildWithCheckClause = (rule, collection) => {
7572
7941
  if (rule.withCheck) {
7573
7942
  return resolveRawSql(rule.withCheck);
7574
7943
  }
7575
- return buildUsingClause(rule);
7944
+ return buildUsingClause(rule, collection);
7576
7945
  };
7577
7946
  const getPolicyNameHash = (rule) => {
7578
7947
  const data = JSON.stringify({
@@ -7588,21 +7957,22 @@
7588
7957
  });
7589
7958
  return crypto.createHash("sha1").update(data).digest("hex").substring(0, 7);
7590
7959
  };
7591
- const generatePolicyCode = (tableName, rule, index) => {
7960
+ const generatePolicyCode = (collection, rule, index) => {
7961
+ const tableName = getTableName(collection);
7592
7962
  const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
7593
7963
  const ruleHash = getPolicyNameHash(rule);
7594
7964
  return ops.map((op, opIdx) => {
7595
7965
  const policyName = rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
7596
- return generateSinglePolicyCode(tableName, rule, op, policyName);
7966
+ return generateSinglePolicyCode(collection, rule, op, policyName);
7597
7967
  }).join("");
7598
7968
  };
7599
- const generateSinglePolicyCode = (tableName, rule, operation, policyName) => {
7969
+ const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
7600
7970
  const mode = rule.mode ?? "permissive";
7601
7971
  const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
7602
7972
  const needsUsing = operation !== "insert";
7603
7973
  const needsWithCheck = operation !== "select" && operation !== "delete";
7604
- let usingClause = needsUsing ? buildUsingClause(rule) : null;
7605
- let withCheckClause = needsWithCheck ? buildWithCheckClause(rule) : null;
7974
+ let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
7975
+ let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
7606
7976
  if (roles2 && roles2.length > 0) {
7607
7977
  if (usingClause) {
7608
7978
  usingClause = wrapWithRoleCheck(usingClause, roles2);
@@ -7671,12 +8041,26 @@
7671
8041
  const generateSchema = async (collections, stripPolicies = false) => {
7672
8042
  let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
7673
8043
  const hasUuid = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "string" && (p.autoValue === "uuid" || p.isId === "uuid")));
7674
- collections.some((c) => c.properties && Object.values(c.properties).some((p) => (p.type === "map" || p.type === "array") && p.columnType === "json"));
8044
+ const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
8045
+ const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
7675
8046
  const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
7676
8047
  if (hasUuid) pgCoreImports.push("uuid");
8048
+ if (hasVector) pgCoreImports.push("vector");
8049
+ if (hasBinary) pgCoreImports.push("customType");
8050
+ const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollection(c) ? c.schema : void 0).filter(Boolean)));
8051
+ if (uniqueSchemas.length > 0) {
8052
+ pgCoreImports.push("pgSchema");
8053
+ }
7677
8054
  schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';
7678
8055
  `;
7679
8056
  schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
8057
+ uniqueSchemas.forEach((schema) => {
8058
+ schemaContent += `export const ${schema}Schema = pgSchema("${schema}");
8059
+ `;
8060
+ });
8061
+ if (uniqueSchemas.length > 0) {
8062
+ schemaContent += "\n";
8063
+ }
7680
8064
  const exportedTableVars = [];
7681
8065
  const exportedEnumVars = [];
7682
8066
  const exportedRelationVars = [];
@@ -7731,6 +8115,9 @@
7731
8115
  const tableVarName = getTableVarName(tableName);
7732
8116
  if (isJunction && relation && sourceCollection && relation.through) {
7733
8117
  const targetCollection = relation.target();
8118
+ const schema = (isPostgresCollection(targetCollection) ? targetCollection.schema : void 0) || (isPostgresCollection(sourceCollection) ? sourceCollection.schema : void 0);
8119
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
8120
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
7734
8121
  const {
7735
8122
  sourceColumn,
7736
8123
  targetColumn
@@ -7741,7 +8128,7 @@
7741
8128
  const targetColType = isNumericId(targetCollection) ? "integer" : getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar";
7742
8129
  const sourceId = getPrimaryKeyName(sourceCollection);
7743
8130
  const targetId = getPrimaryKeyName(targetCollection);
7744
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
8131
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7745
8132
  `;
7746
8133
  schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
7747
8134
  `;
@@ -7752,7 +8139,10 @@
7752
8139
  `;
7753
8140
  schemaContent += "}));\n\n";
7754
8141
  } else if (!isJunction) {
7755
- schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
8142
+ const schema = isPostgresCollection(collection) ? collection.schema : void 0;
8143
+ const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
8144
+ const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
8145
+ schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
7756
8146
  `;
7757
8147
  const columns = /* @__PURE__ */ new Set();
7758
8148
  Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
@@ -7768,7 +8158,7 @@
7768
8158
  if (!stripPolicies && securityRules && securityRules.length > 0) {
7769
8159
  schemaContent += "\n}, (table) => ([\n";
7770
8160
  securityRules.forEach((rule, idx) => {
7771
- schemaContent += generatePolicyCode(tableName, rule);
8161
+ schemaContent += generatePolicyCode(collection, rule);
7772
8162
  });
7773
8163
  schemaContent += "])).enableRLS();\n\n";
7774
8164
  } else {
@@ -7813,11 +8203,11 @@
7813
8203
  references: [${sourceTableVar}.${sourceId}],
7814
8204
  relationName: "${owningRelationName}"
7815
8205
  })`);
7816
- const targetRelName = inverseRelationName ?? owningRelationName;
8206
+ const targetRelationName = inverseRelationName ? inverseRelationName : `${tableName}_${relation.through.targetColumn}`;
7817
8207
  tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {
7818
8208
  fields: [${tableVarName}.${relation.through.targetColumn}],
7819
8209
  references: [${targetTableVar}.${targetId}],
7820
- relationName: "${targetRelName}"
8210
+ relationName: "${targetRelationName}"
7821
8211
  })`);
7822
8212
  }
7823
8213
  } else {
@@ -7916,6 +8306,90 @@ ${tableRelations.join(",\n")}
7916
8306
  schemaContent += tablesExport + enumsExport + relationsExport;
7917
8307
  return schemaContent;
7918
8308
  };
8309
+ const defaultUsersCollection = {
8310
+ name: "Users",
8311
+ singularName: "User",
8312
+ slug: "users",
8313
+ table: "users",
8314
+ schema: "rebase",
8315
+ icon: "Users",
8316
+ group: "Settings",
8317
+ properties: {
8318
+ id: {
8319
+ name: "ID",
8320
+ type: "string",
8321
+ isId: "uuid"
8322
+ },
8323
+ email: {
8324
+ name: "Email",
8325
+ type: "string",
8326
+ validation: {
8327
+ required: true,
8328
+ unique: true
8329
+ }
8330
+ },
8331
+ password_hash: {
8332
+ name: "Password Hash",
8333
+ type: "string",
8334
+ ui: {
8335
+ hideFromCollection: true
8336
+ }
8337
+ },
8338
+ display_name: {
8339
+ name: "Display Name",
8340
+ type: "string"
8341
+ },
8342
+ photo_url: {
8343
+ name: "Photo URL",
8344
+ type: "string"
8345
+ },
8346
+ email_verified: {
8347
+ name: "Email Verified",
8348
+ type: "boolean",
8349
+ defaultValue: false
8350
+ },
8351
+ email_verification_token: {
8352
+ name: "Email Verification Token",
8353
+ type: "string",
8354
+ ui: {
8355
+ hideFromCollection: true
8356
+ }
8357
+ },
8358
+ email_verification_sent_at: {
8359
+ name: "Email Verification Sent At",
8360
+ type: "date",
8361
+ ui: {
8362
+ hideFromCollection: true
8363
+ }
8364
+ },
8365
+ metadata: {
8366
+ name: "Metadata",
8367
+ type: "map",
8368
+ defaultValue: {},
8369
+ ui: {
8370
+ hideFromCollection: true
8371
+ }
8372
+ },
8373
+ created_at: {
8374
+ name: "Created At",
8375
+ type: "date",
8376
+ autoValue: "on_create",
8377
+ ui: {
8378
+ readOnly: true,
8379
+ hideFromCollection: true
8380
+ }
8381
+ },
8382
+ updated_at: {
8383
+ name: "Updated At",
8384
+ type: "date",
8385
+ autoValue: "on_update",
8386
+ ui: {
8387
+ readOnly: true,
8388
+ hideFromCollection: true
8389
+ }
8390
+ }
8391
+ }
8392
+ };
7919
8393
  const formatTerminalText = (text, options = {}) => {
7920
8394
  let codes = "";
7921
8395
  if (options.bold) codes += "\x1B[1m";
@@ -7978,10 +8452,14 @@ ${tableRelations.join(",\n")}
7978
8452
  const imported = await dynamicImport(fileUrl);
7979
8453
  collections = imported.backendCollections || imported.collections;
7980
8454
  }
7981
- if (!collections || !Array.isArray(collections) || collections.length === 0) {
7982
- console.error("Error: Could not find collections array or failed to load directory.");
7983
- return;
8455
+ if (!collections || !Array.isArray(collections)) {
8456
+ collections = [];
7984
8457
  }
8458
+ const hasUsersCollection = collections.some((c) => c.slug === "users");
8459
+ if (!hasUsersCollection) {
8460
+ collections.push(defaultUsersCollection);
8461
+ }
8462
+ collections.sort((a, b) => a.slug.localeCompare(b.slug));
7985
8463
  const schemaContent = await generateSchema(collections);
7986
8464
  if (outputPath) {
7987
8465
  const outputDir = path.dirname(outputPath);
@@ -8209,29 +8687,14 @@ ${tableRelations.join(",\n")}
8209
8687
  },
8210
8688
  authContext
8211
8689
  });
8212
- let entities;
8213
- if (this.driver) {
8214
- entities = await this.driver.fetchCollection({
8215
- path: request.path,
8216
- collection,
8217
- filter: request.filter,
8218
- orderBy: request.orderBy,
8219
- order: request.order,
8220
- limit: request.limit,
8221
- startAfter: request.startAfter,
8222
- searchString: request.searchString
8223
- });
8224
- } else {
8225
- entities = await this.entityService.fetchCollection(request.path, {
8226
- filter: request.filter,
8227
- orderBy: request.orderBy,
8228
- order: request.order,
8229
- limit: request.limit,
8230
- startAfter: request.startAfter,
8231
- databaseId: request.collection?.databaseId,
8232
- searchString: request.searchString
8233
- });
8234
- }
8690
+ const entities = await this.fetchCollectionWithAuth(request.path, {
8691
+ filter: request.filter,
8692
+ orderBy: request.orderBy,
8693
+ order: request.order,
8694
+ limit: request.limit,
8695
+ startAfter: request.startAfter,
8696
+ searchString: request.searchString
8697
+ }, authContext);
8235
8698
  this.sendCollectionUpdate(clientId, subscriptionId, entities);
8236
8699
  } catch (error) {
8237
8700
  this.sendError(clientId, `Failed to subscribe to collection: ${error}`, subscriptionId);
@@ -8255,16 +8718,7 @@ ${tableRelations.join(",\n")}
8255
8718
  entityId: request.entityId,
8256
8719
  authContext
8257
8720
  });
8258
- let entity;
8259
- if (this.driver) {
8260
- entity = await this.driver.fetchEntity({
8261
- path: request.path,
8262
- entityId: request.entityId,
8263
- collection
8264
- });
8265
- } else {
8266
- entity = await this.entityService.fetchEntity(request.path, request.entityId, request.collection?.databaseId);
8267
- }
8721
+ const entity = await this.fetchEntityWithAuth(request.path, String(request.entityId), authContext);
8268
8722
  this.sendEntityUpdate(clientId, subscriptionId, entity || null);
8269
8723
  } catch (error) {
8270
8724
  this.sendError(clientId, `Failed to subscribe to entity: ${request.path} ${request.entityId} ${error}`, subscriptionId);
@@ -8409,87 +8863,77 @@ ${tableRelations.join(",\n")}
8409
8863
  async fetchCollectionWithAuth(notifyPath, collectionRequest, authContext) {
8410
8864
  if (this.driver) {
8411
8865
  const collection = this.registry.getCollectionByPath(notifyPath);
8412
- const fetchFn = async () => this.driver.fetchCollection({
8413
- path: notifyPath,
8414
- collection,
8415
- filter: collectionRequest.filter,
8416
- orderBy: collectionRequest.orderBy,
8417
- order: collectionRequest.order,
8418
- limit: collectionRequest.limit,
8419
- offset: collectionRequest.offset,
8420
- startAfter: collectionRequest.startAfter,
8421
- searchString: collectionRequest.searchString
8866
+ const activeAuth = authContext || {
8867
+ userId: "anon",
8868
+ roles: ["anon"]
8869
+ };
8870
+ return await this.db.transaction(async (tx) => {
8871
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
8872
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
8873
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
8874
+ sub: activeAuth.userId,
8875
+ roles: activeAuth.roles
8876
+ })}, true)`);
8877
+ const txEntityService = new EntityService(tx, this.registry);
8878
+ let fetchedEntities;
8879
+ if (collectionRequest.searchString) {
8880
+ fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8881
+ filter: collectionRequest.filter,
8882
+ orderBy: collectionRequest.orderBy,
8883
+ order: collectionRequest.order,
8884
+ limit: collectionRequest.limit,
8885
+ databaseId: collectionRequest.databaseId
8886
+ });
8887
+ } else {
8888
+ fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8889
+ filter: collectionRequest.filter,
8890
+ orderBy: collectionRequest.orderBy,
8891
+ order: collectionRequest.order,
8892
+ limit: collectionRequest.limit,
8893
+ offset: collectionRequest.offset,
8894
+ startAfter: collectionRequest.startAfter,
8895
+ databaseId: collectionRequest.databaseId
8896
+ });
8897
+ }
8898
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
8899
+ const resolvedCollection = collection ? {
8900
+ ...collection,
8901
+ ...registryCollection
8902
+ } : registryCollection;
8903
+ const callbacks = resolvedCollection?.callbacks;
8904
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8905
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8906
+ const contextForCallback = {
8907
+ user: {
8908
+ uid: activeAuth.userId,
8909
+ roles: activeAuth.roles
8910
+ },
8911
+ driver: this.driver,
8912
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
8913
+ };
8914
+ return await Promise.all(fetchedEntities.map(async (entity) => {
8915
+ let processedEntity = entity;
8916
+ if (callbacks?.afterRead) {
8917
+ processedEntity = await callbacks.afterRead({
8918
+ collection: resolvedCollection,
8919
+ path: notifyPath,
8920
+ entity: processedEntity,
8921
+ context: contextForCallback
8922
+ }) ?? processedEntity;
8923
+ }
8924
+ if (propertyCallbacks?.afterRead) {
8925
+ processedEntity = await propertyCallbacks.afterRead({
8926
+ collection: resolvedCollection,
8927
+ path: notifyPath,
8928
+ entity: processedEntity,
8929
+ context: contextForCallback
8930
+ }) ?? processedEntity;
8931
+ }
8932
+ return processedEntity;
8933
+ }));
8934
+ }
8935
+ return fetchedEntities;
8422
8936
  });
8423
- if (authContext) {
8424
- return await this.db.transaction(async (tx) => {
8425
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8426
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8427
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
8428
- sub: authContext.userId,
8429
- roles: authContext.roles
8430
- })}, true)`);
8431
- const txEntityService = new EntityService(tx, this.registry);
8432
- let fetchedEntities;
8433
- if (collectionRequest.searchString) {
8434
- fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
8435
- filter: collectionRequest.filter,
8436
- orderBy: collectionRequest.orderBy,
8437
- order: collectionRequest.order,
8438
- limit: collectionRequest.limit,
8439
- databaseId: collectionRequest.databaseId
8440
- });
8441
- } else {
8442
- fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
8443
- filter: collectionRequest.filter,
8444
- orderBy: collectionRequest.orderBy,
8445
- order: collectionRequest.order,
8446
- limit: collectionRequest.limit,
8447
- offset: collectionRequest.offset,
8448
- startAfter: collectionRequest.startAfter,
8449
- databaseId: collectionRequest.databaseId
8450
- });
8451
- }
8452
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8453
- const resolvedCollection = collection ? {
8454
- ...collection,
8455
- ...registryCollection
8456
- } : registryCollection;
8457
- const callbacks = resolvedCollection?.callbacks;
8458
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8459
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8460
- const contextForCallback = {
8461
- user: {
8462
- uid: authContext.userId,
8463
- roles: authContext.roles
8464
- },
8465
- driver: this.driver,
8466
- data: this.driver ? this.driver.data : void 0
8467
- };
8468
- return await Promise.all(fetchedEntities.map(async (entity) => {
8469
- let processedEntity = entity;
8470
- if (callbacks?.afterRead) {
8471
- processedEntity = await callbacks.afterRead({
8472
- collection: resolvedCollection,
8473
- path: notifyPath,
8474
- entity: processedEntity,
8475
- context: contextForCallback
8476
- }) ?? processedEntity;
8477
- }
8478
- if (propertyCallbacks?.afterRead) {
8479
- processedEntity = await propertyCallbacks.afterRead({
8480
- collection: resolvedCollection,
8481
- path: notifyPath,
8482
- entity: processedEntity,
8483
- context: contextForCallback
8484
- }) ?? processedEntity;
8485
- }
8486
- return processedEntity;
8487
- }));
8488
- }
8489
- return fetchedEntities;
8490
- });
8491
- }
8492
- return fetchFn();
8493
8937
  }
8494
8938
  if (collectionRequest.searchString) {
8495
8939
  return await this.entityService.searchEntities(notifyPath, collectionRequest.searchString, {
@@ -8553,60 +8997,56 @@ ${tableRelations.join(",\n")}
8553
8997
  async fetchEntityWithAuth(notifyPath, entityId, authContext) {
8554
8998
  if (this.driver) {
8555
8999
  const collection = this.registry.getCollectionByPath(notifyPath);
8556
- const fetchFn = async () => this.driver.fetchEntity({
8557
- path: notifyPath,
8558
- entityId,
8559
- collection
8560
- });
8561
- if (authContext) {
8562
- return await this.db.transaction(async (tx) => {
8563
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
8564
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
8565
- await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
8566
- sub: authContext.userId,
8567
- roles: authContext.roles
8568
- })}, true)`);
8569
- const txEntityService = new EntityService(tx, this.registry);
8570
- let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
8571
- if (processedEntity) {
8572
- const registryCollection = this.registry.getCollectionByPath(notifyPath);
8573
- const resolvedCollection = collection ? {
8574
- ...collection,
8575
- ...registryCollection
8576
- } : registryCollection;
8577
- const callbacks = resolvedCollection?.callbacks;
8578
- const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
8579
- if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
8580
- const contextForCallback = {
8581
- user: {
8582
- uid: authContext.userId,
8583
- roles: authContext.roles
8584
- },
8585
- driver: this.driver,
8586
- data: this.driver ? this.driver.data : void 0
8587
- };
8588
- if (callbacks?.afterRead) {
8589
- processedEntity = await callbacks.afterRead({
8590
- collection: resolvedCollection,
8591
- path: notifyPath,
8592
- entity: processedEntity,
8593
- context: contextForCallback
8594
- }) ?? processedEntity;
8595
- }
8596
- if (propertyCallbacks?.afterRead) {
8597
- processedEntity = await propertyCallbacks.afterRead({
8598
- collection: resolvedCollection,
8599
- path: notifyPath,
8600
- entity: processedEntity,
8601
- context: contextForCallback
8602
- }) ?? processedEntity;
8603
- }
9000
+ const activeAuth = authContext || {
9001
+ userId: "anon",
9002
+ roles: ["anon"]
9003
+ };
9004
+ return await this.db.transaction(async (tx) => {
9005
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
9006
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
9007
+ await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
9008
+ sub: activeAuth.userId,
9009
+ roles: activeAuth.roles
9010
+ })}, true)`);
9011
+ const txEntityService = new EntityService(tx, this.registry);
9012
+ let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
9013
+ if (processedEntity) {
9014
+ const registryCollection = this.registry.getCollectionByPath(notifyPath);
9015
+ const resolvedCollection = collection ? {
9016
+ ...collection,
9017
+ ...registryCollection
9018
+ } : registryCollection;
9019
+ const callbacks = resolvedCollection?.callbacks;
9020
+ const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
9021
+ if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
9022
+ const contextForCallback = {
9023
+ user: {
9024
+ uid: activeAuth.userId,
9025
+ roles: activeAuth.roles
9026
+ },
9027
+ driver: this.driver,
9028
+ data: this.driver && "data" in this.driver ? this.driver.data : void 0
9029
+ };
9030
+ if (callbacks?.afterRead) {
9031
+ processedEntity = await callbacks.afterRead({
9032
+ collection: resolvedCollection,
9033
+ path: notifyPath,
9034
+ entity: processedEntity,
9035
+ context: contextForCallback
9036
+ }) ?? processedEntity;
9037
+ }
9038
+ if (propertyCallbacks?.afterRead) {
9039
+ processedEntity = await propertyCallbacks.afterRead({
9040
+ collection: resolvedCollection,
9041
+ path: notifyPath,
9042
+ entity: processedEntity,
9043
+ context: contextForCallback
9044
+ }) ?? processedEntity;
8604
9045
  }
8605
9046
  }
8606
- return processedEntity;
8607
- });
8608
- }
8609
- return fetchFn();
9047
+ }
9048
+ return processedEntity;
9049
+ });
8610
9050
  }
8611
9051
  return await this.entityService.fetchEntity(notifyPath, entityId);
8612
9052
  }
@@ -8674,6 +9114,31 @@ ${tableRelations.join(",\n")}
8674
9114
  return parentPaths;
8675
9115
  }
8676
9116
  // =============================================================================
9117
+ // Lifecycle / Cleanup
9118
+ // =============================================================================
9119
+ /**
9120
+ * Gracefully tear down all realtime resources.
9121
+ *
9122
+ * This MUST be called during process shutdown, **before** `pool.end()`.
9123
+ * It ensures:
9124
+ * 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
9125
+ * 2. All subscription state and callbacks are cleared.
9126
+ * 3. The dedicated LISTEN client (outside the pool) is disconnected.
9127
+ * 4. All WebSocket clients are removed (but not forcefully closed — the
9128
+ * HTTP server close will handle that).
9129
+ */
9130
+ async destroy() {
9131
+ for (const [key, timer] of this.refetchTimers) {
9132
+ clearTimeout(timer);
9133
+ this.refetchTimers.delete(key);
9134
+ }
9135
+ this._subscriptions.clear();
9136
+ this.subscriptionCallbacks.clear();
9137
+ await this.stopListening();
9138
+ this.clients.clear();
9139
+ this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
9140
+ }
9141
+ // =============================================================================
8677
9142
  // Cross-Instance LISTEN/NOTIFY
8678
9143
  // =============================================================================
8679
9144
  /**
@@ -8814,8 +9279,23 @@ ${tableRelations.join(",\n")}
8814
9279
  const WS_RATE_LIMIT = 2e3;
8815
9280
  const WS_RATE_WINDOW_MS = 6e4;
8816
9281
  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"]);
9282
+ function extractErrorMessage(error) {
9283
+ if (!error) return "Unknown error";
9284
+ if (typeof error === "object") {
9285
+ const err = error;
9286
+ if (err.cause) {
9287
+ return extractErrorMessage(err.cause);
9288
+ }
9289
+ if (typeof err.message === "string") {
9290
+ return err.message;
9291
+ }
9292
+ }
9293
+ return String(error);
9294
+ }
8817
9295
  function isAdminSession(session) {
8818
- if (!session?.user?.roles) return false;
9296
+ if (!session?.user) return false;
9297
+ if (session.user.isAdmin) return true;
9298
+ if (!session.user.roles) return false;
8819
9299
  return session.user.roles.some((r) => {
8820
9300
  if (typeof r === "string") return r === "admin";
8821
9301
  if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
@@ -8823,7 +9303,7 @@ ${tableRelations.join(",\n")}
8823
9303
  return false;
8824
9304
  });
8825
9305
  }
8826
- function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
9306
+ function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
8827
9307
  const isProduction = process.env.NODE_ENV === "production";
8828
9308
  const wsDebug = (...args) => {
8829
9309
  if (!isProduction) console.debug(...args);
@@ -8837,7 +9317,7 @@ ${tableRelations.join(",\n")}
8837
9317
  }
8838
9318
  console.error("❌ [WebSocket Server] Error:", err);
8839
9319
  });
8840
- const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
9320
+ const requireAuth = authAdapter ? true : authConfig?.requireAuth !== false && !!authConfig?.jwtSecret;
8841
9321
  wss.on("connection", (ws2) => {
8842
9322
  const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
8843
9323
  wsDebug(`WebSocket client connected: ${clientId}`);
@@ -8882,11 +9362,37 @@ ${tableRelations.join(",\n")}
8882
9362
  sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
8883
9363
  return;
8884
9364
  }
8885
- const user = serverCore.extractUserFromToken(token);
8886
- if (user) {
9365
+ let verifiedUser = null;
9366
+ if (authAdapter) {
9367
+ try {
9368
+ const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", {
9369
+ headers: {
9370
+ Authorization: `Bearer ${token}`
9371
+ }
9372
+ }));
9373
+ if (adapterUser) {
9374
+ verifiedUser = {
9375
+ userId: adapterUser.uid,
9376
+ roles: adapterUser.roles,
9377
+ isAdmin: adapterUser.isAdmin
9378
+ };
9379
+ }
9380
+ } catch {
9381
+ }
9382
+ } else {
9383
+ const jwtPayload = serverCore.extractUserFromToken(token);
9384
+ if (jwtPayload) {
9385
+ verifiedUser = {
9386
+ userId: jwtPayload.userId,
9387
+ roles: jwtPayload.roles ?? [],
9388
+ isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
9389
+ };
9390
+ }
9391
+ }
9392
+ if (verifiedUser) {
8887
9393
  const session = clientSessions.get(clientId);
8888
9394
  if (session) {
8889
- session.user = user;
9395
+ session.user = verifiedUser;
8890
9396
  session.authenticated = true;
8891
9397
  }
8892
9398
  wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
@@ -8894,11 +9400,11 @@ ${tableRelations.join(",\n")}
8894
9400
  type: "AUTH_SUCCESS",
8895
9401
  requestId,
8896
9402
  payload: {
8897
- userId: user.userId,
8898
- roles: user.roles
9403
+ userId: verifiedUser.userId,
9404
+ roles: verifiedUser.roles
8899
9405
  }
8900
9406
  }));
8901
- wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${user.userId}`);
9407
+ wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.userId}`);
8902
9408
  } else {
8903
9409
  wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
8904
9410
  sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
@@ -8936,16 +9442,19 @@ ${tableRelations.join(",\n")}
8936
9442
  }
8937
9443
  const getScopedDelegate = async () => {
8938
9444
  const session = clientSessions.get(clientId);
8939
- if (session?.user && "withAuth" in driver && typeof driver.withAuth === "function") {
9445
+ if ("withAuth" in driver && typeof driver.withAuth === "function") {
8940
9446
  try {
8941
- const userForAuth = {
9447
+ const userForAuth = session?.user ? {
8942
9448
  uid: session.user.userId,
8943
9449
  roles: session.user.roles ?? []
9450
+ } : {
9451
+ uid: "anon",
9452
+ roles: ["anon"]
8944
9453
  };
8945
9454
  return await driver.withAuth(userForAuth);
8946
9455
  } catch (e) {
8947
- console.error("Failed to create authenticated delegate for WS request", e);
8948
- return driver;
9456
+ console.error("Failed to create RLS scoped delegate for WS request", e);
9457
+ throw new Error("Internal authentication error");
8949
9458
  }
8950
9459
  }
8951
9460
  return driver;
@@ -9076,24 +9585,29 @@ ${tableRelations.join(",\n")}
9076
9585
  sql,
9077
9586
  options
9078
9587
  } = payload;
9079
- const delegate = await getScopedDelegate();
9080
- const admin = delegate.admin;
9081
- if (!isSQLAdmin(admin)) {
9082
- sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9083
- break;
9084
- }
9085
- const result = await admin.executeSql(sql, options);
9086
- if (process.env.NODE_ENV !== "production") {
9087
- wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9588
+ try {
9589
+ const delegate = await getScopedDelegate();
9590
+ const admin = delegate.admin;
9591
+ if (!isSQLAdmin(admin)) {
9592
+ sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
9593
+ break;
9594
+ }
9595
+ const result = await admin.executeSql(sql, options);
9596
+ if (process.env.NODE_ENV !== "production") {
9597
+ wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
9598
+ }
9599
+ const response = {
9600
+ type: "EXECUTE_SQL_SUCCESS",
9601
+ payload: {
9602
+ result
9603
+ },
9604
+ requestId
9605
+ };
9606
+ ws2.send(JSON.stringify(response));
9607
+ } catch (sqlError) {
9608
+ const errMsg = extractErrorMessage(sqlError);
9609
+ sendError("ERROR", "SQL_ERROR", errMsg);
9088
9610
  }
9089
- const response = {
9090
- type: "EXECUTE_SQL_SUCCESS",
9091
- payload: {
9092
- result
9093
- },
9094
- requestId
9095
- };
9096
- ws2.send(JSON.stringify(response));
9097
9611
  }
9098
9612
  break;
9099
9613
  case "FETCH_DATABASES":
@@ -9272,7 +9786,10 @@ ${tableRelations.join(",\n")}
9272
9786
  const authContext = session?.user ? {
9273
9787
  userId: session.user.userId,
9274
9788
  roles: session.user.roles ?? []
9275
- } : void 0;
9789
+ } : {
9790
+ userId: "anon",
9791
+ roles: ["anon"]
9792
+ };
9276
9793
  await realtimeService.handleClientMessage(clientId, {
9277
9794
  type,
9278
9795
  payload,
@@ -9416,29 +9933,58 @@ ${tableRelations.join(",\n")}
9416
9933
  },
9417
9934
  config: null
9418
9935
  }];
9419
- async function ensureAuthTablesExist(db) {
9936
+ async function ensureAuthTablesExist(db, registry) {
9420
9937
  console.log("🔍 Checking auth tables...");
9421
9938
  try {
9939
+ let usersTableName = '"users"';
9940
+ let userIdType = "TEXT";
9941
+ let usersSchema2 = "public";
9942
+ if (registry) {
9943
+ const usersTable = registry.getTable("users");
9944
+ if (usersTable) {
9945
+ const {
9946
+ getTableName: getTableName2
9947
+ } = await import("drizzle-orm");
9948
+ usersSchema2 = pgCore.getTableConfig(usersTable).schema || "public";
9949
+ usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
9950
+ if (usersTable.id) {
9951
+ const col = usersTable.id;
9952
+ const meta = getColumnMeta(col);
9953
+ const columnType = meta.columnType;
9954
+ if (columnType === "PgUUID") {
9955
+ userIdType = "UUID";
9956
+ } else if (columnType === "PgSerial" || columnType === "PgInteger") {
9957
+ userIdType = "INTEGER";
9958
+ } else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
9959
+ userIdType = "BIGINT";
9960
+ }
9961
+ }
9962
+ }
9963
+ }
9964
+ let rolesSchema = "rebase";
9965
+ if (registry) {
9966
+ const rolesTable = registry.getTable("roles");
9967
+ if (rolesTable) {
9968
+ rolesSchema = pgCore.getTableConfig(rolesTable).schema || "public";
9969
+ }
9970
+ }
9971
+ if (usersSchema2 !== "public") {
9972
+ await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(usersSchema2)}`);
9973
+ }
9974
+ if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
9975
+ await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(rolesSchema)}`);
9976
+ }
9422
9977
  await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS rebase`);
9978
+ const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
9979
+ const rolesTableName = `"${rolesSchema}"."roles"`;
9980
+ const userRolesTableName = `"${rolesSchema}"."user_roles"`;
9981
+ const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
9982
+ const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
9983
+ const appConfigTableName = `"${rolesSchema}"."app_config"`;
9423
9984
  await db.execute(drizzleOrm.sql`
9424
- CREATE TABLE IF NOT EXISTS rebase.users (
9425
- id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9426
- email TEXT NOT NULL UNIQUE,
9427
- password_hash TEXT,
9428
- display_name TEXT,
9429
- photo_url TEXT,
9430
- created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
9431
- updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9432
- )
9433
- `);
9434
- await db.execute(drizzleOrm.sql`
9435
- CREATE INDEX IF NOT EXISTS idx_users_email
9436
- ON rebase.users(email)
9437
- `);
9438
- await db.execute(drizzleOrm.sql`
9439
- CREATE TABLE IF NOT EXISTS rebase.user_identities (
9985
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userIdentitiesTable)} (
9440
9986
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9441
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9987
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9442
9988
  provider TEXT NOT NULL,
9443
9989
  provider_id TEXT NOT NULL,
9444
9990
  profile_data JSONB,
@@ -9449,10 +9995,10 @@ ${tableRelations.join(",\n")}
9449
9995
  `);
9450
9996
  await db.execute(drizzleOrm.sql`
9451
9997
  CREATE INDEX IF NOT EXISTS idx_user_identities_user
9452
- ON rebase.user_identities(user_id)
9998
+ ON ${drizzleOrm.sql.raw(userIdentitiesTable)}(user_id)
9453
9999
  `);
9454
10000
  await db.execute(drizzleOrm.sql`
9455
- CREATE TABLE IF NOT EXISTS rebase.roles (
10001
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(rolesTableName)} (
9456
10002
  id TEXT PRIMARY KEY,
9457
10003
  name TEXT NOT NULL,
9458
10004
  is_admin BOOLEAN DEFAULT FALSE,
@@ -9463,20 +10009,20 @@ ${tableRelations.join(",\n")}
9463
10009
  )
9464
10010
  `);
9465
10011
  await db.execute(drizzleOrm.sql`
9466
- CREATE TABLE IF NOT EXISTS rebase.user_roles (
9467
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
9468
- role_id TEXT NOT NULL REFERENCES rebase.roles(id) ON DELETE CASCADE,
10012
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userRolesTableName)} (
10013
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
10014
+ role_id TEXT NOT NULL REFERENCES ${drizzleOrm.sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
9469
10015
  PRIMARY KEY (user_id, role_id)
9470
10016
  )
9471
10017
  `);
9472
10018
  await db.execute(drizzleOrm.sql`
9473
10019
  CREATE INDEX IF NOT EXISTS idx_user_roles_user
9474
- ON rebase.user_roles(user_id)
10020
+ ON ${drizzleOrm.sql.raw(userRolesTableName)}(user_id)
9475
10021
  `);
9476
10022
  await db.execute(drizzleOrm.sql`
9477
- CREATE TABLE IF NOT EXISTS rebase.refresh_tokens (
10023
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(refreshTokensTableName)} (
9478
10024
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9479
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
10025
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9480
10026
  token_hash TEXT NOT NULL UNIQUE,
9481
10027
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9482
10028
  user_agent TEXT,
@@ -9487,16 +10033,16 @@ ${tableRelations.join(",\n")}
9487
10033
  `);
9488
10034
  await db.execute(drizzleOrm.sql`
9489
10035
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash
9490
- ON rebase.refresh_tokens(token_hash)
10036
+ ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(token_hash)
9491
10037
  `);
9492
10038
  await db.execute(drizzleOrm.sql`
9493
10039
  CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user
9494
- ON rebase.refresh_tokens(user_id)
10040
+ ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(user_id)
9495
10041
  `);
9496
10042
  await db.execute(drizzleOrm.sql`
9497
- CREATE TABLE IF NOT EXISTS rebase.password_reset_tokens (
10043
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(passwordResetTokensTableName)} (
9498
10044
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
9499
- user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
10045
+ user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
9500
10046
  token_hash TEXT NOT NULL UNIQUE,
9501
10047
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
9502
10048
  used_at TIMESTAMP WITH TIME ZONE,
@@ -9505,20 +10051,19 @@ ${tableRelations.join(",\n")}
9505
10051
  `);
9506
10052
  await db.execute(drizzleOrm.sql`
9507
10053
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_hash
9508
- ON rebase.password_reset_tokens(token_hash)
10054
+ ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(token_hash)
9509
10055
  `);
9510
10056
  await db.execute(drizzleOrm.sql`
9511
10057
  CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
9512
- ON rebase.password_reset_tokens(user_id)
10058
+ ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(user_id)
9513
10059
  `);
9514
10060
  await db.execute(drizzleOrm.sql`
9515
- CREATE TABLE IF NOT EXISTS rebase.app_config (
10061
+ CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(appConfigTableName)} (
9516
10062
  key TEXT PRIMARY KEY,
9517
10063
  value JSONB NOT NULL,
9518
10064
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9519
10065
  )
9520
10066
  `);
9521
- await applyInternalMigrations(db);
9522
10067
  await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS auth`);
9523
10068
  await db.transaction(async (tx) => {
9524
10069
  await tx.execute(drizzleOrm.sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);
@@ -9541,15 +10086,15 @@ ${tableRelations.join(",\n")}
9541
10086
  $$ LANGUAGE sql STABLE
9542
10087
  `);
9543
10088
  });
9544
- await seedDefaultRoles(db);
10089
+ await seedDefaultRoles(db, rolesTableName);
9545
10090
  console.log("✅ Auth tables ready");
9546
10091
  } catch (error) {
9547
10092
  console.error("❌ Failed to create auth tables:", error);
9548
10093
  console.warn("⚠️ Continuing without creating auth tables.");
9549
10094
  }
9550
10095
  }
9551
- async function seedDefaultRoles(db) {
9552
- const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM rebase.roles`);
10096
+ async function seedDefaultRoles(db, rolesTableName) {
10097
+ const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM ${drizzleOrm.sql.raw(rolesTableName)}`);
9553
10098
  const count = parseInt(result.rows[0]?.count || "0", 10);
9554
10099
  if (count > 0) {
9555
10100
  console.log(`📋 Found ${count} existing roles`);
@@ -9558,7 +10103,7 @@ ${tableRelations.join(",\n")}
9558
10103
  console.log("🌱 Seeding default roles...");
9559
10104
  for (const role of DEFAULT_ROLES) {
9560
10105
  await db.execute(drizzleOrm.sql`
9561
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, config)
10106
+ INSERT INTO ${drizzleOrm.sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
9562
10107
  VALUES (
9563
10108
  ${role.id},
9564
10109
  ${role.name},
@@ -9571,142 +10116,156 @@ ${tableRelations.join(",\n")}
9571
10116
  }
9572
10117
  console.log("✅ Default roles created: admin, editor, viewer");
9573
10118
  }
9574
- async function applyInternalMigrations(db) {
9575
- try {
9576
- await db.execute(drizzleOrm.sql`
9577
- ALTER TABLE rebase.users
9578
- ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT FALSE,
9579
- ADD COLUMN IF NOT EXISTS email_verification_token TEXT,
9580
- ADD COLUMN IF NOT EXISTS email_verification_sent_at TIMESTAMP WITH TIME ZONE
9581
- `);
9582
- const columnsCheck = await db.execute(drizzleOrm.sql`
9583
- SELECT column_name
9584
- FROM information_schema.columns
9585
- WHERE table_schema='rebase' AND table_name='users' AND column_name IN ('google_id', 'linkedin_id', 'provider')
9586
- `);
9587
- const existingColumns = columnsCheck.rows.map((r) => r.column_name);
9588
- if (existingColumns.includes("google_id")) {
9589
- await db.execute(drizzleOrm.sql`
9590
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9591
- SELECT id, 'google', google_id
9592
- FROM rebase.users
9593
- WHERE google_id IS NOT NULL
9594
- ON CONFLICT (provider, provider_id) DO NOTHING
9595
- `);
9596
- }
9597
- if (existingColumns.includes("linkedin_id")) {
9598
- await db.execute(drizzleOrm.sql`
9599
- INSERT INTO rebase.user_identities (user_id, provider, provider_id)
9600
- SELECT id, 'linkedin', linkedin_id
9601
- FROM rebase.users
9602
- WHERE linkedin_id IS NOT NULL
9603
- ON CONFLICT (provider, provider_id) DO NOTHING
9604
- `);
9605
- }
9606
- if (existingColumns.length > 0) {
9607
- await db.execute(drizzleOrm.sql`
9608
- ALTER TABLE rebase.users
9609
- DROP COLUMN IF EXISTS provider,
9610
- DROP COLUMN IF EXISTS google_id,
9611
- DROP COLUMN IF EXISTS linkedin_id
9612
- `);
9613
- await db.execute(drizzleOrm.sql`DROP INDEX IF EXISTS rebase.idx_users_google_id`);
9614
- await db.execute(drizzleOrm.sql`DROP INDEX IF EXISTS rebase.idx_users_linkedin_id`);
9615
- console.log("✅ Migrated to user_identities and dropped legacy columns.");
9616
- }
9617
- await db.execute(drizzleOrm.sql`
9618
- ALTER TABLE rebase.roles
9619
- ADD COLUMN IF NOT EXISTS collection_permissions JSONB
9620
- `);
9621
- await db.execute(drizzleOrm.sql`
9622
- ALTER TABLE rebase.refresh_tokens
9623
- ADD COLUMN IF NOT EXISTS user_agent TEXT,
9624
- ADD COLUMN IF NOT EXISTS ip_address TEXT
9625
- `);
9626
- const constraintCheck = await db.execute(drizzleOrm.sql`
9627
- SELECT 1 FROM information_schema.table_constraints
9628
- WHERE constraint_name = 'unique_device_session'
9629
- AND table_schema = 'rebase'
9630
- AND table_name = 'refresh_tokens'
9631
- `);
9632
- if (constraintCheck.rows.length === 0) {
9633
- try {
9634
- await db.execute(drizzleOrm.sql`
9635
- ALTER TABLE rebase.refresh_tokens
9636
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9637
- `);
9638
- console.log("✅ Added unique_device_session constraint");
9639
- } catch (e) {
9640
- const errorMessage = e instanceof Error ? e.message : String(e);
9641
- if (errorMessage.includes("could not create unique index")) {
9642
- console.warn("⚠️ Duplicate sessions found, cleaning up before adding constraint...");
9643
- await db.execute(drizzleOrm.sql`
9644
- DELETE FROM rebase.refresh_tokens a
9645
- USING rebase.refresh_tokens b
9646
- WHERE a.user_id = b.user_id
9647
- AND COALESCE(a.user_agent, '') = COALESCE(b.user_agent, '')
9648
- AND COALESCE(a.ip_address, '') = COALESCE(b.ip_address, '')
9649
- AND a.created_at < b.created_at
9650
- `);
9651
- await db.execute(drizzleOrm.sql`
9652
- ALTER TABLE rebase.refresh_tokens
9653
- ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
9654
- `).catch((retryErr) => {
9655
- const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
9656
- console.error("Failed to add unique_device_session constraint after cleanup:", retryMessage);
9657
- });
9658
- } else {
9659
- console.error("Constraint migration issue:", errorMessage);
9660
- }
9661
- }
9662
- }
9663
- } catch (error) {
9664
- console.error("❌ Failed to run internal migrations:", error);
10119
+ function getColumnKey(table, ...keys2) {
10120
+ if (!table) return void 0;
10121
+ for (const key of keys2) {
10122
+ if (key in table) return key;
10123
+ const snake = toSnakeCase(key);
10124
+ if (snake in table) return snake;
10125
+ const camel = camelCase(key);
10126
+ if (camel in table) return camel;
9665
10127
  }
10128
+ return void 0;
10129
+ }
10130
+ function getColumn(table, ...keys2) {
10131
+ if (!table) return void 0;
10132
+ const key = getColumnKey(table, ...keys2);
10133
+ return key ? table[key] : void 0;
9666
10134
  }
9667
10135
  class UserService {
9668
- constructor(db) {
10136
+ constructor(db, tableOrTables) {
9669
10137
  this.db = db;
10138
+ if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
10139
+ const tables = tableOrTables;
10140
+ this.usersTable = tables.users || users;
10141
+ this.userIdentitiesTable = tables.userIdentities || userIdentities;
10142
+ this.userRolesTable = tables.userRoles || userRoles;
10143
+ this.rolesTable = tables.roles || roles;
10144
+ } else {
10145
+ const table = tableOrTables;
10146
+ this.usersTable = table || users;
10147
+ this.userIdentitiesTable = userIdentities;
10148
+ this.userRolesTable = userRoles;
10149
+ this.rolesTable = roles;
10150
+ }
10151
+ }
10152
+ usersTable;
10153
+ userIdentitiesTable;
10154
+ userRolesTable;
10155
+ rolesTable;
10156
+ getQualifiedUsersTableName() {
10157
+ const name = drizzleOrm.getTableName(this.usersTable);
10158
+ const schema = pgCore.getTableConfig(this.usersTable).schema || "public";
10159
+ return `"${schema}"."${name}"`;
10160
+ }
10161
+ mapRowToUser(row) {
10162
+ if (!row) return row;
10163
+ const id = row.id ?? row.uid;
10164
+ const email = row.email;
10165
+ const passwordHash = row.password_hash ?? row.passwordHash ?? null;
10166
+ const displayName = row.display_name ?? row.displayName ?? null;
10167
+ const photoUrl = row.photo_url ?? row.photoUrl ?? row.photoURL ?? null;
10168
+ const emailVerified = row.email_verified ?? row.emailVerified ?? false;
10169
+ const emailVerificationToken = row.email_verification_token ?? row.emailVerificationToken ?? null;
10170
+ const emailVerificationSentAt = row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null;
10171
+ const createdAt = row.created_at ?? row.createdAt;
10172
+ const updatedAt = row.updated_at ?? row.updatedAt;
10173
+ const metadata = {
10174
+ ...row.metadata || {}
10175
+ };
10176
+ 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"]);
10177
+ for (const [key, val] of Object.entries(row)) {
10178
+ if (!knownKeys.has(key)) {
10179
+ const camelKey = camelCase(key);
10180
+ metadata[camelKey] = val;
10181
+ }
10182
+ }
10183
+ return {
10184
+ id,
10185
+ email,
10186
+ passwordHash,
10187
+ displayName,
10188
+ photoUrl,
10189
+ emailVerified,
10190
+ emailVerificationToken,
10191
+ emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
10192
+ createdAt: createdAt ? new Date(createdAt) : /* @__PURE__ */ new Date(),
10193
+ updatedAt: updatedAt ? new Date(updatedAt) : /* @__PURE__ */ new Date(),
10194
+ metadata
10195
+ };
10196
+ }
10197
+ mapPayload(data) {
10198
+ if (!data) return {};
10199
+ const payload = {};
10200
+ const idKey = getColumnKey(this.usersTable, "id") || "id";
10201
+ const emailKey = getColumnKey(this.usersTable, "email") || "email";
10202
+ const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10203
+ const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
10204
+ const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
10205
+ const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10206
+ const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10207
+ const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10208
+ const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
10209
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10210
+ const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
10211
+ if ("id" in data) payload[idKey] = data.id;
10212
+ if ("email" in data) payload[emailKey] = data.email;
10213
+ if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
10214
+ if ("displayName" in data) payload[displayNameKey] = data.displayName;
10215
+ if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
10216
+ if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
10217
+ if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
10218
+ if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
10219
+ if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
10220
+ if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
10221
+ const metadata = {
10222
+ ...data.metadata || {}
10223
+ };
10224
+ const remainingMetadata = {};
10225
+ for (const [key, val] of Object.entries(metadata)) {
10226
+ const tableColKey = getColumnKey(this.usersTable, key);
10227
+ if (tableColKey && tableColKey !== idKey && tableColKey !== emailKey && tableColKey !== passwordHashKey && tableColKey !== displayNameKey && tableColKey !== photoUrlKey && tableColKey !== emailVerifiedKey && tableColKey !== emailVerificationTokenKey && tableColKey !== emailVerificationSentAtKey && tableColKey !== createdAtKey && tableColKey !== updatedAtKey && tableColKey !== metadataKey) {
10228
+ payload[tableColKey] = val;
10229
+ } else {
10230
+ remainingMetadata[key] = val;
10231
+ }
10232
+ }
10233
+ if (metadataKey in this.usersTable) {
10234
+ payload[metadataKey] = remainingMetadata;
10235
+ }
10236
+ return payload;
9670
10237
  }
9671
10238
  async createUser(data) {
9672
- const [user] = await this.db.insert(users).values(data).returning();
9673
- return user;
10239
+ const payload = this.mapPayload(data);
10240
+ const [row] = await this.db.insert(this.usersTable).values(payload).returning();
10241
+ return this.mapRowToUser(row);
9674
10242
  }
9675
10243
  async getUserById(id) {
9676
- const [user] = await this.db.select().from(users).where(drizzleOrm.eq(users.id, id));
9677
- return user || null;
10244
+ const idCol = getColumn(this.usersTable, "id");
10245
+ if (!idCol) return null;
10246
+ const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(idCol, id));
10247
+ return row ? this.mapRowToUser(row) : null;
9678
10248
  }
9679
10249
  async getUserByEmail(email) {
9680
- const [user] = await this.db.select().from(users).where(drizzleOrm.eq(users.email, email.toLowerCase()));
9681
- return user || null;
10250
+ const emailCol = getColumn(this.usersTable, "email");
10251
+ if (!emailCol) return null;
10252
+ const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(emailCol, email.toLowerCase()));
10253
+ return row ? this.mapRowToUser(row) : null;
9682
10254
  }
9683
10255
  async getUserByIdentity(provider, providerId) {
9684
- const result = await this.db.execute(drizzleOrm.sql`
9685
- SELECT u.*
9686
- FROM rebase.users u
9687
- INNER JOIN rebase.user_identities ui ON u.id = ui.user_id
9688
- WHERE ui.provider = ${provider} AND ui.provider_id = ${providerId}
9689
- LIMIT 1
9690
- `);
9691
- if (result.rows.length === 0) return null;
9692
- const row = result.rows[0];
9693
- return {
9694
- id: row.id,
9695
- email: row.email,
9696
- passwordHash: row.password_hash ?? null,
9697
- displayName: row.display_name ?? null,
9698
- photoUrl: row.photo_url ?? null,
9699
- emailVerified: row.email_verified ?? false,
9700
- emailVerificationToken: row.email_verification_token ?? null,
9701
- emailVerificationSentAt: row.email_verification_sent_at ?? null,
9702
- createdAt: row.created_at,
9703
- updatedAt: row.updated_at
9704
- };
10256
+ const userIdCol = getColumn(this.usersTable, "id");
10257
+ if (!userIdCol) return null;
10258
+ const result = await this.db.select({
10259
+ user: this.usersTable
10260
+ }).from(this.usersTable).innerJoin(this.userIdentitiesTable, drizzleOrm.eq(userIdCol, this.userIdentitiesTable.userId)).where(drizzleOrm.sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`).limit(1);
10261
+ if (result.length === 0) return null;
10262
+ return this.mapRowToUser(result[0].user);
9705
10263
  }
9706
10264
  async getUserIdentities(userId) {
10265
+ const schema = pgCore.getTableConfig(this.userIdentitiesTable).schema || "public";
9707
10266
  const result = await this.db.execute(drizzleOrm.sql`
9708
10267
  SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
9709
- FROM rebase.user_identities
10268
+ FROM ${drizzleOrm.sql.raw(`"${schema}"."user_identities"`)}
9710
10269
  WHERE user_id = ${userId}
9711
10270
  `);
9712
10271
  return result.rows.map((row) => ({
@@ -9720,27 +10279,32 @@ ${tableRelations.join(",\n")}
9720
10279
  }));
9721
10280
  }
9722
10281
  async linkUserIdentity(userId, provider, providerId, profileData) {
9723
- await this.db.insert(userIdentities).values({
10282
+ await this.db.insert(this.userIdentitiesTable).values({
9724
10283
  userId,
9725
10284
  provider,
9726
10285
  providerId,
9727
10286
  profileData: profileData || null
9728
10287
  }).onConflictDoNothing({
9729
- target: [userIdentities.provider, userIdentities.providerId]
10288
+ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId]
9730
10289
  });
9731
10290
  }
9732
10291
  async updateUser(id, data) {
9733
- const [user] = await this.db.update(users).set({
9734
- ...data,
9735
- updatedAt: /* @__PURE__ */ new Date()
9736
- }).where(drizzleOrm.eq(users.id, id)).returning();
9737
- return user || null;
10292
+ const idCol = getColumn(this.usersTable, "id");
10293
+ if (!idCol) return null;
10294
+ const payload = this.mapPayload(data);
10295
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10296
+ payload[updatedAtKey] = /* @__PURE__ */ new Date();
10297
+ const [row] = await this.db.update(this.usersTable).set(payload).where(drizzleOrm.eq(idCol, id)).returning();
10298
+ return row ? this.mapRowToUser(row) : null;
9738
10299
  }
9739
10300
  async deleteUser(id) {
9740
- await this.db.delete(users).where(drizzleOrm.eq(users.id, id));
10301
+ const idCol = getColumn(this.usersTable, "id");
10302
+ if (!idCol) return;
10303
+ await this.db.delete(this.usersTable).where(drizzleOrm.eq(idCol, id));
9741
10304
  }
9742
10305
  async listUsers() {
9743
- return this.db.select().from(users);
10306
+ const rows = await this.db.select().from(this.usersTable);
10307
+ return rows.map((row) => this.mapRowToUser(row));
9744
10308
  }
9745
10309
  async listUsersPaginated(options) {
9746
10310
  const limit = options?.limit ?? 25;
@@ -9749,49 +10313,40 @@ ${tableRelations.join(",\n")}
9749
10313
  const orderBy = options?.orderBy || "createdAt";
9750
10314
  const orderDir = options?.orderDir || "desc";
9751
10315
  const roleId = options?.roleId;
9752
- const columnMap = {
9753
- email: "email",
9754
- displayName: "display_name",
9755
- createdAt: "created_at",
9756
- updatedAt: "updated_at",
9757
- provider: "provider"
9758
- };
9759
- const orderColumn = columnMap[orderBy] || "created_at";
10316
+ const orderCol = getColumn(this.usersTable, orderBy);
10317
+ const orderColumn = orderCol ? orderCol.name : "created_at";
9760
10318
  const direction = orderDir === "asc" ? drizzleOrm.sql`ASC` : drizzleOrm.sql`DESC`;
10319
+ const emailCol = getColumn(this.usersTable, "email");
10320
+ const emailColumn = emailCol ? emailCol.name : "email";
10321
+ const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
10322
+ const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
10323
+ const idCol = getColumn(this.usersTable, "id");
10324
+ const idColumn = idCol ? idCol.name : "id";
10325
+ const usersTableName = this.getQualifiedUsersTableName();
10326
+ const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
9761
10327
  const conditions = [];
9762
10328
  if (roleId) {
9763
- conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM rebase.user_roles ur WHERE ur.user_id = users.id AND ur.role_id = ${roleId})`);
10329
+ conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
9764
10330
  }
9765
10331
  if (search) {
9766
10332
  const pattern = `%${search}%`;
9767
- conditions.push(drizzleOrm.sql`(email ILIKE ${pattern} OR display_name ILIKE ${pattern})`);
10333
+ conditions.push(drizzleOrm.sql`(${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(emailColumn)} ILIKE ${pattern} OR ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(displayNameColumn)} ILIKE ${pattern})`);
9768
10334
  }
9769
10335
  const whereClause = conditions.length > 0 ? drizzleOrm.sql`WHERE ${drizzleOrm.sql.join(conditions, drizzleOrm.sql` AND `)}` : drizzleOrm.sql``;
9770
- const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY (SELECT count(*) FROM rebase.user_roles ur WHERE ur.user_id = users.id) DESC, ${drizzleOrm.sql.raw(orderColumn)} ${direction}`;
10336
+ const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY (SELECT count(*) FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)}) DESC, ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}`;
9771
10337
  const countResult = await this.db.execute(drizzleOrm.sql`
9772
- SELECT count(*)::int as total FROM rebase.users
10338
+ SELECT count(*)::int as total FROM ${drizzleOrm.sql.raw(usersTableName)}
9773
10339
  ${whereClause}
9774
10340
  `);
9775
10341
  const total = countResult.rows[0].total;
9776
10342
  const dataResult = await this.db.execute(drizzleOrm.sql`
9777
- SELECT * FROM rebase.users
10343
+ SELECT * FROM ${drizzleOrm.sql.raw(usersTableName)}
9778
10344
  ${whereClause}
9779
10345
  ${orderByClause}
9780
10346
  LIMIT ${limit} OFFSET ${offset}
9781
10347
  `);
9782
10348
  const rows = dataResult.rows;
9783
- const mappedUsers = rows.map((row) => ({
9784
- id: row.id,
9785
- email: row.email,
9786
- passwordHash: row.password_hash ?? row.passwordHash ?? null,
9787
- displayName: row.display_name ?? row.displayName ?? null,
9788
- photoUrl: row.photo_url ?? row.photoUrl ?? null,
9789
- emailVerified: row.email_verified ?? row.emailVerified ?? false,
9790
- emailVerificationToken: row.email_verification_token ?? row.emailVerificationToken ?? null,
9791
- emailVerificationSentAt: row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null,
9792
- createdAt: row.created_at ?? row.createdAt,
9793
- updatedAt: row.updated_at ?? row.updatedAt
9794
- }));
10349
+ const mappedUsers = rows.map((row) => this.mapRowToUser(row));
9795
10350
  return {
9796
10351
  users: mappedUsers,
9797
10352
  total,
@@ -9803,46 +10358,63 @@ ${tableRelations.join(",\n")}
9803
10358
  * Update user's password hash
9804
10359
  */
9805
10360
  async updatePassword(id, passwordHash) {
9806
- await this.db.update(users).set({
9807
- passwordHash,
9808
- updatedAt: /* @__PURE__ */ new Date()
9809
- }).where(drizzleOrm.eq(users.id, id));
10361
+ const idCol = getColumn(this.usersTable, "id");
10362
+ if (!idCol) return;
10363
+ const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
10364
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10365
+ await this.db.update(this.usersTable).set({
10366
+ [passwordHashColKey]: passwordHash,
10367
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10368
+ }).where(drizzleOrm.eq(idCol, id));
9810
10369
  }
9811
10370
  /**
9812
10371
  * Set email verification status
9813
10372
  */
9814
10373
  async setEmailVerified(id, verified) {
9815
- await this.db.update(users).set({
9816
- emailVerified: verified,
9817
- emailVerificationToken: null,
9818
- updatedAt: /* @__PURE__ */ new Date()
9819
- }).where(drizzleOrm.eq(users.id, id));
10374
+ const idCol = getColumn(this.usersTable, "id");
10375
+ if (!idCol) return;
10376
+ const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
10377
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10378
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10379
+ await this.db.update(this.usersTable).set({
10380
+ [emailVerifiedColKey]: verified,
10381
+ [emailVerificationTokenColKey]: null,
10382
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10383
+ }).where(drizzleOrm.eq(idCol, id));
9820
10384
  }
9821
10385
  /**
9822
10386
  * Set email verification token
9823
10387
  */
9824
10388
  async setVerificationToken(id, token) {
9825
- await this.db.update(users).set({
9826
- emailVerificationToken: token,
9827
- emailVerificationSentAt: token ? /* @__PURE__ */ new Date() : null,
9828
- updatedAt: /* @__PURE__ */ new Date()
9829
- }).where(drizzleOrm.eq(users.id, id));
10389
+ const idCol = getColumn(this.usersTable, "id");
10390
+ if (!idCol) return;
10391
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
10392
+ const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
10393
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
10394
+ await this.db.update(this.usersTable).set({
10395
+ [emailVerificationTokenColKey]: token,
10396
+ [emailVerificationSentAtColKey]: token ? /* @__PURE__ */ new Date() : null,
10397
+ [updatedAtColKey]: /* @__PURE__ */ new Date()
10398
+ }).where(drizzleOrm.eq(idCol, id));
9830
10399
  }
9831
10400
  /**
9832
10401
  * Find user by email verification token
9833
10402
  */
9834
10403
  async getUserByVerificationToken(token) {
9835
- const [user] = await this.db.select().from(users).where(drizzleOrm.eq(users.emailVerificationToken, token));
9836
- return user || null;
10404
+ const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
10405
+ if (!tokenCol) return null;
10406
+ const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(tokenCol, token));
10407
+ return row ? this.mapRowToUser(row) : null;
9837
10408
  }
9838
10409
  /**
9839
10410
  * Get roles for a user from database
9840
10411
  */
9841
10412
  async getUserRoles(userId) {
10413
+ const rolesSchema = pgCore.getTableConfig(this.rolesTable).schema || "public";
9842
10414
  const result = await this.db.execute(drizzleOrm.sql`
9843
10415
  SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
9844
- FROM rebase.roles r
9845
- INNER JOIN rebase.user_roles ur ON r.id = ur.role_id
10416
+ FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."roles"`)} r
10417
+ INNER JOIN ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
9846
10418
  WHERE ur.user_id = ${userId}
9847
10419
  `);
9848
10420
  return result.rows.map((row) => ({
@@ -9865,10 +10437,11 @@ ${tableRelations.join(",\n")}
9865
10437
  * Set roles for a user
9866
10438
  */
9867
10439
  async setUserRoles(userId, roleIds) {
9868
- await this.db.execute(drizzleOrm.sql`DELETE FROM rebase.user_roles WHERE user_id = ${userId}`);
10440
+ const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
10441
+ await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
9869
10442
  for (const roleId of roleIds) {
9870
10443
  await this.db.execute(drizzleOrm.sql`
9871
- INSERT INTO rebase.user_roles (user_id, role_id)
10444
+ INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9872
10445
  VALUES (${userId}, ${roleId})
9873
10446
  ON CONFLICT DO NOTHING
9874
10447
  `);
@@ -9878,8 +10451,9 @@ ${tableRelations.join(",\n")}
9878
10451
  * Assign a specific role to new user
9879
10452
  */
9880
10453
  async assignDefaultRole(userId, roleId) {
10454
+ const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
9881
10455
  await this.db.execute(drizzleOrm.sql`
9882
- INSERT INTO rebase.user_roles (user_id, role_id)
10456
+ INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
9883
10457
  VALUES (${userId}, ${roleId})
9884
10458
  ON CONFLICT DO NOTHING
9885
10459
  `);
@@ -9898,13 +10472,25 @@ ${tableRelations.join(",\n")}
9898
10472
  }
9899
10473
  }
9900
10474
  class RoleService {
9901
- constructor(db) {
10475
+ constructor(db, tableOrTables) {
9902
10476
  this.db = db;
10477
+ if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
10478
+ this.rolesTable = tableOrTables.roles || roles;
10479
+ } else {
10480
+ this.rolesTable = tableOrTables || roles;
10481
+ }
10482
+ }
10483
+ rolesTable;
10484
+ getQualifiedRolesTableName() {
10485
+ const name = drizzleOrm.getTableName(this.rolesTable);
10486
+ const schema = pgCore.getTableConfig(this.rolesTable).schema || "public";
10487
+ return `"${schema}"."${name}"`;
9903
10488
  }
9904
10489
  async getRoleById(id) {
10490
+ const tableName = this.getQualifiedRolesTableName();
9905
10491
  const result = await this.db.execute(drizzleOrm.sql`
9906
10492
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9907
- FROM rebase.roles
10493
+ FROM ${drizzleOrm.sql.raw(tableName)}
9908
10494
  WHERE id = ${id}
9909
10495
  `);
9910
10496
  if (result.rows.length === 0) return null;
@@ -9919,9 +10505,10 @@ ${tableRelations.join(",\n")}
9919
10505
  };
9920
10506
  }
9921
10507
  async listRoles() {
10508
+ const tableName = this.getQualifiedRolesTableName();
9922
10509
  const result = await this.db.execute(drizzleOrm.sql`
9923
10510
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
9924
- FROM rebase.roles
10511
+ FROM ${drizzleOrm.sql.raw(tableName)}
9925
10512
  ORDER BY name
9926
10513
  `);
9927
10514
  return result.rows.map((row) => ({
@@ -9934,8 +10521,9 @@ ${tableRelations.join(",\n")}
9934
10521
  }));
9935
10522
  }
9936
10523
  async createRole(data) {
10524
+ const tableName = this.getQualifiedRolesTableName();
9937
10525
  const result = await this.db.execute(drizzleOrm.sql`
9938
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, collection_permissions, config)
10526
+ INSERT INTO ${drizzleOrm.sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
9939
10527
  VALUES (
9940
10528
  ${data.id},
9941
10529
  ${data.name},
@@ -9959,8 +10547,9 @@ ${tableRelations.join(",\n")}
9959
10547
  async updateRole(id, data) {
9960
10548
  const existing = await this.getRoleById(id);
9961
10549
  if (!existing) return null;
10550
+ const tableName = this.getQualifiedRolesTableName();
9962
10551
  await this.db.execute(drizzleOrm.sql`
9963
- UPDATE rebase.roles
10552
+ UPDATE ${drizzleOrm.sql.raw(tableName)}
9964
10553
  SET
9965
10554
  name = ${data.name ?? existing.name},
9966
10555
  is_admin = ${data.isAdmin ?? existing.isAdmin},
@@ -9972,23 +10561,36 @@ ${tableRelations.join(",\n")}
9972
10561
  return this.getRoleById(id);
9973
10562
  }
9974
10563
  async deleteRole(id) {
9975
- await this.db.execute(drizzleOrm.sql`DELETE FROM rebase.roles WHERE id = ${id}`);
10564
+ const tableName = this.getQualifiedRolesTableName();
10565
+ await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(tableName)} WHERE id = ${id}`);
9976
10566
  }
9977
10567
  }
9978
10568
  class RefreshTokenService {
9979
- constructor(db) {
10569
+ constructor(db, tableOrTables) {
9980
10570
  this.db = db;
10571
+ if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) {
10572
+ this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
10573
+ } else {
10574
+ this.refreshTokensTable = tableOrTables || refreshTokens;
10575
+ }
10576
+ }
10577
+ refreshTokensTable;
10578
+ getQualifiedRefreshTokensTableName() {
10579
+ const name = drizzleOrm.getTableName(this.refreshTokensTable);
10580
+ const schema = pgCore.getTableConfig(this.refreshTokensTable).schema || "public";
10581
+ return `"${schema}"."${name}"`;
9981
10582
  }
9982
10583
  async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
9983
10584
  const safeUserAgent = userAgent || "";
9984
10585
  const safeIpAddress = ipAddress || "";
10586
+ const tableName = this.getQualifiedRefreshTokensTableName();
9985
10587
  await this.db.execute(drizzleOrm.sql`
9986
- DELETE FROM rebase.refresh_tokens
10588
+ DELETE FROM ${drizzleOrm.sql.raw(tableName)}
9987
10589
  WHERE user_id = ${userId}
9988
10590
  AND user_agent = ${safeUserAgent}
9989
10591
  AND ip_address = ${safeIpAddress}
9990
10592
  `);
9991
- await this.db.insert(refreshTokens).values({
10593
+ await this.db.insert(this.refreshTokensTable).values({
9992
10594
  userId,
9993
10595
  tokenHash,
9994
10596
  expiresAt,
@@ -9998,51 +10600,63 @@ ${tableRelations.join(",\n")}
9998
10600
  }
9999
10601
  async findByHash(tokenHash) {
10000
10602
  const [token] = await this.db.select({
10001
- id: refreshTokens.id,
10002
- userId: refreshTokens.userId,
10003
- tokenHash: refreshTokens.tokenHash,
10004
- expiresAt: refreshTokens.expiresAt,
10005
- createdAt: refreshTokens.createdAt,
10006
- userAgent: refreshTokens.userAgent,
10007
- ipAddress: refreshTokens.ipAddress
10008
- }).from(refreshTokens).where(drizzleOrm.eq(refreshTokens.tokenHash, tokenHash));
10603
+ id: this.refreshTokensTable.id,
10604
+ userId: this.refreshTokensTable.userId,
10605
+ tokenHash: this.refreshTokensTable.tokenHash,
10606
+ expiresAt: this.refreshTokensTable.expiresAt,
10607
+ createdAt: this.refreshTokensTable.createdAt,
10608
+ userAgent: this.refreshTokensTable.userAgent,
10609
+ ipAddress: this.refreshTokensTable.ipAddress
10610
+ }).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
10009
10611
  return token || null;
10010
10612
  }
10011
10613
  async deleteByHash(tokenHash) {
10012
- await this.db.delete(refreshTokens).where(drizzleOrm.eq(refreshTokens.tokenHash, tokenHash));
10614
+ await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
10013
10615
  }
10014
10616
  async deleteAllForUser(userId) {
10015
- await this.db.delete(refreshTokens).where(drizzleOrm.eq(refreshTokens.userId, userId));
10617
+ await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId));
10016
10618
  }
10017
10619
  async listForUser(userId) {
10018
10620
  const tokens = await this.db.select({
10019
- id: refreshTokens.id,
10020
- userId: refreshTokens.userId,
10021
- tokenHash: refreshTokens.tokenHash,
10022
- expiresAt: refreshTokens.expiresAt,
10023
- createdAt: refreshTokens.createdAt,
10024
- userAgent: refreshTokens.userAgent,
10025
- ipAddress: refreshTokens.ipAddress
10026
- }).from(refreshTokens).where(drizzleOrm.eq(refreshTokens.userId, userId)).orderBy(refreshTokens.createdAt);
10621
+ id: this.refreshTokensTable.id,
10622
+ userId: this.refreshTokensTable.userId,
10623
+ tokenHash: this.refreshTokensTable.tokenHash,
10624
+ expiresAt: this.refreshTokensTable.expiresAt,
10625
+ createdAt: this.refreshTokensTable.createdAt,
10626
+ userAgent: this.refreshTokensTable.userAgent,
10627
+ ipAddress: this.refreshTokensTable.ipAddress
10628
+ }).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId)).orderBy(this.refreshTokensTable.createdAt);
10027
10629
  return tokens;
10028
10630
  }
10029
10631
  async deleteById(id, userId) {
10030
- await this.db.delete(refreshTokens).where(drizzleOrm.sql`${refreshTokens.id} = ${id} AND ${refreshTokens.userId} = ${userId}`);
10632
+ await this.db.delete(this.refreshTokensTable).where(drizzleOrm.sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
10031
10633
  }
10032
10634
  }
10033
10635
  class PasswordResetTokenService {
10034
- constructor(db) {
10636
+ constructor(db, tableOrTables) {
10035
10637
  this.db = db;
10638
+ if (tableOrTables && (tableOrTables.passwordResetTokens || tableOrTables.users)) {
10639
+ this.passwordResetTokensTable = tableOrTables.passwordResetTokens || passwordResetTokens;
10640
+ } else {
10641
+ this.passwordResetTokensTable = tableOrTables || passwordResetTokens;
10642
+ }
10643
+ }
10644
+ passwordResetTokensTable;
10645
+ getQualifiedPasswordResetTokensTableName() {
10646
+ const name = drizzleOrm.getTableName(this.passwordResetTokensTable);
10647
+ const schema = pgCore.getTableConfig(this.passwordResetTokensTable).schema || "public";
10648
+ return `"${schema}"."${name}"`;
10036
10649
  }
10037
10650
  /**
10038
10651
  * Create a password reset token
10039
10652
  */
10040
10653
  async createToken(userId, tokenHash, expiresAt) {
10654
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10041
10655
  await this.db.execute(drizzleOrm.sql`
10042
- DELETE FROM rebase.password_reset_tokens
10656
+ DELETE FROM ${drizzleOrm.sql.raw(tableName)}
10043
10657
  WHERE user_id = ${userId} AND used_at IS NULL
10044
10658
  `);
10045
- await this.db.insert(passwordResetTokens).values({
10659
+ await this.db.insert(this.passwordResetTokensTable).values({
10046
10660
  userId,
10047
10661
  tokenHash,
10048
10662
  expiresAt
@@ -10053,13 +10667,14 @@ ${tableRelations.join(",\n")}
10053
10667
  */
10054
10668
  async findValidByHash(tokenHash) {
10055
10669
  const [token] = await this.db.select({
10056
- userId: passwordResetTokens.userId,
10057
- expiresAt: passwordResetTokens.expiresAt
10058
- }).from(passwordResetTokens).where(drizzleOrm.eq(passwordResetTokens.tokenHash, tokenHash));
10670
+ userId: this.passwordResetTokensTable.userId,
10671
+ expiresAt: this.passwordResetTokensTable.expiresAt
10672
+ }).from(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10059
10673
  if (!token) return null;
10674
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10060
10675
  const result = await this.db.execute(drizzleOrm.sql`
10061
10676
  SELECT user_id, expires_at
10062
- FROM rebase.password_reset_tokens
10677
+ FROM ${drizzleOrm.sql.raw(tableName)}
10063
10678
  WHERE token_hash = ${tokenHash}
10064
10679
  AND used_at IS NULL
10065
10680
  AND expires_at > NOW()
@@ -10075,31 +10690,32 @@ ${tableRelations.join(",\n")}
10075
10690
  * Mark token as used
10076
10691
  */
10077
10692
  async markAsUsed(tokenHash) {
10078
- await this.db.update(passwordResetTokens).set({
10693
+ await this.db.update(this.passwordResetTokensTable).set({
10079
10694
  usedAt: /* @__PURE__ */ new Date()
10080
- }).where(drizzleOrm.eq(passwordResetTokens.tokenHash, tokenHash));
10695
+ }).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
10081
10696
  }
10082
10697
  /**
10083
10698
  * Delete all tokens for a user
10084
10699
  */
10085
10700
  async deleteAllForUser(userId) {
10086
- await this.db.delete(passwordResetTokens).where(drizzleOrm.eq(passwordResetTokens.userId, userId));
10701
+ await this.db.delete(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.userId, userId));
10087
10702
  }
10088
10703
  /**
10089
10704
  * Clean up expired tokens
10090
10705
  */
10091
10706
  async deleteExpired() {
10707
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
10092
10708
  await this.db.execute(drizzleOrm.sql`
10093
- DELETE FROM rebase.password_reset_tokens
10709
+ DELETE FROM ${drizzleOrm.sql.raw(tableName)}
10094
10710
  WHERE expires_at < NOW()
10095
10711
  `);
10096
10712
  }
10097
10713
  }
10098
10714
  class PostgresTokenRepository {
10099
- constructor(db) {
10715
+ constructor(db, tableOrTables) {
10100
10716
  this.db = db;
10101
- this.refreshTokenService = new RefreshTokenService(db);
10102
- this.passwordResetTokenService = new PasswordResetTokenService(db);
10717
+ this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
10718
+ this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
10103
10719
  }
10104
10720
  refreshTokenService;
10105
10721
  passwordResetTokenService;
@@ -10140,11 +10756,11 @@ ${tableRelations.join(",\n")}
10140
10756
  }
10141
10757
  }
10142
10758
  class PostgresAuthRepository {
10143
- constructor(db) {
10759
+ constructor(db, tableOrTables) {
10144
10760
  this.db = db;
10145
- this.userService = new UserService(db);
10146
- this.roleService = new RoleService(db);
10147
- this.tokenRepository = new PostgresTokenRepository(db);
10761
+ this.userService = new UserService(db, tableOrTables);
10762
+ this.roleService = new RoleService(db, tableOrTables);
10763
+ this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
10148
10764
  }
10149
10765
  userService;
10150
10766
  roleService;
@@ -10205,8 +10821,7 @@ ${tableRelations.join(",\n")}
10205
10821
  await this.userService.assignDefaultRole(userId, roleId);
10206
10822
  }
10207
10823
  async getUserWithRoles(userId) {
10208
- const result = await this.userService.getUserWithRoles(userId);
10209
- return result;
10824
+ return this.userService.getUserWithRoles(userId);
10210
10825
  }
10211
10826
  // Role operations (delegate to RoleService)
10212
10827
  async getRoleById(id) {
@@ -10394,6 +11009,24 @@ ${tableRelations.join(",\n")}
10394
11009
  return result.rowCount ?? 0;
10395
11010
  }
10396
11011
  }
11012
+ function deepEqual(a, b) {
11013
+ if (a === b) return true;
11014
+ if (a == null || b == null) return false;
11015
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
11016
+ if (Array.isArray(a) && Array.isArray(b)) {
11017
+ if (a.length !== b.length) return false;
11018
+ return a.every((v, i) => deepEqual(v, b[i]));
11019
+ }
11020
+ if (typeof a === "object" && typeof b === "object") {
11021
+ const aObj = a;
11022
+ const bObj = b;
11023
+ const aKeys = Object.keys(aObj);
11024
+ const bKeys = Object.keys(bObj);
11025
+ if (aKeys.length !== bKeys.length) return false;
11026
+ return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
11027
+ }
11028
+ return false;
11029
+ }
10397
11030
  function findChangedFields(oldValues, newValues) {
10398
11031
  const changed = [];
10399
11032
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
@@ -10403,7 +11036,7 @@ ${tableRelations.join(",\n")}
10403
11036
  if (key.startsWith("__")) continue;
10404
11037
  if (oldVal !== newVal) {
10405
11038
  if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
10406
- if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
11039
+ if (!deepEqual(oldVal, newVal)) {
10407
11040
  changed.push(key);
10408
11041
  }
10409
11042
  } else {
@@ -10521,14 +11154,32 @@ ${tableRelations.join(",\n")}
10521
11154
  if (!authConfig) return void 0;
10522
11155
  const internals = driverResult.internals;
10523
11156
  const db = internals.db;
10524
- await ensureAuthTablesExist(db);
11157
+ const registry = internals.registry;
11158
+ await ensureAuthTablesExist(db, registry);
10525
11159
  let emailService;
10526
11160
  if (authConfig.email) {
10527
11161
  emailService = serverCore.createEmailService(authConfig.email);
10528
11162
  }
10529
- const userService = new UserService(db);
10530
- const roleService = new RoleService(db);
10531
- const authRepository = new PostgresAuthRepository(db);
11163
+ const customUsersTable = registry?.getTable("users");
11164
+ const customRolesTable = registry?.getTable("roles");
11165
+ let usersSchemaName = "rebase";
11166
+ let rolesSchemaName = "rebase";
11167
+ if (customUsersTable) {
11168
+ usersSchemaName = pgCore.getTableConfig(customUsersTable).schema || "public";
11169
+ }
11170
+ if (customRolesTable) {
11171
+ rolesSchemaName = pgCore.getTableConfig(customRolesTable).schema || "public";
11172
+ }
11173
+ const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
11174
+ if (customUsersTable) {
11175
+ authTables.users = customUsersTable;
11176
+ }
11177
+ if (customRolesTable) {
11178
+ authTables.roles = customRolesTable;
11179
+ }
11180
+ const userService = new UserService(db, authTables);
11181
+ const roleService = new RoleService(db, authTables);
11182
+ const authRepository = new PostgresAuthRepository(db, authTables);
10532
11183
  return {
10533
11184
  userService,
10534
11185
  roleService,
@@ -10560,11 +11211,54 @@ ${tableRelations.join(",\n")}
10560
11211
  },
10561
11212
  mountRoutes(app, basePath, driverResult) {
10562
11213
  },
10563
- async initializeWebsockets(server, realtimeService, driver, config) {
11214
+ async initializeWebsockets(server, realtimeService, driver, config, adapter) {
10564
11215
  const {
10565
11216
  createPostgresWebSocket: createPostgresWebSocket2
10566
11217
  } = await Promise.resolve().then(() => websocket);
10567
- createPostgresWebSocket2(server, realtimeService, driver, config);
11218
+ createPostgresWebSocket2(server, realtimeService, driver, config, adapter);
11219
+ }
11220
+ };
11221
+ }
11222
+ function createPostgresAdapter(pgConfig) {
11223
+ const bootstrapper = createPostgresBootstrapper(pgConfig);
11224
+ return {
11225
+ type: bootstrapper.type,
11226
+ async initializeDriver(config) {
11227
+ return bootstrapper.initializeDriver(config);
11228
+ },
11229
+ async initializeRealtime(driverResult) {
11230
+ if (bootstrapper.initializeRealtime) {
11231
+ return bootstrapper.initializeRealtime({}, driverResult);
11232
+ }
11233
+ return void 0;
11234
+ },
11235
+ async initializeAuth(config, driverResult) {
11236
+ if (bootstrapper.initializeAuth) {
11237
+ return bootstrapper.initializeAuth(config, driverResult);
11238
+ }
11239
+ return void 0;
11240
+ },
11241
+ async initializeHistory(config, driverResult) {
11242
+ if (bootstrapper.initializeHistory) {
11243
+ return bootstrapper.initializeHistory(config, driverResult);
11244
+ }
11245
+ return void 0;
11246
+ },
11247
+ initializeWebsockets(server, realtimeService, driver, config) {
11248
+ if (bootstrapper.initializeWebsockets) {
11249
+ return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
11250
+ }
11251
+ },
11252
+ getAdmin(driverResult) {
11253
+ if (bootstrapper.getAdmin) {
11254
+ return bootstrapper.getAdmin(driverResult);
11255
+ }
11256
+ return void 0;
11257
+ },
11258
+ mountRoutes(app, basePath, driverResult) {
11259
+ if (bootstrapper.mountRoutes) {
11260
+ bootstrapper.mountRoutes(app, basePath, driverResult);
11261
+ }
10568
11262
  }
10569
11263
  };
10570
11264
  }
@@ -10578,6 +11272,8 @@ ${tableRelations.join(",\n")}
10578
11272
  exports2.PostgresRealtimeProvider = PostgresRealtimeProvider;
10579
11273
  exports2.RealtimeService = RealtimeService;
10580
11274
  exports2.appConfig = appConfig;
11275
+ exports2.createAuthSchema = createAuthSchema;
11276
+ exports2.createPostgresAdapter = createPostgresAdapter;
10581
11277
  exports2.createPostgresBootstrapper = createPostgresBootstrapper;
10582
11278
  exports2.createPostgresDatabaseConnection = createPostgresDatabaseConnection;
10583
11279
  exports2.createPostgresWebSocket = createPostgresWebSocket;
@@ -10595,6 +11291,7 @@ ${tableRelations.join(",\n")}
10595
11291
  exports2.userRolesRelations = userRolesRelations;
10596
11292
  exports2.users = users;
10597
11293
  exports2.usersRelations = usersRelations;
11294
+ exports2.usersSchema = usersSchema;
10598
11295
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
10599
11296
  });
10600
11297
  //# sourceMappingURL=index.umd.js.map