@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.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
  /**
@@ -2904,68 +2890,184 @@
2904
2890
  return null;
2905
2891
  }
2906
2892
  }
2893
+ function getLastValue(val) {
2894
+ if (Array.isArray(val)) {
2895
+ return val[val.length - 1];
2896
+ }
2897
+ return val;
2898
+ }
2899
+ function parseLogicalString(str) {
2900
+ str = str.trim();
2901
+ if (str.startsWith("or(") && str.endsWith(")")) {
2902
+ const inner = str.slice(3, -1);
2903
+ return {
2904
+ type: "or",
2905
+ conditions: parseLogicalList(inner)
2906
+ };
2907
+ }
2908
+ if (str.startsWith("and(") && str.endsWith(")")) {
2909
+ const inner = str.slice(4, -1);
2910
+ return {
2911
+ type: "and",
2912
+ conditions: parseLogicalList(inner)
2913
+ };
2914
+ }
2915
+ const firstDot = str.indexOf(".");
2916
+ if (firstDot === -1) {
2917
+ return {
2918
+ column: str,
2919
+ operator: "==",
2920
+ value: true
2921
+ };
2922
+ }
2923
+ const field = str.substring(0, firstDot);
2924
+ const rest = str.substring(firstDot + 1);
2925
+ const secondDot = rest.indexOf(".");
2926
+ let op = "eq";
2927
+ let valStr = rest;
2928
+ if (secondDot !== -1) {
2929
+ op = rest.substring(0, secondDot);
2930
+ valStr = rest.substring(secondDot + 1);
2931
+ } else {
2932
+ op = "eq";
2933
+ valStr = rest;
2934
+ }
2935
+ const rebaseOp = mapOperator(op) || "==";
2936
+ let parsedVal = valStr;
2937
+ if (valStr === "true") parsedVal = true;
2938
+ else if (valStr === "false") parsedVal = false;
2939
+ else if (valStr === "null") parsedVal = null;
2940
+ else if (!isNaN(Number(valStr)) && valStr.trim() !== "") parsedVal = Number(valStr);
2941
+ else if (valStr.startsWith("(")) {
2942
+ const arrayContent = valStr.endsWith(")") ? valStr.slice(1, -1) : valStr.slice(1);
2943
+ parsedVal = arrayContent.split(",").map((v) => {
2944
+ const trimmed = v.trim();
2945
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
2946
+ if (trimmed === "true") return true;
2947
+ if (trimmed === "false") return false;
2948
+ if (trimmed === "null") return null;
2949
+ return trimmed;
2950
+ });
2951
+ }
2952
+ return {
2953
+ column: field,
2954
+ operator: rebaseOp,
2955
+ value: parsedVal
2956
+ };
2957
+ }
2958
+ function parseLogicalList(str) {
2959
+ const list = [];
2960
+ let depth = 0;
2961
+ let current = "";
2962
+ for (let i2 = 0; i2 < str.length; i2++) {
2963
+ const char = str[i2];
2964
+ if (char === "(") depth++;
2965
+ if (char === ")") depth--;
2966
+ if (char === "," && depth === 0) {
2967
+ list.push(parseLogicalString(current));
2968
+ current = "";
2969
+ } else {
2970
+ current += char;
2971
+ }
2972
+ }
2973
+ if (current) {
2974
+ list.push(parseLogicalString(current));
2975
+ }
2976
+ return list;
2977
+ }
2907
2978
  function parseQueryOptions(query) {
2908
2979
  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));
2980
+ const limitVal = getLastValue(query.limit);
2981
+ if (limitVal) options2.limit = parseInt(String(limitVal));
2982
+ const offsetVal = getLastValue(query.offset);
2983
+ if (offsetVal) options2.offset = parseInt(String(offsetVal));
2984
+ const pageVal = getLastValue(query.page);
2985
+ if (pageVal) {
2986
+ const page = parseInt(String(pageVal));
2913
2987
  const limit = options2.limit || 20;
2914
2988
  options2.offset = (page - 1) * limit;
2915
2989
  }
2916
2990
  options2.where = {};
2917
- const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold"];
2991
+ const orVal = getLastValue(query.or);
2992
+ const andVal = getLastValue(query.and);
2993
+ if (orVal) {
2994
+ let orStr = String(orVal).trim();
2995
+ if (orStr.startsWith("(") && orStr.endsWith(")")) {
2996
+ orStr = orStr.slice(1, -1);
2997
+ }
2998
+ options2.logical = {
2999
+ type: "or",
3000
+ conditions: parseLogicalList(orStr)
3001
+ };
3002
+ } else if (andVal) {
3003
+ let andStr = String(andVal).trim();
3004
+ if (andStr.startsWith("(") && andStr.endsWith(")")) {
3005
+ andStr = andStr.slice(1, -1);
3006
+ }
3007
+ options2.logical = {
3008
+ type: "and",
3009
+ conditions: parseLogicalList(andStr)
3010
+ };
3011
+ }
3012
+ const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold", "or", "and"];
2918
3013
  for (const [key, rawValue] of Object.entries(query)) {
2919
3014
  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
- });
3015
+ const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
3016
+ const conditions = [];
3017
+ for (const value of rawValues) {
3018
+ if (typeof value === "string") {
3019
+ const parts = value.split(".");
3020
+ if (parts.length >= 2) {
3021
+ const op = parts[0];
3022
+ const val = parts.slice(1).join(".");
3023
+ const rebaseOp = mapOperator(op);
3024
+ if (rebaseOp) {
3025
+ let parsedVal = val;
3026
+ if (val === "true") parsedVal = true;
3027
+ else if (val === "false") parsedVal = false;
3028
+ else if (val === "null") parsedVal = null;
3029
+ else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
3030
+ else if (val.startsWith("(")) {
3031
+ const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
3032
+ parsedVal = arrayContent.split(",").map((v) => {
3033
+ const trimmed = v.trim();
3034
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
3035
+ if (trimmed === "true") return true;
3036
+ if (trimmed === "false") return false;
3037
+ if (trimmed === "null") return null;
3038
+ return trimmed;
3039
+ });
3040
+ }
3041
+ conditions.push([rebaseOp, parsedVal]);
3042
+ } else {
3043
+ let parsedVal = value;
3044
+ if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3045
+ conditions.push(["==", parsedVal]);
2943
3046
  }
2944
- options2.where[key] = [rebaseOp, parsedVal];
2945
3047
  } else {
2946
3048
  let parsedVal = value;
2947
- if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
2948
- options2.where[key] = ["==", parsedVal];
3049
+ if (value === "true") parsedVal = true;
3050
+ else if (value === "false") parsedVal = false;
3051
+ else if (value === "null") parsedVal = null;
3052
+ else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3053
+ conditions.push(["==", parsedVal]);
2949
3054
  }
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
3055
  }
2958
3056
  }
3057
+ if (conditions.length > 0) {
3058
+ options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
3059
+ }
2959
3060
  }
2960
3061
  if (Object.keys(options2.where).length === 0) {
2961
3062
  delete options2.where;
2962
3063
  }
2963
- if (query.orderBy) {
3064
+ const orderByVal = getLastValue(query.orderBy);
3065
+ if (orderByVal) {
2964
3066
  try {
2965
- options2.orderBy = typeof query.orderBy === "string" ? JSON.parse(query.orderBy) : query.orderBy;
3067
+ options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
2966
3068
  } catch {
2967
- if (typeof query.orderBy === "string") {
2968
- const [field, direction] = query.orderBy.split(":");
3069
+ if (typeof orderByVal === "string") {
3070
+ const [field, direction] = orderByVal.split(":");
2969
3071
  const dir = direction === "desc" ? "desc" : "asc";
2970
3072
  options2.orderBy = [{
2971
3073
  field,
@@ -2974,20 +3076,24 @@
2974
3076
  }
2975
3077
  }
2976
3078
  }
2977
- if (query.include) {
2978
- const includeStr = String(query.include).trim();
3079
+ const includeVal = getLastValue(query.include);
3080
+ if (includeVal) {
3081
+ const includeStr = String(includeVal).trim();
2979
3082
  if (includeStr === "*") {
2980
3083
  options2.include = ["*"];
2981
3084
  } else {
2982
3085
  options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
2983
3086
  }
2984
3087
  }
2985
- if (query.fields) {
2986
- const fieldsStr = String(query.fields).trim();
3088
+ const fieldsVal = getLastValue(query.fields);
3089
+ if (fieldsVal) {
3090
+ const fieldsStr = String(fieldsVal).trim();
2987
3091
  options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
2988
3092
  }
2989
- if (query.vector_search && query.vector) {
2990
- const vectorStr = String(query.vector);
3093
+ const vectorSearchVal = getLastValue(query.vector_search);
3094
+ const vectorVal = getLastValue(query.vector);
3095
+ if (vectorSearchVal && vectorVal) {
3096
+ const vectorStr = String(vectorVal);
2991
3097
  let queryVector;
2992
3098
  try {
2993
3099
  queryVector = JSON.parse(vectorStr);
@@ -2997,17 +3103,19 @@
2997
3103
  } catch {
2998
3104
  throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
2999
3105
  }
3000
- const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
3106
+ const distanceParamVal = getLastValue(query.vector_distance);
3107
+ const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
3001
3108
  if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
3002
3109
  throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
3003
3110
  }
3004
3111
  const vectorSearch = {
3005
- property: String(query.vector_search),
3112
+ property: String(vectorSearchVal),
3006
3113
  vector: queryVector,
3007
3114
  distance: distanceParam
3008
3115
  };
3009
- if (query.vector_threshold) {
3010
- const threshold = parseFloat(String(query.vector_threshold));
3116
+ const thresholdVal = getLastValue(query.vector_threshold);
3117
+ if (thresholdVal) {
3118
+ const threshold = parseFloat(String(thresholdVal));
3011
3119
  if (isNaN(threshold)) {
3012
3120
  throw new Error("Invalid vector_threshold. Expected a number.");
3013
3121
  }
@@ -3102,9 +3210,9 @@
3102
3210
  const resolvedCollection = collection;
3103
3211
  this.router.get(`${basePath}/count`, async (c) => {
3104
3212
  this.enforceApiKeyPermission(c, collection.slug);
3105
- const queryDict = c.req.query();
3213
+ const queryDict = c.req.queries();
3106
3214
  const queryOptions = parseQueryOptions(queryDict);
3107
- const searchString = queryDict.searchString;
3215
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3108
3216
  const driver = c.get("driver") || this.driver;
3109
3217
  const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
3110
3218
  return c.json({
@@ -3113,9 +3221,9 @@
3113
3221
  });
3114
3222
  this.router.get(basePath, async (c) => {
3115
3223
  this.enforceApiKeyPermission(c, collection.slug);
3116
- const queryDict = c.req.query();
3224
+ const queryDict = c.req.queries();
3117
3225
  const queryOptions = parseQueryOptions(queryDict);
3118
- const searchString = queryDict.searchString;
3226
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3119
3227
  const driver = c.get("driver") || this.driver;
3120
3228
  const fetchService = this.getFetchService(driver);
3121
3229
  const hookCtx = this.buildHookContext(c, "GET");
@@ -3158,7 +3266,7 @@
3158
3266
  this.router.get(`${basePath}/:id`, async (c) => {
3159
3267
  this.enforceApiKeyPermission(c, collection.slug);
3160
3268
  const id = c.req.param("id");
3161
- const queryDict = c.req.query();
3269
+ const queryDict = c.req.queries();
3162
3270
  const queryOptions = parseQueryOptions(queryDict);
3163
3271
  const driver = c.get("driver") || this.driver;
3164
3272
  const fetchService = this.getFetchService(driver);
@@ -3327,12 +3435,13 @@
3327
3435
  const driver = c.get("driver") || this.driver;
3328
3436
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3329
3437
  if (parsed.entityId === "count") {
3330
- const queryDict = c.req.query();
3438
+ const queryDict = c.req.queries();
3331
3439
  const queryOptions = parseQueryOptions(queryDict);
3440
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3332
3441
  const total = driver.countEntities ? await driver.countEntities({
3333
3442
  path: parsed.collectionPath,
3334
3443
  filter: queryOptions.where,
3335
- searchString: queryDict.searchString
3444
+ searchString
3336
3445
  }) : 0;
3337
3446
  return c.json({
3338
3447
  count: total
@@ -3345,15 +3454,16 @@
3345
3454
  if (!entity) throw ApiError.notFound("Entity not found");
3346
3455
  return c.json(this.flattenEntity(entity));
3347
3456
  } else {
3348
- const queryDict = c.req.query();
3457
+ const queryDict = c.req.queries();
3349
3458
  const queryOptions = parseQueryOptions(queryDict);
3459
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3350
3460
  const entities = await driver.fetchCollection({
3351
3461
  path: parsed.collectionPath,
3352
3462
  filter: queryOptions.where,
3353
3463
  limit: queryOptions.limit,
3354
3464
  orderBy: queryOptions.orderBy?.[0]?.field,
3355
3465
  order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
3356
- searchString: queryDict.searchString
3466
+ searchString
3357
3467
  });
3358
3468
  return c.json({
3359
3469
  data: entities.map((e) => this.flattenEntity(e)),
@@ -13022,11 +13132,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13022
13132
  const results = await Promise.all(users.map((u) => applyUserAfterRead(u, ctx)));
13023
13133
  return results.filter((u) => u !== null);
13024
13134
  }
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
13135
  function toAdminUser(u, roles) {
13031
13136
  return {
13032
13137
  uid: u.id,
@@ -13385,90 +13490,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13385
13490
  success: true
13386
13491
  });
13387
13492
  });
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
13493
  return router;
13473
13494
  }
13474
13495
  function createBuiltinAuthAdapter(config) {
@@ -13512,8 +13533,7 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13512
13533
  const extendedPayload = payload;
13513
13534
  let roles = payload.roles || [];
13514
13535
  try {
13515
- const userRoles = await authRepository.getUserRoles(payload.userId);
13516
- roles = userRoles.map((r) => r.id);
13536
+ roles = await authRepository.getUserRoleIds(payload.userId);
13517
13537
  } catch {
13518
13538
  }
13519
13539
  const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
@@ -13543,8 +13563,7 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13543
13563
  const extendedPayload = payload;
13544
13564
  let roles = payload.roles || [];
13545
13565
  try {
13546
- const userRoles = await authRepository.getUserRoles(payload.userId);
13547
- roles = userRoles.map((r) => r.id);
13566
+ roles = await authRepository.getUserRoleIds(payload.userId);
13548
13567
  } catch {
13549
13568
  }
13550
13569
  const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
@@ -13558,7 +13577,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13558
13577
  };
13559
13578
  },
13560
13579
  userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
13561
- roleManagement: createRoleManagementFromRepo(authRepository),
13562
13580
  createAuthRoutes() {
13563
13581
  return createAuthRoutes({
13564
13582
  authRepo: authRepository,
@@ -13674,43 +13692,13 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13674
13692
  }
13675
13693
  },
13676
13694
  async getUserRoles(userId) {
13677
- const roles = await repo.getUserRoles(userId);
13678
- return roles.map(toAuthRoleData);
13695
+ return repo.getUserRoleIds(userId);
13679
13696
  },
13680
13697
  async setUserRoles(userId, roleIds) {
13681
13698
  await repo.setUserRoles(userId, roleIds);
13682
13699
  }
13683
13700
  };
13684
13701
  }
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
13702
  function toAuthUserData(user) {
13715
13703
  return {
13716
13704
  id: user.id,
@@ -13723,15 +13711,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
13723
13711
  updatedAt: user.updatedAt
13724
13712
  };
13725
13713
  }
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
13714
  function configureLogLevel(logLevel) {
13736
13715
  const LOG_LEVEL = logLevel || process.env.LOG_LEVEL || "info";
13737
13716
  const logLevels = {
@@ -24914,7 +24893,6 @@ ${credentialScope}
24914
24893
  verifyRequest: options2.verifyRequest,
24915
24894
  verifyToken: resolvedVerifyToken,
24916
24895
  userManagement: options2.userManagement,
24917
- roleManagement: options2.roleManagement,
24918
24896
  getCapabilities() {
24919
24897
  return defaultCapabilities;
24920
24898
  }
@@ -26259,15 +26237,33 @@ ${credentialScope}
26259
26237
  if (value === null) return "eq.null";
26260
26238
  if (typeof value === "boolean") return `eq.${value}`;
26261
26239
  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}`;
26240
+ if (Array.isArray(value)) {
26241
+ const conditions = Array.isArray(value[0]) ? value : [value];
26242
+ const [rawOp, val] = conditions[0] || [];
26243
+ if (rawOp) {
26244
+ const op = OP_MAP[rawOp] ?? rawOp;
26245
+ if (val === null) return `${op}.null`;
26246
+ if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26247
+ return `${op}.${val}`;
26248
+ }
26268
26249
  }
26269
26250
  return String(value);
26270
26251
  }
26252
+ function serializeLogicalCondition(cond2) {
26253
+ if ("type" in cond2) {
26254
+ const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
26255
+ return `${cond2.type}(${sub})`;
26256
+ } else {
26257
+ const op = OP_MAP[cond2.operator] ?? cond2.operator;
26258
+ let formattedValue = cond2.value;
26259
+ if (Array.isArray(cond2.value)) {
26260
+ formattedValue = `(${cond2.value.join(",")})`;
26261
+ } else if (cond2.value === null) {
26262
+ formattedValue = "null";
26263
+ }
26264
+ return `${cond2.column}.${op}.${formattedValue}`;
26265
+ }
26266
+ }
26271
26267
  function buildQueryString(params) {
26272
26268
  if (!params) return "";
26273
26269
  const parts = [];
@@ -26283,10 +26279,22 @@ ${credentialScope}
26283
26279
  if (params.include && params.include.length > 0) {
26284
26280
  parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
26285
26281
  }
26282
+ if (params.logical) {
26283
+ const root = params.logical;
26284
+ const serialized = root.conditions.map(serializeLogicalCondition).join(",");
26285
+ parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
26286
+ }
26286
26287
  if (params.where) {
26287
26288
  for (const [field, value] of Object.entries(params.where)) {
26288
- const normalized2 = normalizeWhereValue(value);
26289
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26289
+ if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
26290
+ for (const subVal of value) {
26291
+ const normalized2 = normalizeWhereValue(subVal);
26292
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26293
+ }
26294
+ } else {
26295
+ const normalized2 = normalizeWhereValue(value);
26296
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26297
+ }
26290
26298
  }
26291
26299
  }
26292
26300
  return parts.length > 0 ? "?" + parts.join("&") : "";
@@ -26961,33 +26969,6 @@ ${credentialScope}
26961
26969
  method: "DELETE"
26962
26970
  });
26963
26971
  }
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
26972
  async function bootstrap() {
26992
26973
  return transport.request(adminPath + "/bootstrap", {
26993
26974
  method: "POST"
@@ -27000,11 +26981,6 @@ ${credentialScope}
27000
26981
  createUser,
27001
26982
  updateUser,
27002
26983
  deleteUser,
27003
- listRoles,
27004
- getRole,
27005
- createRole,
27006
- updateRole,
27007
- deleteRole,
27008
26984
  bootstrap
27009
26985
  };
27010
26986
  }
@@ -27070,11 +27046,18 @@ ${credentialScope}
27070
27046
  };
27071
27047
  },
27072
27048
  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);
27049
+ try {
27050
+ const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27051
+ method: "GET"
27052
+ });
27053
+ if (!raw) return void 0;
27054
+ return rowToEntity(raw, slug);
27055
+ } catch (err) {
27056
+ if (err instanceof RebaseApiError && err.status === 404) {
27057
+ return void 0;
27058
+ }
27059
+ throw err;
27060
+ }
27078
27061
  },
27079
27062
  async create(data, id) {
27080
27063
  const body = {
@@ -27114,8 +27097,12 @@ ${credentialScope}
27114
27097
  return raw.count ?? 0;
27115
27098
  },
27116
27099
  // Fluent builder instantiation
27117
- where(column, operator, value) {
27118
- return new QueryBuilder(client).where(column, operator, value);
27100
+ where(columnOrCondition, operator, value) {
27101
+ const builder = new QueryBuilder(client);
27102
+ if (typeof columnOrCondition === "object") {
27103
+ return builder.where(columnOrCondition);
27104
+ }
27105
+ return builder.where(columnOrCondition, operator, value);
27119
27106
  },
27120
27107
  orderBy(column, ascending) {
27121
27108
  return new QueryBuilder(client).orderBy(column, ascending);
@@ -38393,13 +38380,35 @@ ${credentialScope}
38393
38380
  mailer2 = new Mailer(transporter, options2, defaults);
38394
38381
  return mailer2;
38395
38382
  };
38383
+ function getHostname(urlStr) {
38384
+ try {
38385
+ const url = new URL(urlStr.includes("://") ? urlStr : `https://${urlStr}`);
38386
+ return url.hostname;
38387
+ } catch {
38388
+ return void 0;
38389
+ }
38390
+ }
38396
38391
  class SMTPEmailService {
38397
38392
  transporter = null;
38398
38393
  config;
38399
38394
  constructor(config) {
38400
38395
  this.config = config;
38401
38396
  if (config.smtp) {
38397
+ let smtpName = config.smtp.name;
38398
+ if (!smtpName) {
38399
+ const urlsToTry = [process.env.FRONTEND_URL, config.resetPasswordUrl, config.verifyEmailUrl];
38400
+ for (const urlStr of urlsToTry) {
38401
+ if (urlStr) {
38402
+ const hostname = getHostname(urlStr);
38403
+ if (hostname) {
38404
+ smtpName = hostname;
38405
+ break;
38406
+ }
38407
+ }
38408
+ }
38409
+ }
38402
38410
  this.transporter = createTransport({
38411
+ name: smtpName,
38403
38412
  host: config.smtp.host,
38404
38413
  port: config.smtp.port,
38405
38414
  secure: config.smtp.secure ?? config.smtp.port === 465,
@@ -38549,6 +38558,14 @@ ${credentialScope}
38549
38558
  dir: config.collectionsDir
38550
38559
  });
38551
38560
  }
38561
+ if (config.defaultSecurityRules?.length) {
38562
+ for (const collection of activeCollections) {
38563
+ if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
38564
+ collection.securityRules = config.defaultSecurityRules;
38565
+ }
38566
+ }
38567
+ logger.info("Default security rules applied to collections without explicit rules");
38568
+ }
38552
38569
  const realtimeServices = {};
38553
38570
  const delegates = {};
38554
38571
  let bootstrappers = config.bootstrappers || [];
@@ -38617,8 +38634,7 @@ ${credentialScope}
38617
38634
  id: authAdapter.id
38618
38635
  });
38619
38636
  authConfigResult = {
38620
- userService: authAdapter.userManagement ?? {},
38621
- roleService: authAdapter.roleManagement ?? {}
38637
+ userService: authAdapter.userManagement ?? {}
38622
38638
  };
38623
38639
  } else {
38624
38640
  const safeAuthConfig = config.auth;
@@ -38963,6 +38979,26 @@ ${credentialScope}
38963
38979
  logger.info("Email service attached to singleton", {
38964
38980
  configured: emailService.isConfigured()
38965
38981
  });
38982
+ if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
38983
+ emailService.verifyConnection().then((success) => {
38984
+ if (!success) {
38985
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
38986
+ } else {
38987
+ logger.info("SMTP connection verified successfully.");
38988
+ }
38989
+ }).catch((err) => {
38990
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", {
38991
+ error: err
38992
+ });
38993
+ });
38994
+ }
38995
+ }
38996
+ const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
38997
+ if (isSQLAdmin(driverAdmin)) {
38998
+ Object.assign(serverClient, {
38999
+ sql: (query, options2) => driverAdmin.executeSql(query, options2)
39000
+ });
39001
+ logger.info("SQL capability attached to singleton");
38966
39002
  }
38967
39003
  _initRebase(serverClient);
38968
39004
  logger.info("Rebase singleton initialized");