@rebasepro/server-core 0.3.0 → 0.4.0

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 (40) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +5 -8
  2. package/dist/common/src/data/query_builder.d.ts +6 -2
  3. package/dist/index.es.js +318 -282
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +318 -282
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/server-core/src/api/rest/query-parser.d.ts +2 -0
  8. package/dist/server-core/src/api/types.d.ts +2 -1
  9. package/dist/server-core/src/email/types.d.ts +1 -0
  10. package/dist/server-core/src/init.d.ts +31 -1
  11. package/dist/types/src/controllers/auth.d.ts +2 -2
  12. package/dist/types/src/controllers/client.d.ts +25 -40
  13. package/dist/types/src/controllers/data.d.ts +21 -3
  14. package/dist/types/src/controllers/data_driver.d.ts +5 -0
  15. package/dist/types/src/controllers/email.d.ts +2 -0
  16. package/dist/types/src/types/auth_adapter.d.ts +3 -56
  17. package/dist/types/src/types/backend.d.ts +2 -2
  18. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  19. package/dist/types/src/types/collections.d.ts +9 -5
  20. package/dist/types/src/types/entity_views.d.ts +19 -28
  21. package/dist/types/src/types/properties.d.ts +9 -7
  22. package/dist/types/src/types/user_management_delegate.d.ts +16 -53
  23. package/dist/types/src/users/index.d.ts +0 -1
  24. package/dist/types/src/users/user.d.ts +0 -1
  25. package/package.json +5 -5
  26. package/src/api/rest/api-generator.ts +11 -9
  27. package/src/api/rest/query-parser.ts +184 -63
  28. package/src/api/types.ts +2 -1
  29. package/src/auth/admin-routes.ts +2 -91
  30. package/src/auth/builtin-auth-adapter.ts +5 -55
  31. package/src/auth/custom-auth-adapter.ts +1 -1
  32. package/src/email/smtp-email-service.ts +31 -0
  33. package/src/email/types.ts +1 -0
  34. package/src/init.ts +136 -24
  35. package/src/storage/image-transform.ts +2 -1
  36. package/test/admin-routes.test.ts +0 -169
  37. package/test/backend-hooks-admin.test.ts +0 -25
  38. package/test/custom-auth-adapter.test.ts +2 -10
  39. package/test/smtp-email-service.test.ts +169 -0
  40. package/dist/types/src/users/roles.d.ts +0 -14
package/dist/index.es.js CHANGED
@@ -146,6 +146,9 @@ class Vector {
146
146
  this.value = value;
147
147
  }
148
148
  }
149
+ function isPostgresCollection(collection) {
150
+ return !collection.driver || collection.driver === "postgres";
151
+ }
149
152
  function isSQLAdmin(admin) {
150
153
  return !!admin && typeof admin.executeSql === "function";
151
154
  }
@@ -2547,30 +2550,6 @@ class CollectionRegistry {
2547
2550
  };
2548
2551
  }
2549
2552
  }
