@rebasepro/server-core 0.2.5 → 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.
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,166 +2537,6 @@
2534
2537
  };
2535
2538
  }
2536
2539
  }
2537
- const defaultUsersCollection = {
2538
- name: "Users",
2539
- singularName: "User",
2540
- slug: "users",
2541
- table: "users",
2542
- schema: "rebase",
2543
- icon: "Users",
2544
- group: "Settings",
2545
- openEntityMode: "dialog",
2546
- disableDefaultActions: ["copy"],
2547
- sort: ["createdAt", "desc"],
2548
- properties: {
2549
- id: {
2550
- name: "ID",
2551
- type: "string",
2552
- isId: "uuid",
2553
- ui: {
2554
- readOnly: true
2555
- }
2556
- },
2557
- email: {
2558
- name: "Email",
2559
- type: "string",
2560
- validation: {
2561
- required: true,
2562
- unique: true
2563
- }
2564
- },
2565
- displayName: {
2566
- name: "Name",
2567
- type: "string",
2568
- columnName: "display_name",
2569
- validation: {
2570
- required: true
2571
- }
2572
- },
2573
- photoURL: {
2574
- name: "Photo URL",
2575
- type: "string",
2576
- columnName: "photo_url",
2577
- url: "image"
2578
- },
2579
- roles: {
2580
- name: "Roles",
2581
- type: "array",
2582
- columnType: "text[]",
2583
- of: {
2584
- name: "Role",
2585
- type: "string",
2586
- enum: {
2587
- admin: "Admin",
2588
- editor: "Editor",
2589
- viewer: "Viewer"
2590
- }
2591
- }
2592
- },
2593
- passwordHash: {
2594
- name: "Password Hash",
2595
- type: "string",
2596
- columnName: "password_hash",
2597
- ui: {
2598
- hideFromCollection: true,
2599
- disabled: {
2600
- hidden: true
2601
- }
2602
- }
2603
- },
2604
- emailVerified: {
2605
- name: "Email Verified",
2606
- type: "boolean",
2607
- columnName: "email_verified",
2608
- defaultValue: false,
2609
- ui: {
2610
- hideFromCollection: true,
2611
- disabled: {
2612
- hidden: true
2613
- }
2614
- }
2615
- },
2616
- emailVerificationToken: {
2617
- name: "Email Verification Token",
2618
- type: "string",
2619
- columnName: "email_verification_token",
2620
- ui: {
2621
- hideFromCollection: true,
2622
- disabled: {
2623
- hidden: true
2624
- }
2625
- }
2626
- },
2627
- emailVerificationSentAt: {
2628
- name: "Email Verification Sent At",
2629
- type: "date",
2630
- columnName: "email_verification_sent_at",
2631
- ui: {
2632
- hideFromCollection: true,
2633
- disabled: {
2634
- hidden: true
2635
- }
2636
- }
2637
- },
2638
- metadata: {
2639
- name: "Metadata",
2640
- type: "map",
2641
- defaultValue: {},
2642
- ui: {
2643
- hideFromCollection: true,
2644
- disabled: {
2645
- hidden: true
2646
- }
2647
- }
2648
- },
2649
- createdAt: {
2650
- name: "Created At",
2651
- type: "date",
2652
- columnName: "created_at",
2653
- ui: {
2654
- readOnly: true
2655
- }
2656
- },
2657
- updatedAt: {
2658
- name: "Updated At",
2659
- type: "date",
2660
- columnName: "updated_at",
2661
- autoValue: "on_update",
2662
- ui: {
2663
- hideFromCollection: true,
2664
- disabled: {
2665
- hidden: true
2666
- }
2667
- }
2668
- }
2669
- },
2670
- listProperties: ["displayName", "email", "roles", "createdAt"],
2671
- propertiesOrder: ["id", "email", "displayName", "roles", "createdAt"]
2672
- };
2673
- function mapOperator$1(op) {
2674
- switch (op) {
2675
- case "==":
2676
- return "eq";
2677
- case "!=":
2678
- return "neq";
2679
- case ">":
2680
- return "gt";
2681
- case ">=":
2682
- return "gte";
2683
- case "<":
2684
- return "lt";
2685
- case "<=":
2686
- return "lte";
2687
- case "array-contains":
2688
- return "cs";
2689
- case "array-contains-any":
2690
- return "csa";
2691
- case "not-in":
2692
- return "nin";
2693
- default:
2694
- return op;
2695
- }
2696
- }
2697
2540
  class QueryBuilder {
2698
2541
  constructor(collection) {
2699
2542
  this.collection = collection;
@@ -2701,23 +2544,30 @@
2701
2544
  params = {
2702
2545
  where: {}
2703
2546
  };
2704
- /**
2705
- * Add a filter condition to your query.
2706
- * @example
2707
- * client.collection('users').where('age', '>=', 18).find()
2708
- */
2709
- 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
+ }
2710
2552
  if (!this.params.where) {
2711
2553
  this.params.where = {};
2712
2554
  }
2713
- const mappedOp = mapOperator$1(operator);
2714
- let formattedValue = value;
2715
- if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2716
- formattedValue = `(${value.join(",")})`;
2717
- } else if (value === null) {
2718
- 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];
2719
2570
  }
