@rebasepro/server-core 0.3.0 → 0.5.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 (50) hide show
  1. package/README.md +62 -25
  2. package/dist/common/src/collections/default-collections.d.ts +5 -8
  3. package/dist/common/src/data/query_builder.d.ts +6 -2
  4. package/dist/common/src/util/permissions.d.ts +14 -6
  5. package/dist/index.es.js +393 -315
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +393 -315
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/server-core/src/api/errors.d.ts +15 -0
  10. package/dist/server-core/src/api/rest/query-parser.d.ts +2 -0
  11. package/dist/server-core/src/api/types.d.ts +2 -1
  12. package/dist/server-core/src/auth/jwt.d.ts +10 -0
  13. package/dist/server-core/src/email/types.d.ts +1 -0
  14. package/dist/server-core/src/init.d.ts +31 -1
  15. package/dist/types/src/controllers/auth.d.ts +2 -2
  16. package/dist/types/src/controllers/client.d.ts +25 -40
  17. package/dist/types/src/controllers/data.d.ts +21 -3
  18. package/dist/types/src/controllers/data_driver.d.ts +5 -0
  19. package/dist/types/src/controllers/email.d.ts +2 -0
  20. package/dist/types/src/types/auth_adapter.d.ts +3 -56
  21. package/dist/types/src/types/backend.d.ts +38 -3
  22. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  23. package/dist/types/src/types/collections.d.ts +30 -6
  24. package/dist/types/src/types/entity_views.d.ts +19 -28
  25. package/dist/types/src/types/properties.d.ts +9 -15
  26. package/dist/types/src/types/user_management_delegate.d.ts +16 -53
  27. package/dist/types/src/users/index.d.ts +0 -1
  28. package/dist/types/src/users/user.d.ts +0 -1
  29. package/package.json +5 -5
  30. package/src/api/errors.ts +20 -1
  31. package/src/api/openapi-generator.ts +1 -1
  32. package/src/api/rest/api-generator.ts +82 -24
  33. package/src/api/rest/query-parser.ts +184 -63
  34. package/src/api/server.ts +1 -1
  35. package/src/api/types.ts +2 -1
  36. package/src/auth/admin-routes.ts +2 -91
  37. package/src/auth/builtin-auth-adapter.ts +9 -70
  38. package/src/auth/custom-auth-adapter.ts +1 -1
  39. package/src/auth/jwt.ts +10 -0
  40. package/src/auth/routes.ts +5 -9
  41. package/src/email/smtp-email-service.ts +31 -0
  42. package/src/email/types.ts +1 -0
  43. package/src/init.ts +135 -31
  44. package/src/storage/image-transform.ts +2 -1
  45. package/test/admin-routes.test.ts +0 -169
  46. package/test/backend-hooks-admin.test.ts +0 -25
  47. package/test/custom-auth-adapter.test.ts +2 -10
  48. package/test/smtp-email-service.test.ts +169 -0
  49. package/dist/types/src/users/roles.d.ts +0 -14
  50. package/test.ts +0 -6
package/dist/index.umd.js CHANGED
@@ -133,6 +133,9 @@
133
133
  this.value = value;
134
134
  }
135
135
  }
136
+ function isPostgresCollection(collection) {
137
+ return !collection.driver || collection.driver === "postgres";
138
+ }
136
139
  function isSQLAdmin(admin) {
137
140
  return !!admin && typeof admin.executeSql === "function";
138
141
  }
@@ -2534,30 +2537,6 @@
2534
2537
  };
2535
2538
  }
2536
2539
  }
2537
- function mapOperator$1(op) {
2538
- switch (op) {
2539
- case "==":
2540
- return "eq";
2541
- case "!=":
2542
- return "neq";
2543
- case ">":
2544
- return "gt";
2545
- case ">=":
2546
- return "gte";
2547
- case "<":
2548
- return "lt";
2549
- case "<=":
2550
- return "lte";
2551
- case "array-contains":
2552
- return "cs";
2553
- case "array-contains-any":
2554
- return "csa";
2555
- case "not-in":
2556
- return "nin";
2557
- default:
2558
- return op;
2559
- }
2560
- }
2561
2540
  class QueryBuilder {
2562
2541
  constructor(collection) {
2563
2542
  this.collection = collection;
@@ -2565,23 +2544,30 @@
2565
2544
  params = {
2566
2545
  where: {}
2567
2546
  };
2568
- /**
2569
- * Add a filter condition to your query.
2570
- * @example
2571
- * client.collection('users').where('age', '>=', 18).find()
2572
- */
2573
- where(column, operator, value) {
2547
+ where(columnOrCondition, operator, value) {
2548
+ if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
2549
+ this.params.logical = columnOrCondition;
2550
+ return this;
2551
+ }
2574
2552
  if (!this.params.where) {
2575
2553
  this.params.where = {};
2576
2554
  }
2577
- const mappedOp = mapOperator$1(operator);
2578
- let formattedValue = value;
2579
- if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2580
- formattedValue = `(${value.join(",")})`;
2581
- } else if (value === null) {
2582
- formattedValue = "null";
2555
+ const column = columnOrCondition;
2556
+ const condition = [operator, value];
2557
+ const existing = this.params.where[column];
2558
+ if (existing === void 0) {
2559
+ this.params.where[column] = condition;
2560
+ } else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
2561
+ this.params.where[column].push(condition);
2562
+ } else {
2563
+ let firstCondition;
2564
+ if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
2565
+ firstCondition = existing;
2566
+ } else {
2567
+ firstCondition = ["==", existing];
2568
+ }
2569
+ this.params.where[column] = [firstCondition, condition];
2583
2570
  }
2584
- this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2585
2571
  return this;
2586
2572
  }