2550
- function mapOperator$1(op) {
2551
- switch (op) {
2552
- case "==":
2553
- return "eq";
2554
- case "!=":
2555
- return "neq";
2556
- case ">":
2557
- return "gt";
2558
- case ">=":
2559
- return "gte";
2560
- case "<":
2561
- return "lt";
2562
- case "<=":
2563
- return "lte";
2564
- case "array-contains":
2565
- return "cs";
2566
- case "array-contains-any":
2567
- return "csa";
2568
- case "not-in":
2569
- return "nin";
2570
- default:
2571
- return op;
2572
- }
2573
- }
2574
2553
  class QueryBuilder {
2575
2554
  constructor(collection) {
2576
2555
  this.collection = collection;
@@ -2578,23 +2557,30 @@ class QueryBuilder {
2578
2557
  params = {
2579
2558
  where: {}
2580
2559
  };
2581
- /**
2582
- * Add a filter condition to your query.
2583
- * @example
2584
- * client.collection('users').where('age', '>=', 18).find()
2585
- */
2586
- where(column, operator, value) {
2560
+ where(columnOrCondition, operator, value) {
2561
+ if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
2562
+ this.params.logical = columnOrCondition;
2563
+ return this;
2564
+ }
2587
2565
  if (!this.params.where) {
2588
2566
  this.params.where = {};
2589
2567
  }
2590
- const mappedOp = mapOperator$1(operator);
2591
- let formattedValue = value;
2592
- if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2593
- formattedValue = `(${value.join(",")})`;
2594
- } else if (value === null) {
2595
- formattedValue = "null";
2568
+ const column = columnOrCondition;
2569
+ const condition = [operator, value];
2570
+ const existing = this.params.where[column];
2571
+ if (existing === void 0) {
2572
+ this.params.where[column] = condition;
2573
+ } else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
2574
+ this.params.where[column].push(condition);
2575
+ } else {
2576
+ let firstCondition;
2577
+ if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
2578
+ firstCondition = existing;
2579
+ } else {
2580
+ firstCondition = ["==", existing];
2581
+ }
2582
+ this.params.where[column] = [firstCondition, condition];
2596
2583
  }
2597
- this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2598
2584
  return this;
2599
2585
  }
2600
2586
  /**
@@ -2917,68 +2903,184 @@ function mapOperator(op) {
2917
2903
  return null;
2918
2904
  }
2919
2905
  }
2906
+ function getLastValue(val) {
2907
+ if (Array.isArray(val)) {
2908
+ return val[val.length - 1];
2909
+ }
2910
+ return val;
2911
+ }
2912
+ function parseLogicalString(str) {
2913
+ str = str.trim();
2914
+ if (str.startsWith("or(") && str.endsWith(")")) {
2915
+ const inner = str.slice(3, -1);
2916
+ return {
2917
+ type: "or",
2918
+ conditions: parseLogicalList(inner)
2919
+ };
2920
+ }
2921
+ if (str.startsWith("and(") && str.endsWith(")")) {
2922
+ const inner = str.slice(4, -1);
2923
+ return {
2924
+ type: "and",
2925
+ conditions: parseLogicalList(inner)
2926
+ };
2927
+ }
2928
+ const firstDot = str.indexOf(".");
2929
+ if (firstDot === -1) {
2930
+ return {
2931
+ column: str,
2932
+ operator: "==",
2933
+ value: true
2934
+ };
2935
+ }
2936
+ const field = str.substring(0, firstDot);
2937
+ const rest = str.substring(firstDot + 1);
2938
+ const secondDot = rest.indexOf(".");
2939
+ let op = "eq";
2940
+ let valStr = rest;
2941
+ if (secondDot !== -1) {
2942
+ op = rest.substring(0, secondDot);
2943
+ valStr = rest.substring(secondDot + 1);
2944
+ } else {
2945
+ op = "eq";
2946
+ valStr = rest;
2947
+ }
2948
+ const rebaseOp = mapOperator(op) || "==";
2949
+ let parsedVal = valStr;
2950
+ if (valStr === "true") parsedVal = true;
2951
+ else if (valStr === "false") parsedVal = false;
2952
+ else if (valStr === "null") parsedVal = null;
2953
+ else if (!isNaN(Number(valStr)) && valStr.trim() !== "") parsedVal = Number(valStr);
2954
+ else if (valStr.startsWith("(")) {
2955
+ const arrayContent = valStr.endsWith(")") ? valStr.slice(1, -1) : valStr.slice(1);
2956
+ parsedVal = arrayContent.split(",").map((v) => {
2957
+ const trimmed = v.trim();
2958
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
2959
+ if (trimmed === "true") return true;
2960
+ if (trimmed === "false") return false;
2961
+ if (trimmed === "null") return null;
2962
+ return trimmed;
2963
+ });
2964
+ }
2965
+ return {
2966
+ column: field,
2967
+ operator: rebaseOp,
2968
+ value: parsedVal
2969
+ };
2970
+ }
2971
+ function parseLogicalList(str) {
2972
+ const list = [];
2973
+ let depth = 0;
2974
+ let current = "";
2975
+ for (let i = 0; i < str.length; i++) {
2976
+ const char = str[i];
2977
+ if (char === "(") depth++;
2978
+ if (char === ")") depth--;
2979
+ if (char === "," && depth === 0) {
2980
+ list.push(parseLogicalString(current));
2981
+ current = "";
2982
+ } else {
2983
+ current += char;
2984
+ }
2985
+ }
2986
+ if (current) {
2987
+ list.push(parseLogicalString(current));
2988
+ }
2989
+ return list;
2990
+ }
2920
2991
  function parseQueryOptions(query) {
2921
2992
  const options2 = {};
2922
- if (query.limit) options2.limit = parseInt(String(query.limit));
2923
- if (query.offset) options2.offset = parseInt(String(query.offset));
2924
- if (query.page) {
2925
- const page = parseInt(String(query.page));
2993
+ const limitVal = getLastValue(query.limit);
2994
+ if (limitVal) options2.limit = parseInt(String(limitVal));
2995
+ const offsetVal = getLastValue(query.offset);
2996
+ if (offsetVal) options2.offset = parseInt(String(offsetVal));
2997
+ const pageVal = getLastValue(query.page);
2998
+ if (pageVal) {
2999
+ const page = parseInt(String(pageVal));
2926
3000
  const limit = options2.limit || 20;
2927
3001
  options2.offset = (page - 1) * limit;
2928
3002
  }
2929
3003
  options2.where = {};
2930
- const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold"];
3004
+ const orVal = getLastValue(query.or);
3005
+ const andVal = getLastValue(query.and);
3006
+ if (orVal) {
3007
+ let orStr = String(orVal).trim();
3008
+ if (orStr.startsWith("(") && orStr.endsWith(")")) {
3009
+ orStr = orStr.slice(1, -1);
3010
+ }
3011
+ options2.logical = {
3012
+ type: "or",
3013
+ conditions: parseLogicalList(orStr)
3014
+ };
3015
+ } else if (andVal) {
3016
+ let andStr = String(andVal).trim();
3017
+ if (andStr.startsWith("(") && andStr.endsWith(")")) {
3018
+ andStr = andStr.slice(1, -1);
3019
+ }
3020
+ options2.logical = {
3021
+ type: "and",
3022
+ conditions: parseLogicalList(andStr)
3023
+ };
3024
+ }
3025
+ const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold", "or", "and"];
2931
3026
  for (const [key, rawValue] of Object.entries(query)) {
2932
3027
  if (reservedQueryKeys.includes(key)) continue;
2933
- const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
2934
- if (typeof value === "string") {
2935
- const parts = value.split(".");
2936
- if (parts.length >= 2) {
2937
- const op = parts[0];
2938
- const val = parts.slice(1).join(".");
2939
- const rebaseOp = mapOperator(op);
2940
- if (rebaseOp) {
2941
- let parsedVal = val;
2942
- if (val === "true") parsedVal = true;
2943
- else if (val === "false") parsedVal = false;
2944
- else if (val === "null") parsedVal = null;
2945
- else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
2946
- else if (val.startsWith("(")) {
2947
- const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
2948
- parsedVal = arrayContent.split(",").map((v) => {
2949
- const trimmed = v.trim();
2950
- if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
2951
- if (trimmed === "true") return true;
2952
- if (trimmed === "false") return false;
2953
- if (trimmed === "null") return null;
2954
- return trimmed;
2955
- });
3028
+ const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
3029
+ const conditions = [];
3030
+ for (const value of rawValues) {
3031
+ if (typeof value === "string") {
3032
+ const parts = value.split(".");
3033
+ if (parts.length >= 2) {
3034
+ const op = parts[0];
3035
+ const val = parts.slice(1).join(".");
3036
+ const rebaseOp = mapOperator(op);
3037
+ if (rebaseOp) {
3038
+ let parsedVal = val;
3039
+ if (val === "true") parsedVal = true;
3040
+ else if (val === "false") parsedVal = false;
3041
+ else if (val === "null") parsedVal = null;
3042
+ else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
3043
+ else if (val.startsWith("(")) {
3044
+ const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
3045
+ parsedVal = arrayContent.split(",").map((v) => {
3046
+ const trimmed = v.trim();
3047
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
3048
+ if (trimmed === "true") return true;
3049
+ if (trimmed === "false") return false;
3050
+ if (trimmed === "null") return null;
3051
+ return trimmed;
3052
+ });
3053
+ }
3054
+ conditions.push([rebaseOp, parsedVal]);
3055
+ } else {
3056
+ let parsedVal = value;
3057
+ if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3058
+ conditions.push(["==", parsedVal]);
2956
3059
  }
2957
- options2.where[key] = [rebaseOp, parsedVal];
2958
3060
  } else {
2959
3061
  let parsedVal = value;
2960
- if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
2961
- options2.where[key] = ["==", parsedVal];
3062
+ if (value === "true") parsedVal = true;
3063
+ else if (value === "false") parsedVal = false;
3064
+ else if (value === "null") parsedVal = null;
3065
+ else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3066
+ conditions.push(["==", parsedVal]);
2962
3067
  }
2963
- } else {
2964
- let parsedVal = value;
2965
- if (value === "true") parsedVal = true;
2966
- else if (value === "false") parsedVal = false;
2967
- else if (value === "null") parsedVal = null;
2968
- else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
2969
- options2.where[key] = ["==", parsedVal];
2970
3068
  }
2971
3069
  }
3070
+ if (conditions.length > 0) {
3071
+ options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
3072
+ }
2972
3073
  }
2973
3074
  if (Object.keys(options2.where).length === 0) {
2974
3075
  delete options2.where;
2975
3076
  }
2976
- if (query.orderBy) {
3077
+ const orderByVal = getLastValue(query.orderBy);
3078
+ if (orderByVal) {
2977
3079
  try {
2978
- options2.orderBy = typeof query.orderBy === "string" ? JSON.parse(query.orderBy) : query.orderBy;
3080
+ options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
2979
3081
  } catch {
2980
- if (typeof query.orderBy === "string") {
2981
- const [field, direction] = query.orderBy.split(":");
3082
+ if (typeof orderByVal === "string") {
3083
+ const [field, direction] = orderByVal.split(":");
2982
3084
  const dir = direction === "desc" ? "desc" : "asc";
2983
3085
  options2.orderBy = [{
2984
3086
  field,
@@ -2987,20 +3089,24 @@ function parseQueryOptions(query) {
2987
3089
  }
2988
3090
  }
2989
3091
  }
2990
- if (query.include) {
2991
- const includeStr = String(query.include).trim();
3092
+ const includeVal = getLastValue(query.include);
3093
+ if (includeVal) {
3094
+ const includeStr = String(includeVal).trim();
2992
3095
  if (includeStr === "*") {
2993
3096
  options2.include = ["*"];
2994
3097
  } else {
2995
3098
  options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
2996
3099
  }
2997
3100
  }
2998
- if (query.fields) {
2999
- const fieldsStr = String(query.fields).trim();
3101
+ const fieldsVal = getLastValue(query.fields);
3102
+ if (fieldsVal) {
3103
+ const fieldsStr = String(fieldsVal).trim();
3000
3104
  options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
3001
3105
  }
3002
- if (query.vector_search && query.vector) {
3003
- const vectorStr = String(query.vector);
3106
+ const vectorSearchVal = getLastValue(query.vector_search);
3107
+ const vectorVal = getLastValue(query.vector);
3108
+ if (vectorSearchVal && vectorVal) {
3109
+ const vectorStr = String(vectorVal);
3004
3110
  let queryVector;
3005
3111
  try {
3006
3112
  queryVector = JSON.parse(vectorStr);
@@ -3010,17 +3116,19 @@ function parseQueryOptions(query) {
3010
3116
  } catch {
3011
3117
  throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
3012
3118
  }
3013
- const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
3119
+ const distanceParamVal = getLastValue(query.vector_distance);
3120
+ const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
3014
3121
  if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
3015
3122
  throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
3016
3123
  }
3017
3124
  const vectorSearch = {
3018
- property: String(query.vector_search),
3125
+ property: String(vectorSearchVal),
3019
3126
  vector: queryVector,
3020
3127
  distance: distanceParam
3021
3128
  };
3022
- if (query.vector_threshold) {
3023
- const threshold = parseFloat(String(query.vector_threshold));
3129
+ const thresholdVal = getLastValue(query.vector_threshold);
3130
+ if (thresholdVal) {
3131
+ const threshold = parseFloat(String(thresholdVal));
3024
3132
  if (isNaN(threshold)) {
3025
3133
  throw new Error("Invalid vector_threshold. Expected a number.");
3026
3134
  }
@@ -3115,9 +3223,9 @@ class RestApiGenerator {
3115
3223
  const resolvedCollection = collection;
3116
3224
  this.router.get(`${basePath}/count`, async (c) => {
3117
3225
  this.enforceApiKeyPermission(c, collection.slug);
3118
- const queryDict = c.req.query();
3226
+ const queryDict = c.req.queries();
3119
3227
  const queryOptions = parseQueryOptions(queryDict);
3120
- const searchString = queryDict.searchString;
3228
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3121
3229
  const driver = c.get("driver") || this.driver;
3122
3230
  const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
3123
3231
  return c.json({
@@ -3126,9 +3234,9 @@ class RestApiGenerator {
3126
3234
  });
3127
3235
  this.router.get(basePath, async (c) => {
3128
3236
  this.enforceApiKeyPermission(c, collection.slug);
3129
- const queryDict = c.req.query();
3237
+ const queryDict = c.req.queries();
3130
3238
  const queryOptions = parseQueryOptions(queryDict);
3131
- const searchString = queryDict.searchString;
3239
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3132
3240
  const driver = c.get("driver") || this.driver;
3133
3241
  const fetchService = this.getFetchService(driver);
3134
3242
  const hookCtx = this.buildHookContext(c, "GET");
@@ -3171,7 +3279,7 @@ class RestApiGenerator {
3171
3279
  this.router.get(`${basePath}/:id`, async (c) => {
3172
3280
  this.enforceApiKeyPermission(c, collection.slug);
3173
3281
  const id = c.req.param("id");
3174
- const queryDict = c.req.query();
3282
+ const queryDict = c.req.queries();
3175
3283
  const queryOptions = parseQueryOptions(queryDict);
3176
3284
  const driver = c.get("driver") || this.driver;
3177
3285
  const fetchService = this.getFetchService(driver);
@@ -3340,12 +3448,13 @@ class RestApiGenerator {
3340
3448
  const driver = c.get("driver") || this.driver;
3341
3449
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3342
3450
  if (parsed.entityId === "count") {
3343
- const queryDict = c.req.query();
3451
+ const queryDict = c.req.queries();
3344
3452
  const queryOptions = parseQueryOptions(queryDict);
3453
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3345
3454
  const total = driver.countEntities ? await driver.countEntities({
3346
3455
  path: parsed.collectionPath,
3347
3456
  filter: queryOptions.where,
3348
- searchString: queryDict.searchString
3457
+ searchString
3349
3458
  }) : 0;
3350
3459
  return c.json({
3351
3460
  count: total
@@ -3358,15 +3467,16 @@ class RestApiGenerator {
3358
3467
  if (!entity) throw ApiError.notFound("Entity not found");
3359
3468
  return c.json(this.flattenEntity(entity));
3360
3469
  } else {
3361
- const queryDict = c.req.query();
3470
+ const queryDict = c.req.queries();
3362
3471
  const queryOptions = parseQueryOptions(queryDict);
3472
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3363
3473
  const entities = await driver.fetchCollection({
3364
3474
  path: parsed.collectionPath,
3365
3475
  filter: queryOptions.where,
3366
3476
  limit: queryOptions.limit,
3367
3477
  orderBy: queryOptions.orderBy?.[0]?.field,
3368
3478
  order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
3369
- searchString: queryDict.searchString
3479
+ searchString
3370
3480
  });
3371
3481
  return c.json({
3372
3482
  data: entities.map((e) => this.flattenEntity(e)),
@@ -13035,11 +13145,6 @@ function createAdminRoutes(config) {
13035
13145
  const results = await Promise.all(users.map((u) => applyUserAfterRead(u, ctx)));
13036
13146
  return results.filter((u) => u !== null);
13037
13147
  }
13038
- async function applyRoleAfterReadBatch(roles, ctx) {
13039
- if (!hooks?.roles?.afterRead) return roles;
13040
- const results = await Promise.all(roles.map((r) => hooks.roles.afterRead(r, ctx)));
13041
- return results.filter((r) => r !== null);
13042
- }
13043
13148
  function toAdminUser(u, roles) {
13044
13149
  return {
13045
13150
  uid: u.id,
@@ -13398,90 +13503,6 @@ function createAdminRoutes(config) {
13398
13503
  success: true
13399
13504
  });
13400
13505
  });
13401
- router.get("/roles", requireAdmin, async (c) => {
13402
- const roles = await authRepo.listRoles();
13403
- const hookCtx = buildHookContext(c, "GET");
13404
- let adminRoles = roles.map((r) => ({
13405
- id: r.id,
13406
- name: r.name,
13407
- isAdmin: r.isAdmin,
13408
- defaultPermissions: r.defaultPermissions
13409
- }));
13410
- adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
13411
- return c.json({
13412
- roles: adminRoles
13413
- });
13414
- });
13415
- router.get("/roles/:roleId", requireAdmin, async (c) => {
13416
- const roleId = c.req.param("roleId");
13417
- const role = await authRepo.getRoleById(roleId);
13418
- if (!role) {
13419
- throw ApiError.notFound("Role not found");
13420
- }
13421
- return c.json({
13422
- role
13423
- });
13424
- });
13425
- router.post("/roles", requireAdmin, async (c) => {
13426
- const body = await c.req.json();
13427
- const {
13428
- id,
13429
- name: name2,
13430
- isAdmin,
13431
- defaultPermissions
13432
- } = body;
13433
- if (!id || !name2) {
13434
- throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
13435
- }
13436
- const existing = await authRepo.getRoleById(id);
13437
- if (existing) {
13438
- throw ApiError.conflict("Role already exists", "ROLE_EXISTS");
13439
- }
13440
- const role = await authRepo.createRole({
13441
- id,
13442
- name: name2,
13443
- isAdmin: isAdmin ?? false,
13444
- defaultPermissions: defaultPermissions ?? null
13445
- });
13446
- return c.json({
13447
- role
13448
- }, 201);
13449
- });
13450
- router.put("/roles/:roleId", requireAdmin, async (c) => {
13451
- const roleId = c.req.param("roleId");
13452
- const body = await c.req.json();
13453
- const {
13454
- name: name2,
13455
- isAdmin,
13456
- defaultPermissions
13457
- } = body;
13458
- const existing = await authRepo.getRoleById(roleId);
13459
- if (!existing) {
13460
- throw ApiError.notFound("Role not found");
13461
- }
13462
- const role = await authRepo.updateRole(roleId, {
13463
- name: name2,
13464
- isAdmin,
13465
- defaultPermissions
13466
- });
13467
- return c.json({
13468
- role
13469
- });
13470
- });
13471
- router.delete("/roles/:roleId", requireAdmin, async (c) => {
13472
- const roleId = c.req.param("roleId");
13473
- if (["admin", "editor", "viewer"].includes(roleId)) {
13474
- throw ApiError.badRequest("Cannot delete built-in roles", "BUILTIN_ROLE");
13475
- }
13476
- const existing = await authRepo.getRoleById(roleId);
13477
- if (!existing) {
13478
- throw ApiError.notFound("Role not found");
13479
- }
13480
- await authRepo.deleteRole(roleId);
13481
- return c.json({
13482
- success: true
13483
- });
13484
- });
13485
13506
  return router;
13486
13507
  }
13487
13508
  function createBuiltinAuthAdapter(config) {
@@ -13525,8 +13546,7 @@ function createBuiltinAuthAdapter(config) {
13525
13546
  const extendedPayload = payload;
13526
13547
  let roles = payload.roles || [];
13527
13548
  try {
13528
- const userRoles = await authRepository.getUserRoles(payload.userId);
13529
- roles = userRoles.map((r) => r.id);
13549
+ roles = await authRepository.getUserRoleIds(payload.userId);
13530
13550
  } catch {
13531
13551
  }
13532
13552
  const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
@@ -13556,8 +13576,7 @@ function createBuiltinAuthAdapter(config) {
13556
13576
  const extendedPayload = payload;
13557
13577
  let roles = payload.roles || [];
13558
13578
  try {
13559
- const userRoles = await authRepository.getUserRoles(payload.userId);
13560
- roles = userRoles.map((r) => r.id);
13579
+ roles = await authRepository.getUserRoleIds(payload.userId);
13561
13580
  } catch {
13562
13581
  }
13563
13582
  const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
@@ -13571,7 +13590,6 @@ function createBuiltinAuthAdapter(config) {
13571
13590
  };
13572
13591
  },
13573
13592
  userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
13574
- roleManagement: createRoleManagementFromRepo(authRepository),
13575
13593
  createAuthRoutes() {
13576
13594
  return createAuthRoutes({
13577
13595
  authRepo: authRepository,
@@ -13687,43 +13705,13 @@ function createUserManagementFromRepo(repo, resolvedOps, authHooks) {
13687
13705
  }
13688
13706
  },
13689
13707
  async getUserRoles(userId) {
13690
- const roles = await repo.getUserRoles(userId);
13691
- return roles.map(toAuthRoleData);
13708
+ return repo.getUserRoleIds(userId);
13692
13709
  },
13693
13710
  async setUserRoles(userId, roleIds) {
13694
13711
  await repo.setUserRoles(userId, roleIds);
13695
13712
  }
13696
13713
  };
13697
13714
  }
13698
- function createRoleManagementFromRepo(repo) {
13699
- return {
13700
- async listRoles() {
13701
- const roles = await repo.listRoles();
13702
- return roles.map(toAuthRoleData);
13703
- },
13704
- async getRoleById(id) {
13705
- const role = await repo.getRoleById(id);
13706
- return role ? toAuthRoleData(role) : null;
13707
- },
13708
- async createRole(data) {
13709
- const role = await repo.createRole({
13710
- id: data.id,
13711
- name: data.name,
13712
- isAdmin: data.isAdmin,
13713
- defaultPermissions: data.defaultPermissions,
13714
- collectionPermissions: data.collectionPermissions
13715
- });
13716
- return toAuthRoleData(role);
13717
- },
13718
- async updateRole(id, data) {
13719
- const role = await repo.updateRole(id, data);
13720
- return role ? toAuthRoleData(role) : null;
13721
- },
13722
- async deleteRole(id) {
13723
- await repo.deleteRole(id);
13724
- }
13725
- };
13726
- }
13727
13715
  function toAuthUserData(user) {
13728
13716
  return {
13729
13717
  id: user.id,
@@ -13736,15 +13724,6 @@ function toAuthUserData(user) {
13736
13724
  updatedAt: user.updatedAt
13737
13725
  };
13738
13726
  }
13739
- function toAuthRoleData(role) {
13740
- return {
13741
- id: role.id,
13742
- name: role.name,
13743
- isAdmin: role.isAdmin,
13744
- defaultPermissions: role.defaultPermissions,
13745
- collectionPermissions: role.collectionPermissions
13746
- };
13747
- }
13748
13727
  function configureLogLevel(logLevel) {
13749
13728
  const LOG_LEVEL = logLevel || process.env.LOG_LEVEL || "info";
13750
13729
  const logLevels = {
@@ -24927,7 +24906,6 @@ function createCustomAuthAdapter(options2) {
24927
24906
  verifyRequest: options2.verifyRequest,
24928
24907
  verifyToken: resolvedVerifyToken,
24929
24908
  userManagement: options2.userManagement,
24930
- roleManagement: options2.roleManagement,
24931
24909
  getCapabilities() {
24932
24910
  return defaultCapabilities;
24933
24911
  }
@@ -26225,15 +26203,33 @@ function normalizeWhereValue(value) {
26225
26203
  if (value === null) return "eq.null";
26226
26204
  if (typeof value === "boolean") return `eq.${value}`;
26227
26205
  if (typeof value === "number") return String(value);
26228
- if (Array.isArray(value) && value.length === 2) {
26229
- const [rawOp, val] = value;
26230
- const op = OP_MAP[rawOp] ?? rawOp;
26231
- if (val === null) return `${op}.null`;
26232
- if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26233
- return `${op}.${val}`;
26206
+ if (Array.isArray(value)) {
26207
+ const conditions = Array.isArray(value[0]) ? value : [value];
26208
+ const [rawOp, val] = conditions[0] || [];
26209
+ if (rawOp) {
26210
+ const op = OP_MAP[rawOp] ?? rawOp;
26211
+ if (val === null) return `${op}.null`;
26212
+ if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26213
+ return `${op}.${val}`;
26214
+ }
26234
26215
  }
26235
26216
  return String(value);
26236
26217
  }
26218
+ function serializeLogicalCondition(cond2) {
26219
+ if ("type" in cond2) {
26220
+ const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
26221
+ return `${cond2.type}(${sub})`;
26222
+ } else {
26223
+ const op = OP_MAP[cond2.operator] ?? cond2.operator;
26224
+ let formattedValue = cond2.value;
26225
+ if (Array.isArray(cond2.value)) {
26226
+ formattedValue = `(${cond2.value.join(",")})`;
26227
+ } else if (cond2.value === null) {
26228
+ formattedValue = "null";
26229
+ }
26230
+ return `${cond2.column}.${op}.${formattedValue}`;
26231
+ }
26232
+ }
26237
26233
  function buildQueryString(params) {
26238
26234
  if (!params) return "";
26239
26235
  const parts = [];
@@ -26249,10 +26245,22 @@ function buildQueryString(params) {
26249
26245
  if (params.include && params.include.length > 0) {
26250
26246
  parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
26251
26247
  }
26248
+ if (params.logical) {
26249
+ const root = params.logical;
26250
+ const serialized = root.conditions.map(serializeLogicalCondition).join(",");
26251
+ parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
26252
+ }
26252
26253
  if (params.where) {
26253
26254
  for (const [field, value] of Object.entries(params.where)) {
26254
- const normalized2 = normalizeWhereValue(value);
26255
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26255
+ if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
26256
+ for (const subVal of value) {
26257
+ const normalized2 = normalizeWhereValue(subVal);
26258
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26259
+ }
26260
+ } else {
26261
+ const normalized2 = normalizeWhereValue(value);
26262
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26263
+ }
26256
26264
  }
26257
26265
  }
26258
26266
  return parts.length > 0 ? "?" + parts.join("&") : "";
@@ -26927,33 +26935,6 @@ function createAdmin(transport, options2) {
26927
26935
  method: "DELETE"
26928
26936
  });
26929
26937
  }
26930
- async function listRoles() {
26931
- return transport.request(adminPath + "/roles", {
26932
- method: "GET"
26933
- });
26934
- }
26935
- async function getRole(roleId) {
26936
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
26937
- method: "GET"
26938
- });
26939
- }
26940
- async function createRole(data) {
26941
- return transport.request(adminPath + "/roles", {
26942
- method: "POST",
26943
- body: JSON.stringify(data)
26944
- });
26945
- }
26946
- async function updateRole(roleId, data) {
26947
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
26948
- method: "PUT",
26949
- body: JSON.stringify(data)
26950
- });
26951
- }
26952
- async function deleteRole(roleId) {
26953
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
26954
- method: "DELETE"
26955
- });
26956
- }
26957
26938
  async function bootstrap() {
26958
26939
  return transport.request(adminPath + "/bootstrap", {
26959
26940
  method: "POST"
@@ -26966,11 +26947,6 @@ function createAdmin(transport, options2) {
26966
26947
  createUser,
26967
26948
  updateUser,
26968
26949
  deleteUser,
26969
- listRoles,
26970
- getRole,
26971
- createRole,
26972
- updateRole,
26973
- deleteRole,
26974
26950
  bootstrap
26975
26951
  };
26976
26952
  }
@@ -27036,11 +27012,18 @@ function createCollectionClient(transport, slug, ws) {
27036
27012
  };
27037
27013
  },
27038
27014
  async findById(id) {
27039
- const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27040
- method: "GET"
27041
- });
27042
- if (!raw) return void 0;
27043
- return rowToEntity(raw, slug);
27015
+ try {
27016
+ const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27017
+ method: "GET"
27018
+ });
27019
+ if (!raw) return void 0;
27020
+ return rowToEntity(raw, slug);
27021
+ } catch (err) {
27022
+ if (err instanceof RebaseApiError && err.status === 404) {
27023
+ return void 0;
27024
+ }
27025
+ throw err;
27026
+ }
27044
27027
  },
27045
27028
  async create(data, id) {
27046
27029
  const body = {
@@ -27080,8 +27063,12 @@ function createCollectionClient(transport, slug, ws) {
27080
27063
  return raw.count ?? 0;
27081
27064
  },
27082
27065
  // Fluent builder instantiation
27083
- where(column, operator, value) {
27084
- return new QueryBuilder(client).where(column, operator, value);
27066
+ where(columnOrCondition, operator, value) {
27067
+ const builder = new QueryBuilder(client);
27068
+ if (typeof columnOrCondition === "object") {
27069
+ return builder.where(columnOrCondition);
27070
+ }
27071
+ return builder.where(columnOrCondition, operator, value);
27085
27072
  },
27086
27073
  orderBy(column, ascending) {
27087
27074
  return new QueryBuilder(client).orderBy(column, ascending);
@@ -38359,13 +38346,35 @@ var createTransport = function(transporter, defaults) {
38359
38346
  mailer2 = new Mailer(transporter, options2, defaults);
38360
38347
  return mailer2;
38361
38348
  };
38349
+ function getHostname(urlStr) {
38350
+ try {
38351
+ const url = new URL(urlStr.includes("://") ? urlStr : `https://${urlStr}`);
38352
+ return url.hostname;
38353
+ } catch {
38354
+ return void 0;
38355
+ }
38356
+ }
38362
38357
  class SMTPEmailService {
38363
38358
  transporter = null;
38364
38359
  config;
38365
38360
  constructor(config) {
38366
38361
  this.config = config;
38367
38362
  if (config.smtp) {
38363
+ let smtpName = config.smtp.name;
38364
+ if (!smtpName) {
38365
+ const urlsToTry = [process.env.FRONTEND_URL, config.resetPasswordUrl, config.verifyEmailUrl];
38366
+ for (const urlStr of urlsToTry) {
38367
+ if (urlStr) {
38368
+ const hostname = getHostname(urlStr);
38369
+ if (hostname) {
38370
+ smtpName = hostname;
38371
+ break;
38372
+ }
38373
+ }
38374
+ }
38375
+ }
38368
38376
  this.transporter = createTransport({
38377
+ name: smtpName,
38369
38378
  host: config.smtp.host,
38370
38379
  port: config.smtp.port,
38371
38380
  secure: config.smtp.secure ?? config.smtp.port === 465,
@@ -38515,6 +38524,14 @@ async function _initializeRebaseBackend(config) {
38515
38524
  dir: config.collectionsDir
38516
38525
  });
38517
38526
  }
38527
+ if (config.defaultSecurityRules?.length) {
38528
+ for (const collection of activeCollections) {
38529
+ if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
38530
+ collection.securityRules = config.defaultSecurityRules;
38531
+ }
38532
+ }
38533
+ logger.info("Default security rules applied to collections without explicit rules");
38534
+ }
38518
38535
  const realtimeServices = {};
38519
38536
  const delegates = {};
38520
38537
  let bootstrappers = config.bootstrappers || [];
@@ -38583,8 +38600,7 @@ async function _initializeRebaseBackend(config) {
38583
38600
  id: authAdapter.id
38584
38601
  });
38585
38602
  authConfigResult = {
38586
- userService: authAdapter.userManagement ?? {},
38587
- roleService: authAdapter.roleManagement ?? {}
38603
+ userService: authAdapter.userManagement ?? {}
38588
38604
  };
38589
38605
  } else {
38590
38606
  const safeAuthConfig = config.auth;
@@ -38929,6 +38945,26 @@ async function _initializeRebaseBackend(config) {
38929
38945
  logger.info("Email service attached to singleton", {
38930
38946
  configured: emailService.isConfigured()
38931
38947
  });
38948
+ if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
38949
+ emailService.verifyConnection().then((success) => {
38950
+ if (!success) {
38951
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
38952
+ } else {
38953
+ logger.info("SMTP connection verified successfully.");
38954
+ }
38955
+ }).catch((err) => {
38956
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", {
38957
+ error: err
38958
+ });
38959
+ });
38960
+ }
38961
+ }
38962
+ const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
38963
+ if (isSQLAdmin(driverAdmin)) {
38964
+ Object.assign(serverClient, {
38965
+ sql: (query, options2) => driverAdmin.executeSql(query, options2)
38966
+ });
38967
+ logger.info("SQL capability attached to singleton");
38932
38968
  }
38933
38969
  _initRebase(serverClient);
38934
38970
  logger.info("Rebase singleton initialized");