2720
- this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2721
2571
  return this;
2722
2572
  }
2723
2573
  /**
@@ -3040,68 +2890,184 @@
3040
2890
  return null;
3041
2891
  }
3042
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
+ }
3043
2978
  function parseQueryOptions(query) {
3044
2979
  const options2 = {};
3045
- if (query.limit) options2.limit = parseInt(String(query.limit));
3046
- if (query.offset) options2.offset = parseInt(String(query.offset));
3047
- if (query.page) {
3048
- 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));
3049
2987
  const limit = options2.limit || 20;
3050
2988
  options2.offset = (page - 1) * limit;
3051
2989
  }
3052
2990
  options2.where = {};
3053
- 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"];
3054
3013
  for (const [key, rawValue] of Object.entries(query)) {
3055
3014
  if (reservedQueryKeys.includes(key)) continue;
3056
- const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
3057
- if (typeof value === "string") {
3058
- const parts = value.split(".");
3059
- if (parts.length >= 2) {
3060
- const op = parts[0];
3061
- const val = parts.slice(1).join(".");
3062
- const rebaseOp = mapOperator(op);
3063
- if (rebaseOp) {
3064
- let parsedVal = val;
3065
- if (val === "true") parsedVal = true;
3066
- else if (val === "false") parsedVal = false;
3067
- else if (val === "null") parsedVal = null;
3068
- else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
3069
- else if (val.startsWith("(")) {
3070
- const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
3071
- parsedVal = arrayContent.split(",").map((v) => {
3072
- const trimmed = v.trim();
3073
- if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
3074
- if (trimmed === "true") return true;
3075
- if (trimmed === "false") return false;
3076
- if (trimmed === "null") return null;
3077
- return trimmed;
3078
- });
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]);
3079
3046
  }
3080
- options2.where[key] = [rebaseOp, parsedVal];
3081
3047
  } else {
3082
3048
  let parsedVal = value;
3083
- if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3084
- 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]);
3085
3054
  }
3086
- } else {
3087
- let parsedVal = value;
3088
- if (value === "true") parsedVal = true;
3089
- else if (value === "false") parsedVal = false;
3090
- else if (value === "null") parsedVal = null;
3091
- else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3092
- options2.where[key] = ["==", parsedVal];
3093
3055
  }
3094
3056
  }
3057
+ if (conditions.length > 0) {
3058
+ options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
3059
+ }
3095
3060
  }
3096
3061
  if (Object.keys(options2.where).length === 0) {
3097
3062
  delete options2.where;
3098
3063
  }
3099
- if (query.orderBy) {
3064
+ const orderByVal = getLastValue(query.orderBy);
3065
+ if (orderByVal) {
3100
3066
  try {
3101
- options2.orderBy = typeof query.orderBy === "string" ? JSON.parse(query.orderBy) : query.orderBy;
3067
+ options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
3102
3068
  } catch {
3103
- if (typeof query.orderBy === "string") {
3104
- const [field, direction] = query.orderBy.split(":");
3069
+ if (typeof orderByVal === "string") {
3070
+ const [field, direction] = orderByVal.split(":");
3105
3071
  const dir = direction === "desc" ? "desc" : "asc";
3106
3072
  options2.orderBy = [{
3107
3073
  field,
@@ -3110,20 +3076,24 @@
3110
3076
  }
3111
3077
  }
3112
3078
  }
3113
- if (query.include) {
3114
- const includeStr = String(query.include).trim();
3079
+ const includeVal = getLastValue(query.include);
3080
+ if (includeVal) {
3081
+ const includeStr = String(includeVal).trim();
3115
3082
  if (includeStr === "*") {
3116
3083
  options2.include = ["*"];
3117
3084
  } else {
3118
3085
  options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
3119
3086
  }
3120
3087
  }
3121
- if (query.fields) {
3122
- const fieldsStr = String(query.fields).trim();
3088
+ const fieldsVal = getLastValue(query.fields);
3089
+ if (fieldsVal) {
3090
+ const fieldsStr = String(fieldsVal).trim();
3123
3091
  options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
3124
3092
  }
3125
- if (query.vector_search && query.vector) {
3126
- 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);
3127
3097
  let queryVector;
3128
3098
  try {
3129
3099
  queryVector = JSON.parse(vectorStr);
@@ -3133,17 +3103,19 @@
3133
3103
  } catch {
3134
3104
  throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
3135
3105
  }
3136
- const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
3106
+ const distanceParamVal = getLastValue(query.vector_distance);
3107
+ const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
3137
3108
  if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
3138
3109
  throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
3139
3110
  }
3140
3111
  const vectorSearch = {
3141
- property: String(query.vector_search),
3112
+ property: String(vectorSearchVal),
3142
3113
  vector: queryVector,
3143
3114
  distance: distanceParam
3144
3115
  };
3145
- if (query.vector_threshold) {
3146
- const threshold = parseFloat(String(query.vector_threshold));
3116
+ const thresholdVal = getLastValue(query.vector_threshold);
3117
+ if (thresholdVal) {
3118
+ const threshold = parseFloat(String(thresholdVal));
3147
3119
  if (isNaN(threshold)) {
3148
3120
  throw new Error("Invalid vector_threshold. Expected a number.");
3149
3121
  }
@@ -3238,9 +3210,9 @@
3238
3210
  const resolvedCollection = collection;
3239
3211
  this.router.get(`${basePath}/count`, async (c) => {
3240
3212
  this.enforceApiKeyPermission(c, collection.slug);
3241
- const queryDict = c.req.query();
3213
+ const queryDict = c.req.queries();
3242
3214
  const queryOptions = parseQueryOptions(queryDict);
3243
- const searchString = queryDict.searchString;
3215
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3244
3216
  const driver = c.get("driver") || this.driver;
3245
3217
  const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
3246
3218
  return c.json({
@@ -3249,9 +3221,9 @@
3249
3221
  });
3250
3222
  this.router.get(basePath, async (c) => {
3251
3223
  this.enforceApiKeyPermission(c, collection.slug);
3252
- const queryDict = c.req.query();
3224
+ const queryDict = c.req.queries();
3253
3225
  const queryOptions = parseQueryOptions(queryDict);
3254
- const searchString = queryDict.searchString;
3226
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3255
3227
  const driver = c.get("driver") || this.driver;
3256
3228
  const fetchService = this.getFetchService(driver);
3257
3229
  const hookCtx = this.buildHookContext(c, "GET");
@@ -3294,7 +3266,7 @@
3294
3266
  this.router.get(`${basePath}/:id`, async (c) => {
3295
3267
  this.enforceApiKeyPermission(c, collection.slug);
3296
3268
  const id = c.req.param("id");
3297
- const queryDict = c.req.query();
3269
+ const queryDict = c.req.queries();
3298
3270
  const queryOptions = parseQueryOptions(queryDict);
3299
3271
  const driver = c.get("driver") || this.driver;
3300
3272
  const fetchService = this.getFetchService(driver);
@@ -3463,12 +3435,13 @@
3463
3435
  const driver = c.get("driver") || this.driver;
3464
3436
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3465
3437
  if (parsed.entityId === "count") {
3466
- const queryDict = c.req.query();
3438
+ const queryDict = c.req.queries();
3467
3439
  const queryOptions = parseQueryOptions(queryDict);
3440
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3468
3441
  const total = driver.countEntities ? await driver.countEntities({
3469
3442
  path: parsed.collectionPath,
3470
3443
  filter: queryOptions.where,
3471
- searchString: queryDict.searchString
3444
+ searchString
3472
3445
  }) : 0;
3473
3446
  return c.json({
3474
3447
  count: total
@@ -3481,15 +3454,16 @@
3481
3454
  if (!entity) throw ApiError.notFound("Entity not found");
3482
3455
  return c.json(this.flattenEntity(entity));
3483
3456
  } else {
3484
- const queryDict = c.req.query();
3457
+ const queryDict = c.req.queries();
3485
3458
  const queryOptions = parseQueryOptions(queryDict);
3459
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3486
3460
  const entities = await driver.fetchCollection({
3487
3461
  path: parsed.collectionPath,
3488
3462
  filter: queryOptions.where,
3489
3463
  limit: queryOptions.limit,
3490
3464
  orderBy: queryOptions.orderBy?.[0]?.field,
3491
3465
  order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
3492
- searchString: queryDict.searchString
3466
+ searchString
3493
3467
  });
3494
3468
  return c.json({
3495
3469
  data: entities.map((e) => this.flattenEntity(e)),
@@ -26263,15 +26237,33 @@ ${credentialScope}
26263
26237
  if (value === null) return "eq.null";
26264
26238
  if (typeof value === "boolean") return `eq.${value}`;
26265
26239
  if (typeof value === "number") return String(value);
26266
- if (Array.isArray(value) && value.length === 2) {
26267
- const [rawOp, val] = value;
26268
- const op = OP_MAP[rawOp] ?? rawOp;
26269
- if (val === null) return `${op}.null`;
26270
- if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26271
- 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
+ }
26272
26249
  }
26273
26250
  return String(value);
26274
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
+ }
26275
26267
  function buildQueryString(params) {
26276
26268
  if (!params) return "";
26277
26269
  const parts = [];
@@ -26287,10 +26279,22 @@ ${credentialScope}
26287
26279
  if (params.include && params.include.length > 0) {
26288
26280
  parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
26289
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
+ }
26290
26287
  if (params.where) {
26291
26288
  for (const [field, value] of Object.entries(params.where)) {
26292
- const normalized2 = normalizeWhereValue(value);
26293
- 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
+ }
26294
26298
  }
26295
26299
  }
26296
26300
  return parts.length > 0 ? "?" + parts.join("&") : "";
@@ -27042,11 +27046,18 @@ ${credentialScope}
27042
27046
  };
27043
27047
  },
27044
27048
  async findById(id) {
27045
- const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27046
- method: "GET"
27047
- });
27048
- if (!raw) return void 0;
27049
- 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
+ }
27050
27061
  },
27051
27062
  async create(data, id) {
27052
27063
  const body = {
@@ -27086,8 +27097,12 @@ ${credentialScope}
27086
27097
  return raw.count ?? 0;
27087
27098
  },
27088
27099
  // Fluent builder instantiation
27089
- where(column, operator, value) {
27090
- 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);
27091
27106
  },
27092
27107
  orderBy(column, ascending) {
27093
27108
  return new QueryBuilder(client).orderBy(column, ascending);
@@ -38365,13 +38380,35 @@ ${credentialScope}
38365
38380
  mailer2 = new Mailer(transporter, options2, defaults);
38366
38381
  return mailer2;
38367
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
+ }
38368
38391
  class SMTPEmailService {
38369
38392
  transporter = null;
38370
38393
  config;
38371
38394
  constructor(config) {
38372
38395
  this.config = config;
38373
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
+ }
38374
38410
  this.transporter = createTransport({
38411
+ name: smtpName,
38375
38412
  host: config.smtp.host,
38376
38413
  port: config.smtp.port,
38377
38414
  secure: config.smtp.secure ?? config.smtp.port === 465,
@@ -38521,8 +38558,13 @@ ${credentialScope}
38521
38558
  dir: config.collectionsDir
38522
38559
  });
38523
38560
  }
38524
- if (config.auth) {
38525
- activeCollections = Array.from(new Map([defaultUsersCollection, ...activeCollections].map((c) => [c.slug, c])).values());
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");
38526
38568
  }
38527
38569
  const realtimeServices = {};
38528
38570
  const delegates = {};
@@ -38937,6 +38979,19 @@ ${credentialScope}
38937
38979
  logger.info("Email service attached to singleton", {
38938
38980
  configured: emailService.isConfigured()
38939
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
+ }
38940
38995
  }
38941
38996
  const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
38942
38997
  if (isSQLAdmin(driverAdmin)) {