2587
2573
  /**
@@ -2791,6 +2777,9 @@
2791
2777
  return new ApiError(503, code2, message);
2792
2778
  }
2793
2779
  }
2780
+ function isRebaseApiError(error2) {
2781
+ return error2 instanceof Error;
2782
+ }
2794
2783
  const errorHandler = (err, c) => {
2795
2784
  const error2 = err;
2796
2785
  if (error2 instanceof ApiError || error2.name === "ApiError") {
@@ -2904,68 +2893,184 @@
2904
2893
  return null;
2905
2894
  }
2906
2895
  }
2896
+ function getLastValue(val) {
2897
+ if (Array.isArray(val)) {
2898
+ return val[val.length - 1];
2899
+ }
2900
+ return val;
2901
+ }
2902
+ function parseLogicalString(str) {
2903
+ str = str.trim();
2904
+ if (str.startsWith("or(") && str.endsWith(")")) {
2905
+ const inner = str.slice(3, -1);
2906
+ return {
2907
+ type: "or",
2908
+ conditions: parseLogicalList(inner)
2909
+ };
2910
+ }
2911
+ if (str.startsWith("and(") && str.endsWith(")")) {
2912
+ const inner = str.slice(4, -1);
2913
+ return {
2914
+ type: "and",
2915
+ conditions: parseLogicalList(inner)
2916
+ };
2917
+ }
2918
+ const firstDot = str.indexOf(".");
2919
+ if (firstDot === -1) {
2920
+ return {
2921
+ column: str,
2922
+ operator: "==",
2923
+ value: true
2924
+ };
2925
+ }
2926
+ const field = str.substring(0, firstDot);
2927
+ const rest = str.substring(firstDot + 1);
2928
+ const secondDot = rest.indexOf(".");
2929
+ let op = "eq";
2930
+ let valStr = rest;
2931
+ if (secondDot !== -1) {
2932
+ op = rest.substring(0, secondDot);
2933
+ valStr = rest.substring(secondDot + 1);
2934
+ } else {
2935
+ op = "eq";
2936
+ valStr = rest;
2937
+ }
2938
+ const rebaseOp = mapOperator(op) || "==";
2939
+ let parsedVal = valStr;
2940
+ if (valStr === "true") parsedVal = true;
2941
+ else if (valStr === "false") parsedVal = false;
2942
+ else if (valStr === "null") parsedVal = null;
2943
+ else if (!isNaN(Number(valStr)) && valStr.trim() !== "") parsedVal = Number(valStr);
2944
+ else if (valStr.startsWith("(")) {
2945
+ const arrayContent = valStr.endsWith(")") ? valStr.slice(1, -1) : valStr.slice(1);
2946
+ parsedVal = arrayContent.split(",").map((v) => {
2947
+ const trimmed = v.trim();
2948
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
2949
+ if (trimmed === "true") return true;
2950
+ if (trimmed === "false") return false;
2951
+ if (trimmed === "null") return null;
2952
+ return trimmed;
2953
+ });
2954
+ }
2955
+ return {
2956
+ column: field,
2957
+ operator: rebaseOp,
2958
+ value: parsedVal
2959
+ };
2960
+ }
2961
+ function parseLogicalList(str) {
2962
+ const list = [];
2963
+ let depth = 0;
2964
+ let current = "";
2965
+ for (let i2 = 0; i2 < str.length; i2++) {
2966
+ const char = str[i2];
2967
+ if (char === "(") depth++;
2968
+ if (char === ")") depth--;
2969
+ if (char === "," && depth === 0) {
2970
+ list.push(parseLogicalString(current));
2971
+ current = "";
2972
+ } else {
2973
+ current += char;
2974
+ }
2975
+ }
2976
+ if (current) {
2977
+ list.push(parseLogicalString(current));
2978
+ }
2979
+ return list;
2980
+ }
2907
2981
  function parseQueryOptions(query) {
2908
2982
  const options2 = {};
2909
- if (query.limit) options2.limit = parseInt(String(query.limit));
2910
- if (query.offset) options2.offset = parseInt(String(query.offset));
2911
- if (query.page) {
2912
- const page = parseInt(String(query.page));
2983
+ const limitVal = getLastValue(query.limit);
2984
+ if (limitVal) options2.limit = parseInt(String(limitVal));
2985
+ const offsetVal = getLastValue(query.offset);
2986
+ if (offsetVal) options2.offset = parseInt(String(offsetVal));
2987
+ const pageVal = getLastValue(query.page);
2988
+ if (pageVal) {
2989
+ const page = parseInt(String(pageVal));
2913
2990
  const limit = options2.limit || 20;
2914
2991
  options2.offset = (page - 1) * limit;
2915
2992
  }
2916
2993
  options2.where = {};
2917
- const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold"];
2994
+ const orVal = getLastValue(query.or);
2995
+ const andVal = getLastValue(query.and);
2996
+ if (orVal) {
2997
+ let orStr = String(orVal).trim();
2998
+ if (orStr.startsWith("(") && orStr.endsWith(")")) {
2999
+ orStr = orStr.slice(1, -1);
3000
+ }
3001
+ options2.logical = {
3002
+ type: "or",
3003
+ conditions: parseLogicalList(orStr)
3004
+ };
3005
+ } else if (andVal) {
3006
+ let andStr = String(andVal).trim();
3007
+ if (andStr.startsWith("(") && andStr.endsWith(")")) {
3008
+ andStr = andStr.slice(1, -1);
3009
+ }
3010
+ options2.logical = {
3011
+ type: "and",
3012
+ conditions: parseLogicalList(andStr)
3013
+ };
3014
+ }
3015
+ const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold", "or", "and"];
2918
3016
  for (const [key, rawValue] of Object.entries(query)) {
2919
3017
  if (reservedQueryKeys.includes(key)) continue;
2920
- const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
2921
- if (typeof value === "string") {
2922
- const parts = value.split(".");
2923
- if (parts.length >= 2) {
2924
- const op = parts[0];
2925
- const val = parts.slice(1).join(".");
2926
- const rebaseOp = mapOperator(op);
2927
- if (rebaseOp) {
2928
- let parsedVal = val;
2929
- if (val === "true") parsedVal = true;
2930
- else if (val === "false") parsedVal = false;
2931
- else if (val === "null") parsedVal = null;
2932
- else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
2933
- else if (val.startsWith("(")) {
2934
- const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
2935
- parsedVal = arrayContent.split(",").map((v) => {
2936
- const trimmed = v.trim();
2937
- if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
2938
- if (trimmed === "true") return true;
2939
- if (trimmed === "false") return false;
2940
- if (trimmed === "null") return null;
2941
- return trimmed;
2942
- });
3018
+ const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
3019
+ const conditions = [];
3020
+ for (const value of rawValues) {
3021
+ if (typeof value === "string") {
3022
+ const parts = value.split(".");
3023
+ if (parts.length >= 2) {
3024
+ const op = parts[0];
3025
+ const val = parts.slice(1).join(".");
3026
+ const rebaseOp = mapOperator(op);
3027
+ if (rebaseOp) {
3028
+ let parsedVal = val;
3029
+ if (val === "true") parsedVal = true;
3030
+ else if (val === "false") parsedVal = false;
3031
+ else if (val === "null") parsedVal = null;
3032
+ else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
3033
+ else if (val.startsWith("(")) {
3034
+ const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
3035
+ parsedVal = arrayContent.split(",").map((v) => {
3036
+ const trimmed = v.trim();
3037
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
3038
+ if (trimmed === "true") return true;
3039
+ if (trimmed === "false") return false;
3040
+ if (trimmed === "null") return null;
3041
+ return trimmed;
3042
+ });
3043
+ }
3044
+ conditions.push([rebaseOp, parsedVal]);
3045
+ } else {
3046
+ let parsedVal = value;
3047
+ if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3048
+ conditions.push(["==", parsedVal]);
2943
3049
  }
2944
- options2.where[key] = [rebaseOp, parsedVal];
2945
3050
  } else {
2946
3051
  let parsedVal = value;
2947
- if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
2948
- options2.where[key] = ["==", parsedVal];
3052
+ if (value === "true") parsedVal = true;
3053
+ else if (value === "false") parsedVal = false;
3054
+ else if (value === "null") parsedVal = null;
3055
+ else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3056
+ conditions.push(["==", parsedVal]);
2949
3057
  }
2950
- } else {
2951
- let parsedVal = value;
2952
- if (value === "true") parsedVal = true;
2953
- else if (value === "false") parsedVal = false;
2954
- else if (value === "null") parsedVal = null;
2955
- else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
2956
- options2.where[key] = ["==", parsedVal];
2957
3058
  }
2958
3059
  }
3060
+ if (conditions.length > 0) {
3061
+ options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
3062
+ }
2959
3063
  }
2960
3064
  if (Object.keys(options2.where).length === 0) {
2961
3065
  delete options2.where;
2962
3066
  }
2963
- if (query.orderBy) {
3067
+ const orderByVal = getLastValue(query.orderBy);
3068
+ if (orderByVal) {
2964
3069
  try {
2965
- options2.orderBy = typeof query.orderBy === "string" ? JSON.parse(query.orderBy) : query.orderBy;
3070
+ options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
2966
3071
  } catch {
2967
- if (typeof query.orderBy === "string") {
2968
- const [field, direction] = query.orderBy.split(":");
3072
+ if (typeof orderByVal === "string") {
3073
+ const [field, direction] = orderByVal.split(":");
2969
3074
  const dir = direction === "desc" ? "desc" : "asc";
2970
3075
  options2.orderBy = [{
2971
3076
  field,
@@ -2974,20 +3079,24 @@
2974
3079
  }
2975
3080
  }
2976
3081
  }
2977
- if (query.include) {
2978
- const includeStr = String(query.include).trim();
3082
+ const includeVal = getLastValue(query.include);
3083
+ if (includeVal) {
3084
+ const includeStr = String(includeVal).trim();
2979
3085
  if (includeStr === "*") {
2980
3086
  options2.include = ["*"];
2981
3087
  } else {
2982
3088
  options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
2983
3089
  }
2984
3090
  }
2985
- if (query.fields) {
2986
- const fieldsStr = String(query.fields).trim();
3091
+ const fieldsVal = getLastValue(query.fields);
3092
+ if (fieldsVal) {
3093
+ const fieldsStr = String(fieldsVal).trim();
2987
3094
  options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
2988
3095
  }
2989
- if (query.vector_search && query.vector) {
2990
- const vectorStr = String(query.vector);
3096
+ const vectorSearchVal = getLastValue(query.vector_search);
3097
+ const vectorVal = getLastValue(query.vector);
3098
+ if (vectorSearchVal && vectorVal) {
3099
+ const vectorStr = String(vectorVal);
2991
3100
  let queryVector;
2992
3101
  try {
2993
3102
  queryVector = JSON.parse(vectorStr);
@@ -2997,17 +3106,19 @@
2997
3106
  } catch {
2998
3107
  throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
2999
3108
  }
3000
- const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
3109
+ const distanceParamVal = getLastValue(query.vector_distance);
3110
+ const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
3001
3111
  if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
3002
3112
  throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
3003
3113
  }
3004
3114
  const vectorSearch = {
3005
- property: String(query.vector_search),
3115
+ property: String(vectorSearchVal),
3006
3116
  vector: queryVector,
3007
3117
  distance: distanceParam
3008
3118
  };
3009
- if (query.vector_threshold) {
3010
- const threshold = parseFloat(String(query.vector_threshold));
3119
+ const thresholdVal = getLastValue(query.vector_threshold);
3120
+ if (thresholdVal) {
3121
+ const threshold = parseFloat(String(thresholdVal));
3011
3122
  if (isNaN(threshold)) {
3012
3123
  throw new Error("Invalid vector_threshold. Expected a number.");
3013
3124
  }
@@ -3102,9 +3213,9 @@
3102
3213
  const resolvedCollection = collection;
3103
3214
  this.router.get(`${basePath}/count`, async (c) => {
3104
3215
  this.enforceApiKeyPermission(c, collection.slug);
3105
- const queryDict = c.req.query();
3216
+ const queryDict = c.req.queries();
3106
3217
  const queryOptions = parseQueryOptions(queryDict);
3107
- const searchString = queryDict.searchString;
3218
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3108
3219
  const driver = c.get("driver") || this.driver;
3109
3220
  const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
3110
3221
  return c.json({
@@ -3113,9 +3224,9 @@
3113
3224
  });
3114
3225
  this.router.get(basePath, async (c) => {
3115
3226
  this.enforceApiKeyPermission(c, collection.slug);
3116
- const queryDict = c.req.query();
3227
+ const queryDict = c.req.queries();
3117
3228
  const queryOptions = parseQueryOptions(queryDict);
3118
- const searchString = queryDict.searchString;
3229
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3119
3230
  const driver = c.get("driver") || this.driver;
3120
3231
  const fetchService = this.getFetchService(driver);
3121
3232
  const hookCtx = this.buildHookContext(c, "GET");
@@ -3158,7 +3269,7 @@
3158
3269
  this.router.get(`${basePath}/:id`, async (c) => {
3159
3270
  this.enforceApiKeyPermission(c, collection.slug);
3160
3271
  const id = c.req.param("id");
3161
- const queryDict = c.req.query();
3272
+ const queryDict = c.req.queries();
3162
3273
  const queryOptions = parseQueryOptions(queryDict);
3163
3274
  const driver = c.get("driver") || this.driver;
3164
3275
  const fetchService = this.getFetchService(driver);
@@ -3209,9 +3320,10 @@
3209
3320
  }
3210
3321
  return c.json(response, 201);
3211
3322
  } catch (error2) {
3212
- const err = error2;
3213
- err.code = err.code || "BAD_REQUEST";
3214
- throw err;
3323
+ if (isRebaseApiError(error2) && !error2.code) {
3324
+ error2.code = "BAD_REQUEST";
3325
+ }
3326
+ throw error2;
3215
3327
  }
3216
3328
  });
3217
3329
  this.router.put(`${basePath}/:id`, async (c) => {
@@ -3247,9 +3359,10 @@
3247
3359
  }
3248
3360
  return c.json(response);
3249
3361
  } catch (error2) {
3250
- const err = error2;
3251
- err.code = err.code || "BAD_REQUEST";
3252
- throw err;
3362
+ if (isRebaseApiError(error2) && !error2.code) {
3363
+ error2.code = "BAD_REQUEST";
3364
+ }
3365
+ throw error2;
3253
3366
  }
3254
3367
  });
3255
3368
  this.router.delete(`${basePath}/:id`, async (c) => {
@@ -3326,13 +3439,15 @@
3326
3439
  if (!parsed) return next();
3327
3440
  const driver = c.get("driver") || this.driver;
3328
3441
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3442
+ const hookCtx = this.buildHookContext(c, "GET");
3329
3443
  if (parsed.entityId === "count") {
3330
- const queryDict = c.req.query();
3444
+ const queryDict = c.req.queries();
3331
3445
  const queryOptions = parseQueryOptions(queryDict);
3446
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3332
3447
  const total = driver.countEntities ? await driver.countEntities({
3333
3448
  path: parsed.collectionPath,
3334
3449
  filter: queryOptions.where,
3335
- searchString: queryDict.searchString
3450
+ searchString
3336
3451
  }) : 0;
3337
3452
  return c.json({
3338
3453
  count: total
@@ -3343,25 +3458,36 @@
3343
3458
  entityId: parsed.entityId
3344
3459
  });
3345
3460
  if (!entity) throw ApiError.notFound("Entity not found");
3346
- return c.json(this.flattenEntity(entity));
3461
+ const flatResult = this.flattenEntity(entity);
3462
+ const hookResult = await this.applyAfterRead(c.req.param("parent"), flatResult, hookCtx);
3463
+ if (!hookResult) throw ApiError.notFound("Entity not found");
3464
+ return c.json(hookResult);
3347
3465
  } else {
3348
- const queryDict = c.req.query();
3466
+ const queryDict = c.req.queries();
3349
3467
  const queryOptions = parseQueryOptions(queryDict);
3468
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3350
3469
  const entities = await driver.fetchCollection({
3351
3470
  path: parsed.collectionPath,
3352
3471
  filter: queryOptions.where,
3353
3472
  limit: queryOptions.limit,
3354
3473
  orderBy: queryOptions.orderBy?.[0]?.field,
3355
3474
  order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
3356
- searchString: queryDict.searchString
3475
+ searchString
3357
3476
  });
3477
+ let flatEntities = entities.map((e) => this.flattenEntity(e));
3478
+ flatEntities = await this.applyAfterReadBatch(c.req.param("parent"), flatEntities, hookCtx);
3479
+ const total = driver.countEntities ? await driver.countEntities({
3480
+ path: parsed.collectionPath,
3481
+ filter: queryOptions.where,
3482
+ searchString
3483
+ }) : flatEntities.length;
3358
3484
  return c.json({
3359
- data: entities.map((e) => this.flattenEntity(e)),
3485
+ data: flatEntities,
3360
3486
  meta: {
3361
- total: entities.length,
3487
+ total,
3362
3488
  limit: queryOptions.limit,
3363
3489
  offset: queryOptions.offset,
3364
- hasMore: false
3490
+ hasMore: (queryOptions.offset || 0) + flatEntities.length < total
3365
3491
  }
3366
3492
  });
3367
3493
  }
@@ -3373,14 +3499,24 @@
3373
3499
  const parsed = parseSubPath(rawPath);
3374
3500
  if (!parsed || parsed.entityId) return next();
3375
3501
  const driver = c.get("driver") || this.driver;
3502
+ const hookCtx = this.buildHookContext(c, "POST");
3376
3503
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3377
- const body = await c.req.json().catch(() => ({}));
3504
+ let body = await c.req.json().catch(() => ({}));
3505
+ if (this.dataHooks?.beforeSave) {
3506
+ body = await this.dataHooks.beforeSave(parsed.collectionPath, body, void 0, hookCtx);
3507
+ }
3378
3508
  const entity = await driver.saveEntity({
3379
3509
  path: parsed.collectionPath,
3380
3510
  values: body,
3381
3511
  status: "new"
3382
3512
  });
3383
- return c.json(this.formatResponse(entity), 201);
3513
+ const response = this.formatResponse(entity);
3514
+ if (this.dataHooks?.afterSave) {
3515
+ Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response, hookCtx)).catch((err) => {
3516
+ console.error("[BackendHooks] data.afterSave error:", err instanceof Error ? err.message : err);
3517
+ });
3518
+ }
3519
+ return c.json(response, 201);
3384
3520
  });
3385
3521
  this.router.put("/:parent/:parentId/:rest{.+}", async (c, next) => {
3386
3522
  const rest = c.req.param("rest");
@@ -3389,15 +3525,25 @@
3389
3525
  const parsed = parseSubPath(rawPath);
3390
3526
  if (!parsed || !parsed.entityId) return next();
3391
3527
  const driver = c.get("driver") || this.driver;
3528
+ const hookCtx = this.buildHookContext(c, "PUT");
3392
3529
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3393
- const body = await c.req.json().catch(() => ({}));
3530
+ let body = await c.req.json().catch(() => ({}));
3531
+ if (this.dataHooks?.beforeSave) {
3532
+ body = await this.dataHooks.beforeSave(parsed.collectionPath, body, parsed.entityId, hookCtx);
3533
+ }
3394
3534
  const entity = await driver.saveEntity({
3395
3535
  path: parsed.collectionPath,
3396
3536
  entityId: parsed.entityId,
3397
3537
  values: body,
3398
3538
  status: "existing"
3399
3539
  });
3400
- return c.json(this.formatResponse(entity));
3540
+ const response = this.formatResponse(entity);
3541
+ if (this.dataHooks?.afterSave) {
3542
+ Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response, hookCtx)).catch((err) => {
3543
+ console.error("[BackendHooks] data.afterSave error:", err instanceof Error ? err.message : err);
3544
+ });
3545
+ }
3546
+ return c.json(response);
3401
3547
  });
3402
3548
  this.router.delete("/:parent/:parentId/:rest{.+}", async (c, next) => {
3403
3549
  const rest = c.req.param("rest");
@@ -3406,15 +3552,24 @@
3406
3552
  const parsed = parseSubPath(rawPath);
3407
3553
  if (!parsed || !parsed.entityId) return next();
3408
3554
  const driver = c.get("driver") || this.driver;
3555
+ const hookCtx = this.buildHookContext(c, "DELETE");
3409
3556
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3410
3557
  const existingEntity = await driver.fetchEntity({
3411
3558
  path: parsed.collectionPath,
3412
3559
  entityId: parsed.entityId
3413
3560
  });
3414
3561
  if (!existingEntity) throw ApiError.notFound("Entity not found");
3562
+ if (this.dataHooks?.beforeDelete) {
3563
+ await this.dataHooks.beforeDelete(parsed.collectionPath, parsed.entityId, hookCtx);
3564
+ }
3415
3565
  await driver.deleteEntity({
3416
3566
  entity: existingEntity
3417
3567
  });
3568
+ if (this.dataHooks?.afterDelete) {
3569
+ Promise.resolve(this.dataHooks.afterDelete(parsed.collectionPath, parsed.entityId, hookCtx)).catch((err) => {
3570
+ console.error("[BackendHooks] data.afterDelete error:", err instanceof Error ? err.message : err);
3571
+ });
3572
+ }
3418
3573
  return new Response(null, {
3419
3574
  status: 204
3420
3575
  });
@@ -12897,14 +13052,11 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
12897
13052
  if (!factor || factor.userId !== userCtx.userId) {
12898
13053
  throw ApiError.notFound("MFA factor not found");
12899
13054
  }
12900
- const isRecoveryCode = code2.length > 6;
12901
- let isValid2 = false;
12902
- if (isRecoveryCode) {
13055
+ const secretBuffer = base32Decode(factor.secretEncrypted);
13056
+ let isValid2 = verifyTotp(secretBuffer, code2);
13057
+ if (!isValid2) {
12903
13058
  const codeHash = hashRecoveryCode(code2);
12904
13059
  isValid2 = await authRepo.useRecoveryCode(userCtx.userId, codeHash);
12905
- } else {
12906
- const secretBuffer = base32Decode(factor.secretEncrypted);
12907
- isValid2 = verifyTotp(secretBuffer, code2);
12908
13060
  }
12909
13061
  if (!isValid2) {
12910
13062
  throw ApiError.unauthorized("Invalid verification code", "INVALID_CODE");
@@ -13022,11 +13174,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13022
13174
  const results = await Promise.all(users.map((u) => applyUserAfterRead(u, ctx)));
13023
13175
  return results.filter((u) => u !== null);
13024
13176
  }
13025
- async function applyRoleAfterReadBatch(roles, ctx) {
13026
- if (!hooks?.roles?.afterRead) return roles;
13027
- const results = await Promise.all(roles.map((r) => hooks.roles.afterRead(r, ctx)));
13028
- return results.filter((r) => r !== null);
13029
- }
13030
13177
  function toAdminUser(u, roles) {
13031
13178
  return {
13032
13179
  uid: u.id,
@@ -13385,90 +13532,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13385
13532
  success: true
13386
13533
  });
13387
13534
  });
13388
- router.get("/roles", requireAdmin, async (c) => {
13389
- const roles = await authRepo.listRoles();
13390
- const hookCtx = buildHookContext(c, "GET");
13391
- let adminRoles = roles.map((r) => ({
13392
- id: r.id,
13393
- name: r.name,
13394
- isAdmin: r.isAdmin,
13395
- defaultPermissions: r.defaultPermissions
13396
- }));
13397
- adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
13398
- return c.json({
13399
- roles: adminRoles
13400
- });
13401
- });
13402
- router.get("/roles/:roleId", requireAdmin, async (c) => {
13403
- const roleId = c.req.param("roleId");
13404
- const role = await authRepo.getRoleById(roleId);
13405
- if (!role) {
13406
- throw ApiError.notFound("Role not found");
13407
- }
13408
- return c.json({
13409
- role
13410
- });
13411
- });
13412
- router.post("/roles", requireAdmin, async (c) => {
13413
- const body = await c.req.json();
13414
- const {
13415
- id,
13416
- name: name2,
13417
- isAdmin,
13418
- defaultPermissions
13419
- } = body;
13420
- if (!id || !name2) {
13421
- throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
13422
- }
13423
- const existing = await authRepo.getRoleById(id);
13424
- if (existing) {
13425
- throw ApiError.conflict("Role already exists", "ROLE_EXISTS");
13426
- }
13427
- const role = await authRepo.createRole({
13428
- id,
13429
- name: name2,
13430
- isAdmin: isAdmin ?? false,
13431
- defaultPermissions: defaultPermissions ?? null
13432
- });
13433
- return c.json({
13434
- role
13435
- }, 201);
13436
- });
13437
- router.put("/roles/:roleId", requireAdmin, async (c) => {
13438
- const roleId = c.req.param("roleId");
13439
- const body = await c.req.json();
13440
- const {
13441
- name: name2,
13442
- isAdmin,
13443
- defaultPermissions
13444
- } = body;
13445
- const existing = await authRepo.getRoleById(roleId);
13446
- if (!existing) {
13447
- throw ApiError.notFound("Role not found");
13448
- }
13449
- const role = await authRepo.updateRole(roleId, {
13450
- name: name2,
13451
- isAdmin,
13452
- defaultPermissions
13453
- });
13454
- return c.json({
13455
- role
13456
- });
13457
- });
13458
- router.delete("/roles/:roleId", requireAdmin, async (c) => {
13459
- const roleId = c.req.param("roleId");
13460
- if (["admin", "editor", "viewer"].includes(roleId)) {
13461
- throw ApiError.badRequest("Cannot delete built-in roles", "BUILTIN_ROLE");
13462
- }
13463
- const existing = await authRepo.getRoleById(roleId);
13464
- if (!existing) {
13465
- throw ApiError.notFound("Role not found");
13466
- }
13467
- await authRepo.deleteRole(roleId);
13468
- return c.json({
13469
- success: true
13470
- });
13471
- });
13472
13535
  return router;
13473
13536
  }
13474
13537
  function createBuiltinAuthAdapter(config) {
@@ -13509,18 +13572,16 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13509
13572
  if (!payload) {
13510
13573
  return null;
13511
13574
  }
13512
- const extendedPayload = payload;
13513
13575
  let roles = payload.roles || [];
13514
13576
  try {
13515
- const userRoles = await authRepository.getUserRoles(payload.userId);
13516
- roles = userRoles.map((r) => r.id);
13577
+ roles = await authRepository.getUserRoleIds(payload.userId);
13517
13578
  } catch {
13518
13579
  }
13519
13580
  const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
13520
13581
  return {
13521
13582
  uid: payload.userId,
13522
- email: extendedPayload.email ?? "",
13523
- displayName: extendedPayload.displayName ?? null,
13583
+ email: payload.email ?? "",
13584
+ displayName: payload.displayName ?? null,
13524
13585
  roles,
13525
13586
  isAdmin,
13526
13587
  rawToken: token
@@ -13540,25 +13601,22 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13540
13601
  if (!payload) {
13541
13602
  return null;
13542
13603
  }
13543
- const extendedPayload = payload;
13544
13604
  let roles = payload.roles || [];
13545
13605
  try {
13546
- const userRoles = await authRepository.getUserRoles(payload.userId);
13547
- roles = userRoles.map((r) => r.id);
13606
+ roles = await authRepository.getUserRoleIds(payload.userId);
13548
13607
  } catch {
13549
13608
  }
13550
13609
  const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
13551
13610
  return {
13552
13611
  uid: payload.userId,
13553
- email: extendedPayload.email ?? "",
13554
- displayName: extendedPayload.displayName ?? null,
13612
+ email: payload.email ?? "",
13613
+ displayName: payload.displayName ?? null,
13555
13614
  roles,
13556
13615
  isAdmin,
13557
13616
  rawToken: token
13558
13617
  };
13559
13618
  },
13560
13619
  userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
13561
- roleManagement: createRoleManagementFromRepo(authRepository),
13562
13620
  createAuthRoutes() {
13563
13621
  return createAuthRoutes({
13564
13622
  authRepo: authRepository,
@@ -13674,43 +13732,13 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13674
13732
  }
13675
13733
  },
13676
13734
  async getUserRoles(userId) {
13677
- const roles = await repo.getUserRoles(userId);
13678
- return roles.map(toAuthRoleData);
13735
+ return repo.getUserRoleIds(userId);
13679
13736
  },
13680
13737
  async setUserRoles(userId, roleIds) {
13681
13738
  await repo.setUserRoles(userId, roleIds);
13682
13739
  }
13683
13740
  };
13684
13741
  }
13685
- function createRoleManagementFromRepo(repo) {
13686
- return {
13687
- async listRoles() {
13688
- const roles = await repo.listRoles();
13689
- return roles.map(toAuthRoleData);
13690
- },
13691
- async getRoleById(id) {
13692
- const role = await repo.getRoleById(id);
13693
- return role ? toAuthRoleData(role) : null;
13694
- },
13695
- async createRole(data) {
13696
- const role = await repo.createRole({
13697
- id: data.id,
13698
- name: data.name,
13699
- isAdmin: data.isAdmin,
13700
- defaultPermissions: data.defaultPermissions,
13701
- collectionPermissions: data.collectionPermissions
13702
- });
13703
- return toAuthRoleData(role);
13704
- },
13705
- async updateRole(id, data) {
13706
- const role = await repo.updateRole(id, data);
13707
- return role ? toAuthRoleData(role) : null;
13708
- },
13709
- async deleteRole(id) {
13710
- await repo.deleteRole(id);
13711
- }
13712
- };
13713
- }
13714
13742
  function toAuthUserData(user) {
13715
13743
  return {
13716
13744
  id: user.id,
@@ -13723,15 +13751,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13723
13751
  updatedAt: user.updatedAt
13724
13752
  };
13725
13753
  }
13726
- function toAuthRoleData(role) {
13727
- return {
13728
- id: role.id,
13729
- name: role.name,
13730
- isAdmin: role.isAdmin,
13731
- defaultPermissions: role.defaultPermissions,
13732
- collectionPermissions: role.collectionPermissions
13733
- };
13734
- }
13735
13754
  function configureLogLevel(logLevel) {
13736
13755
  const LOG_LEVEL = logLevel || process.env.LOG_LEVEL || "info";
13737
13756
  const logLevels = {
@@ -24914,7 +24933,6 @@ ${credentialScope}
24914
24933
  verifyRequest: options2.verifyRequest,
24915
24934
  verifyToken: resolvedVerifyToken,
24916
24935
  userManagement: options2.userManagement,
24917
- roleManagement: options2.roleManagement,
24918
24936
  getCapabilities() {
24919
24937
  return defaultCapabilities;
24920
24938
  }
@@ -26259,15 +26277,33 @@ ${credentialScope}
26259
26277
  if (value === null) return "eq.null";
26260
26278
  if (typeof value === "boolean") return `eq.${value}`;
26261
26279
  if (typeof value === "number") return String(value);
26262
- if (Array.isArray(value) && value.length === 2) {
26263
- const [rawOp, val] = value;
26264
- const op = OP_MAP[rawOp] ?? rawOp;
26265
- if (val === null) return `${op}.null`;
26266
- if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26267
- return `${op}.${val}`;
26280
+ if (Array.isArray(value)) {
26281
+ const conditions = Array.isArray(value[0]) ? value : [value];
26282
+ const [rawOp, val] = conditions[0] || [];
26283
+ if (rawOp) {
26284
+ const op = OP_MAP[rawOp] ?? rawOp;
26285
+ if (val === null) return `${op}.null`;
26286
+ if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26287
+ return `${op}.${val}`;
26288
+ }
26268
26289
  }
26269
26290
  return String(value);
26270
26291
  }
26292
+ function serializeLogicalCondition(cond2) {
26293
+ if ("type" in cond2) {
26294
+ const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
26295
+ return `${cond2.type}(${sub})`;
26296
+ } else {
26297
+ const op = OP_MAP[cond2.operator] ?? cond2.operator;
26298
+ let formattedValue = cond2.value;
26299
+ if (Array.isArray(cond2.value)) {
26300
+ formattedValue = `(${cond2.value.join(",")})`;
26301
+ } else if (cond2.value === null) {
26302
+ formattedValue = "null";
26303
+ }
26304
+ return `${cond2.column}.${op}.${formattedValue}`;
26305
+ }
26306
+ }
26271
26307
  function buildQueryString(params) {
26272
26308
  if (!params) return "";
26273
26309
  const parts = [];
@@ -26283,10 +26319,22 @@ ${credentialScope}
26283
26319
  if (params.include && params.include.length > 0) {
26284
26320
  parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
26285
26321
  }
26322
+ if (params.logical) {
26323
+ const root = params.logical;
26324
+ const serialized = root.conditions.map(serializeLogicalCondition).join(",");
26325
+ parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
26326
+ }
26286
26327
  if (params.where) {
26287
26328
  for (const [field, value] of Object.entries(params.where)) {
26288
- const normalized2 = normalizeWhereValue(value);
26289
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26329
+ if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
26330
+ for (const subVal of value) {
26331
+ const normalized2 = normalizeWhereValue(subVal);
26332
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26333
+ }
26334
+ } else {
26335
+ const normalized2 = normalizeWhereValue(value);
26336
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26337
+ }
26290
26338
  }
26291
26339
  }
26292
26340
  return parts.length > 0 ? "?" + parts.join("&") : "";
@@ -26961,33 +27009,6 @@ ${credentialScope}
26961
27009
  method: "DELETE"
26962
27010
  });
26963
27011
  }
26964
- async function listRoles() {
26965
- return transport.request(adminPath + "/roles", {
26966
- method: "GET"
26967
- });
26968
- }
26969
- async function getRole(roleId) {
26970
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
26971
- method: "GET"
26972
- });
26973
- }
26974
- async function createRole(data) {
26975
- return transport.request(adminPath + "/roles", {
26976
- method: "POST",
26977
- body: JSON.stringify(data)
26978
- });
26979
- }
26980
- async function updateRole(roleId, data) {
26981
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
26982
- method: "PUT",
26983
- body: JSON.stringify(data)
26984
- });
26985
- }
26986
- async function deleteRole(roleId) {
26987
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
26988
- method: "DELETE"
26989
- });
26990
- }
26991
27012
  async function bootstrap() {
26992
27013
  return transport.request(adminPath + "/bootstrap", {
26993
27014
  method: "POST"
@@ -27000,11 +27021,6 @@ ${credentialScope}
27000
27021
  createUser,
27001
27022
  updateUser,
27002
27023
  deleteUser,
27003
- listRoles,
27004
- getRole,
27005
- createRole,
27006
- updateRole,
27007
- deleteRole,
27008
27024
  bootstrap
27009
27025
  };
27010
27026
  }
@@ -27070,11 +27086,18 @@ ${credentialScope}
27070
27086
  };
27071
27087
  },
27072
27088
  async findById(id) {
27073
- const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27074
- method: "GET"
27075
- });
27076
- if (!raw) return void 0;
27077
- return rowToEntity(raw, slug);
27089
+ try {
27090
+ const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27091
+ method: "GET"
27092
+ });
27093
+ if (!raw) return void 0;
27094
+ return rowToEntity(raw, slug);
27095
+ } catch (err) {
27096
+ if (err instanceof RebaseApiError && err.status === 404) {
27097
+ return void 0;
27098
+ }
27099
+ throw err;
27100
+ }
27078
27101
  },
27079
27102
  async create(data, id) {
27080
27103
  const body = {
@@ -27114,8 +27137,12 @@ ${credentialScope}
27114
27137
  return raw.count ?? 0;
27115
27138
  },
27116
27139
  // Fluent builder instantiation
27117
- where(column, operator, value) {
27118
- return new QueryBuilder(client).where(column, operator, value);
27140
+ where(columnOrCondition, operator, value) {
27141
+ const builder = new QueryBuilder(client);
27142
+ if (typeof columnOrCondition === "object") {
27143
+ return builder.where(columnOrCondition);
27144
+ }
27145
+ return builder.where(columnOrCondition, operator, value);
27119
27146
  },
27120
27147
  orderBy(column, ascending) {
27121
27148
  return new QueryBuilder(client).orderBy(column, ascending);
@@ -38393,13 +38420,35 @@ ${credentialScope}
38393
38420
  mailer2 = new Mailer(transporter, options2, defaults);
38394
38421
  return mailer2;
38395
38422
  };
38423
+ function getHostname(urlStr) {
38424
+ try {
38425
+ const url = new URL(urlStr.includes("://") ? urlStr : `https://${urlStr}`);
38426
+ return url.hostname;
38427
+ } catch {
38428
+ return void 0;
38429
+ }
38430
+ }
38396
38431
  class SMTPEmailService {
38397
38432
  transporter = null;
38398
38433
  config;
38399
38434
  constructor(config) {
38400
38435
  this.config = config;
38401
38436
  if (config.smtp) {
38437
+ let smtpName = config.smtp.name;
38438
+ if (!smtpName) {
38439
+ const urlsToTry = [process.env.FRONTEND_URL, config.resetPasswordUrl, config.verifyEmailUrl];
38440
+ for (const urlStr of urlsToTry) {
38441
+ if (urlStr) {
38442
+ const hostname = getHostname(urlStr);
38443
+ if (hostname) {
38444
+ smtpName = hostname;
38445
+ break;
38446
+ }
38447
+ }
38448
+ }
38449
+ }
38402
38450
  this.transporter = createTransport({
38451
+ name: smtpName,
38403
38452
  host: config.smtp.host,
38404
38453
  port: config.smtp.port,
38405
38454
  secure: config.smtp.secure ?? config.smtp.port === 465,
@@ -38549,6 +38598,14 @@ ${credentialScope}
38549
38598
  dir: config.collectionsDir
38550
38599
  });
38551
38600
  }
38601
+ if (config.defaultSecurityRules?.length) {
38602
+ for (const collection of activeCollections) {
38603
+ if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
38604
+ collection.securityRules = config.defaultSecurityRules;
38605
+ }
38606
+ }
38607
+ logger.info("Default security rules applied to collections without explicit rules");
38608
+ }
38552
38609
  const realtimeServices = {};
38553
38610
  const delegates = {};
38554
38611
  let bootstrappers = config.bootstrappers || [];
@@ -38592,7 +38649,9 @@ ${credentialScope}
38592
38649
  }
38593
38650
  if (bootstrapper.initializeRealtime) {
38594
38651
  const realtime = await bootstrapper.initializeRealtime({}, driverResult);
38595
- realtimeServices[b.id || bootstrapper.type] = realtime;
38652
+ if (realtime) {
38653
+ realtimeServices[b.id || bootstrapper.type] = realtime;
38654
+ }
38596
38655
  }
38597
38656
  }
38598
38657
  const driverRegistry = DefaultDriverRegistry.create(delegates);
@@ -38617,8 +38676,7 @@ ${credentialScope}
38617
38676
  id: authAdapter.id
38618
38677
  });
38619
38678
  authConfigResult = {
38620
- userService: authAdapter.userManagement ?? {},
38621
- roleService: authAdapter.roleManagement ?? {}
38679
+ userService: authAdapter.userManagement ?? {}
38622
38680
  };
38623
38681
  } else {
38624
38682
  const safeAuthConfig = config.auth;
@@ -38963,6 +39021,26 @@ ${credentialScope}
38963
39021
  logger.info("Email service attached to singleton", {
38964
39022
  configured: emailService.isConfigured()
38965
39023
  });
39024
+ if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
39025
+ emailService.verifyConnection().then((success) => {
39026
+ if (!success) {
39027
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
39028
+ } else {
39029
+ logger.info("SMTP connection verified successfully.");
39030
+ }
39031
+ }).catch((err) => {
39032
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", {
39033
+ error: err
39034
+ });
39035
+ });
39036
+ }
39037
+ }
39038
+ const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
39039
+ if (isSQLAdmin(driverAdmin)) {
39040
+ Object.assign(serverClient, {
39041
+ sql: (query, options2) => driverAdmin.executeSql(query, options2)
39042
+ });
39043
+ logger.info("SQL capability attached to singleton");
38966
39044
  }
38967
39045
  _initRebase(serverClient);
38968
39046
  logger.info("Rebase singleton initialized");
@@ -39052,7 +39130,7 @@ ${credentialScope}
39052
39130
  });
39053
39131
  }
39054
39132
  }
39055
- if (defaultBootstrapper.initializeWebsockets) {
39133
+ if (defaultBootstrapper.initializeWebsockets && defaultRealtimeService) {
39056
39134
  await defaultBootstrapper.initializeWebsockets(config.server, defaultRealtimeService, defaultDriver, config.auth, authAdapter);
39057
39135
  }
39058
39136
  logger.info("Rebase Backend Initialized");
@@ -39098,12 +39176,11 @@ ${credentialScope}
39098
39176
  }
39099
39177
  for (const [key, rt] of Object.entries(realtimeServices)) {
39100
39178
  try {
39101
- const rtWithLifecycle = rt;
39102
- if (typeof rtWithLifecycle.destroy === "function") {
39103
- await rtWithLifecycle.destroy();
39179
+ if (typeof rt.destroy === "function") {
39180
+ await rt.destroy();
39104
39181
  logger.info(`Realtime service "${key}" destroyed`);
39105
- } else if (typeof rtWithLifecycle.stopListening === "function") {
39106
- await rtWithLifecycle.stopListening();
39182
+ } else if (typeof rt.stopListening === "function") {
39183
+ await rt.stopListening();
39107
39184
  logger.info(`Realtime service "${key}" LISTEN client stopped`);
39108
39185
  }
39109
39186
  } catch (err) {
@@ -51257,6 +51334,7 @@ export default ${safeId}Collection;
51257
51334
  exports2.isAuthAdapter = isAuthAdapter;
51258
51335
  exports2.isDatabaseAdapter = isDatabaseAdapter;
51259
51336
  exports2.isOperationAllowed = isOperationAllowed;
51337
+ exports2.isRebaseApiError = isRebaseApiError;
51260
51338
  exports2.isTransformableImage = isTransformableImage;
51261
51339
  exports2.listenWithPortRetry = listenWithPortRetry;
51262
51340
  exports2.loadCronJobsFromDirectory = loadCronJobsFromDirectory;