@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.es.js CHANGED
@@ -146,6 +146,9 @@ class Vector {
146
146
  this.value = value;
147
147
  }
148
148
  }
149
+ function isPostgresCollection(collection) {
150
+ return !collection.driver || collection.driver === "postgres";
151
+ }
149
152
  function isSQLAdmin(admin) {
150
153
  return !!admin && typeof admin.executeSql === "function";
151
154
  }
@@ -2547,166 +2550,6 @@ class CollectionRegistry {
2547
2550
  };
2548
2551
  }
2549
2552
  }
2550
- const defaultUsersCollection = {
2551
- name: "Users",
2552
- singularName: "User",
2553
- slug: "users",
2554
- table: "users",
2555
- schema: "rebase",
2556
- icon: "Users",
2557
- group: "Settings",
2558
- openEntityMode: "dialog",
2559
- disableDefaultActions: ["copy"],
2560
- sort: ["createdAt", "desc"],
2561
- properties: {
2562
- id: {
2563
- name: "ID",
2564
- type: "string",
2565
- isId: "uuid",
2566
- ui: {
2567
- readOnly: true
2568
- }
2569
- },
2570
- email: {
2571
- name: "Email",
2572
- type: "string",
2573
- validation: {
2574
- required: true,
2575
- unique: true
2576
- }
2577
- },
2578
- displayName: {
2579
- name: "Name",
2580
- type: "string",
2581
- columnName: "display_name",
2582
- validation: {
2583
- required: true
2584
- }
2585
- },
2586
- photoURL: {
2587
- name: "Photo URL",
2588
- type: "string",
2589
- columnName: "photo_url",
2590
- url: "image"
2591
- },
2592
- roles: {
2593
- name: "Roles",
2594
- type: "array",
2595
- columnType: "text[]",
2596
- of: {
2597
- name: "Role",
2598
- type: "string",
2599
- enum: {
2600
- admin: "Admin",
2601
- editor: "Editor",
2602
- viewer: "Viewer"
2603
- }
2604
- }
2605
- },
2606
- passwordHash: {
2607
- name: "Password Hash",
2608
- type: "string",
2609
- columnName: "password_hash",
2610
- ui: {
2611
- hideFromCollection: true,
2612
- disabled: {
2613
- hidden: true
2614
- }
2615
- }
2616
- },
2617
- emailVerified: {
2618
- name: "Email Verified",
2619
- type: "boolean",
2620
- columnName: "email_verified",
2621
- defaultValue: false,
2622
- ui: {
2623
- hideFromCollection: true,
2624
- disabled: {
2625
- hidden: true
2626
- }
2627
- }
2628
- },
2629
- emailVerificationToken: {
2630
- name: "Email Verification Token",
2631
- type: "string",
2632
- columnName: "email_verification_token",
2633
- ui: {
2634
- hideFromCollection: true,
2635
- disabled: {
2636
- hidden: true
2637
- }
2638
- }
2639
- },
2640
- emailVerificationSentAt: {
2641
- name: "Email Verification Sent At",
2642
- type: "date",
2643
- columnName: "email_verification_sent_at",
2644
- ui: {
2645
- hideFromCollection: true,
2646
- disabled: {
2647
- hidden: true
2648
- }
2649
- }
2650
- },
2651
- metadata: {
2652
- name: "Metadata",
2653
- type: "map",
2654
- defaultValue: {},
2655
- ui: {
2656
- hideFromCollection: true,
2657
- disabled: {
2658
- hidden: true
2659
- }
2660
- }
2661
- },
2662
- createdAt: {
2663
- name: "Created At",
2664
- type: "date",
2665
- columnName: "created_at",
2666
- ui: {
2667
- readOnly: true
2668
- }
2669
- },
2670
- updatedAt: {
2671
- name: "Updated At",
2672
- type: "date",
2673
- columnName: "updated_at",
2674
- autoValue: "on_update",
2675
- ui: {
2676
- hideFromCollection: true,
2677
- disabled: {
2678
- hidden: true
2679
- }
2680
- }
2681
- }
2682
- },
2683
- listProperties: ["displayName", "email", "roles", "createdAt"],
2684
- propertiesOrder: ["id", "email", "displayName", "roles", "createdAt"]
2685
- };
2686
- function mapOperator$1(op) {
2687
- switch (op) {
2688
- case "==":
2689
- return "eq";
2690
- case "!=":
2691
- return "neq";
2692
- case ">":
2693
- return "gt";
2694
- case ">=":
2695
- return "gte";
2696
- case "<":
2697
- return "lt";
2698
- case "<=":
2699
- return "lte";
2700
- case "array-contains":
2701
- return "cs";
2702
- case "array-contains-any":
2703
- return "csa";
2704
- case "not-in":
2705
- return "nin";
2706
- default:
2707
- return op;
2708
- }
2709
- }
2710
2553
  class QueryBuilder {
2711
2554
  constructor(collection) {
2712
2555
  this.collection = collection;
@@ -2714,23 +2557,30 @@ class QueryBuilder {
2714
2557
  params = {
2715
2558
  where: {}
2716
2559
  };
2717
- /**
2718
- * Add a filter condition to your query.
2719
- * @example
2720
- * client.collection('users').where('age', '>=', 18).find()
2721
- */
2722
- where(column, operator, value) {
2560
+ where(columnOrCondition, operator, value) {
2561
+ if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
2562
+ this.params.logical = columnOrCondition;
2563
+ return this;
2564
+ }
2723
2565
  if (!this.params.where) {
2724
2566
  this.params.where = {};
2725
2567
  }
2726
- const mappedOp = mapOperator$1(operator);
2727
- let formattedValue = value;
2728
- if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2729
- formattedValue = `(${value.join(",")})`;
2730
- } else if (value === null) {
2731
- formattedValue = "null";
2568
+ const column = columnOrCondition;
2569
+ const condition = [operator, value];
2570
+ const existing = this.params.where[column];
2571
+ if (existing === void 0) {
2572
+ this.params.where[column] = condition;
2573
+ } else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
2574
+ this.params.where[column].push(condition);
2575
+ } else {
2576
+ let firstCondition;
2577
+ if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
2578
+ firstCondition = existing;
2579
+ } else {
2580
+ firstCondition = ["==", existing];
2581
+ }
2582
+ this.params.where[column] = [firstCondition, condition];
2732
2583
  }
2733
- this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2734
2584
  return this;
2735
2585
  }
2736
2586
  /**
@@ -3053,68 +2903,184 @@ function mapOperator(op) {
3053
2903
  return null;
3054
2904
  }
3055
2905
  }
2906
+ function getLastValue(val) {
2907
+ if (Array.isArray(val)) {
2908
+ return val[val.length - 1];
2909
+ }
2910
+ return val;
2911
+ }
2912
+ function parseLogicalString(str) {
2913
+ str = str.trim();
2914
+ if (str.startsWith("or(") && str.endsWith(")")) {
2915
+ const inner = str.slice(3, -1);
2916
+ return {
2917
+ type: "or",
2918
+ conditions: parseLogicalList(inner)
2919
+ };
2920
+ }
2921
+ if (str.startsWith("and(") && str.endsWith(")")) {
2922
+ const inner = str.slice(4, -1);
2923
+ return {
2924
+ type: "and",
2925
+ conditions: parseLogicalList(inner)
2926
+ };
2927
+ }
2928
+ const firstDot = str.indexOf(".");
2929
+ if (firstDot === -1) {
2930
+ return {
2931
+ column: str,
2932
+ operator: "==",
2933
+ value: true
2934
+ };
2935
+ }
2936
+ const field = str.substring(0, firstDot);
2937
+ const rest = str.substring(firstDot + 1);
2938
+ const secondDot = rest.indexOf(".");
2939
+ let op = "eq";
2940
+ let valStr = rest;
2941
+ if (secondDot !== -1) {
2942
+ op = rest.substring(0, secondDot);
2943
+ valStr = rest.substring(secondDot + 1);
2944
+ } else {
2945
+ op = "eq";
2946
+ valStr = rest;
2947
+ }
2948
+ const rebaseOp = mapOperator(op) || "==";
2949
+ let parsedVal = valStr;
2950
+ if (valStr === "true") parsedVal = true;
2951
+ else if (valStr === "false") parsedVal = false;
2952
+ else if (valStr === "null") parsedVal = null;
2953
+ else if (!isNaN(Number(valStr)) && valStr.trim() !== "") parsedVal = Number(valStr);
2954
+ else if (valStr.startsWith("(")) {
2955
+ const arrayContent = valStr.endsWith(")") ? valStr.slice(1, -1) : valStr.slice(1);
2956
+ parsedVal = arrayContent.split(",").map((v) => {
2957
+ const trimmed = v.trim();
2958
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
2959
+ if (trimmed === "true") return true;
2960
+ if (trimmed === "false") return false;
2961
+ if (trimmed === "null") return null;
2962
+ return trimmed;
2963
+ });
2964
+ }
2965
+ return {
2966
+ column: field,
2967
+ operator: rebaseOp,
2968
+ value: parsedVal
2969
+ };
2970
+ }
2971
+ function parseLogicalList(str) {
2972
+ const list = [];
2973
+ let depth = 0;
2974
+ let current = "";
2975
+ for (let i = 0; i < str.length; i++) {
2976
+ const char = str[i];
2977
+ if (char === "(") depth++;
2978
+ if (char === ")") depth--;
2979
+ if (char === "," && depth === 0) {
2980
+ list.push(parseLogicalString(current));
2981
+ current = "";
2982
+ } else {
2983
+ current += char;
2984
+ }
2985
+ }
2986
+ if (current) {
2987
+ list.push(parseLogicalString(current));
2988
+ }
2989
+ return list;
2990
+ }
3056
2991
  function parseQueryOptions(query) {
3057
2992
  const options2 = {};
3058
- if (query.limit) options2.limit = parseInt(String(query.limit));
3059
- if (query.offset) options2.offset = parseInt(String(query.offset));
3060
- if (query.page) {
3061
- const page = parseInt(String(query.page));
2993
+ const limitVal = getLastValue(query.limit);
2994
+ if (limitVal) options2.limit = parseInt(String(limitVal));
2995
+ const offsetVal = getLastValue(query.offset);
2996
+ if (offsetVal) options2.offset = parseInt(String(offsetVal));
2997
+ const pageVal = getLastValue(query.page);
2998
+ if (pageVal) {
2999
+ const page = parseInt(String(pageVal));
3062
3000
  const limit = options2.limit || 20;
3063
3001
  options2.offset = (page - 1) * limit;
3064
3002
  }
3065
3003
  options2.where = {};
3066
- const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold"];
3004
+ const orVal = getLastValue(query.or);
3005
+ const andVal = getLastValue(query.and);
3006
+ if (orVal) {
3007
+ let orStr = String(orVal).trim();
3008
+ if (orStr.startsWith("(") && orStr.endsWith(")")) {
3009
+ orStr = orStr.slice(1, -1);
3010
+ }
3011
+ options2.logical = {
3012
+ type: "or",
3013
+ conditions: parseLogicalList(orStr)
3014
+ };
3015
+ } else if (andVal) {
3016
+ let andStr = String(andVal).trim();
3017
+ if (andStr.startsWith("(") && andStr.endsWith(")")) {
3018
+ andStr = andStr.slice(1, -1);
3019
+ }
3020
+ options2.logical = {
3021
+ type: "and",
3022
+ conditions: parseLogicalList(andStr)
3023
+ };
3024
+ }
3025
+ const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold", "or", "and"];
3067
3026
  for (const [key, rawValue] of Object.entries(query)) {
3068
3027
  if (reservedQueryKeys.includes(key)) continue;
3069
- const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
3070
- if (typeof value === "string") {
3071
- const parts = value.split(".");
3072
- if (parts.length >= 2) {
3073
- const op = parts[0];
3074
- const val = parts.slice(1).join(".");
3075
- const rebaseOp = mapOperator(op);
3076
- if (rebaseOp) {
3077
- let parsedVal = val;
3078
- if (val === "true") parsedVal = true;
3079
- else if (val === "false") parsedVal = false;
3080
- else if (val === "null") parsedVal = null;
3081
- else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
3082
- else if (val.startsWith("(")) {
3083
- const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
3084
- parsedVal = arrayContent.split(",").map((v) => {
3085
- const trimmed = v.trim();
3086
- if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
3087
- if (trimmed === "true") return true;
3088
- if (trimmed === "false") return false;
3089
- if (trimmed === "null") return null;
3090
- return trimmed;
3091
- });
3028
+ const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
3029
+ const conditions = [];
3030
+ for (const value of rawValues) {
3031
+ if (typeof value === "string") {
3032
+ const parts = value.split(".");
3033
+ if (parts.length >= 2) {
3034
+ const op = parts[0];
3035
+ const val = parts.slice(1).join(".");
3036
+ const rebaseOp = mapOperator(op);
3037
+ if (rebaseOp) {
3038
+ let parsedVal = val;
3039
+ if (val === "true") parsedVal = true;
3040
+ else if (val === "false") parsedVal = false;
3041
+ else if (val === "null") parsedVal = null;
3042
+ else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
3043
+ else if (val.startsWith("(")) {
3044
+ const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
3045
+ parsedVal = arrayContent.split(",").map((v) => {
3046
+ const trimmed = v.trim();
3047
+ if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
3048
+ if (trimmed === "true") return true;
3049
+ if (trimmed === "false") return false;
3050
+ if (trimmed === "null") return null;
3051
+ return trimmed;
3052
+ });
3053
+ }
3054
+ conditions.push([rebaseOp, parsedVal]);
3055
+ } else {
3056
+ let parsedVal = value;
3057
+ if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3058
+ conditions.push(["==", parsedVal]);
3092
3059
  }
3093
- options2.where[key] = [rebaseOp, parsedVal];
3094
3060
  } else {
3095
3061
  let parsedVal = value;
3096
- if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3097
- options2.where[key] = ["==", parsedVal];
3062
+ if (value === "true") parsedVal = true;
3063
+ else if (value === "false") parsedVal = false;
3064
+ else if (value === "null") parsedVal = null;
3065
+ else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3066
+ conditions.push(["==", parsedVal]);
3098
3067
  }
3099
- } else {
3100
- let parsedVal = value;
3101
- if (value === "true") parsedVal = true;
3102
- else if (value === "false") parsedVal = false;
3103
- else if (value === "null") parsedVal = null;
3104
- else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
3105
- options2.where[key] = ["==", parsedVal];
3106
3068
  }
3107
3069
  }
3070
+ if (conditions.length > 0) {
3071
+ options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
3072
+ }
3108
3073
  }
3109
3074
  if (Object.keys(options2.where).length === 0) {
3110
3075
  delete options2.where;
3111
3076
  }
3112
- if (query.orderBy) {
3077
+ const orderByVal = getLastValue(query.orderBy);
3078
+ if (orderByVal) {
3113
3079
  try {
3114
- options2.orderBy = typeof query.orderBy === "string" ? JSON.parse(query.orderBy) : query.orderBy;
3080
+ options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
3115
3081
  } catch {
3116
- if (typeof query.orderBy === "string") {
3117
- const [field, direction] = query.orderBy.split(":");
3082
+ if (typeof orderByVal === "string") {
3083
+ const [field, direction] = orderByVal.split(":");
3118
3084
  const dir = direction === "desc" ? "desc" : "asc";
3119
3085
  options2.orderBy = [{
3120
3086
  field,
@@ -3123,20 +3089,24 @@ function parseQueryOptions(query) {
3123
3089
  }
3124
3090
  }
3125
3091
  }
3126
- if (query.include) {
3127
- const includeStr = String(query.include).trim();
3092
+ const includeVal = getLastValue(query.include);
3093
+ if (includeVal) {
3094
+ const includeStr = String(includeVal).trim();
3128
3095
  if (includeStr === "*") {
3129
3096
  options2.include = ["*"];
3130
3097
  } else {
3131
3098
  options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
3132
3099
  }
3133
3100
  }
3134
- if (query.fields) {
3135
- const fieldsStr = String(query.fields).trim();
3101
+ const fieldsVal = getLastValue(query.fields);
3102
+ if (fieldsVal) {
3103
+ const fieldsStr = String(fieldsVal).trim();
3136
3104
  options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
3137
3105
  }
3138
- if (query.vector_search && query.vector) {
3139
- const vectorStr = String(query.vector);
3106
+ const vectorSearchVal = getLastValue(query.vector_search);
3107
+ const vectorVal = getLastValue(query.vector);
3108
+ if (vectorSearchVal && vectorVal) {
3109
+ const vectorStr = String(vectorVal);
3140
3110
  let queryVector;
3141
3111
  try {
3142
3112
  queryVector = JSON.parse(vectorStr);
@@ -3146,17 +3116,19 @@ function parseQueryOptions(query) {
3146
3116
  } catch {
3147
3117
  throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
3148
3118
  }
3149
- const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
3119
+ const distanceParamVal = getLastValue(query.vector_distance);
3120
+ const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
3150
3121
  if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
3151
3122
  throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
3152
3123
  }
3153
3124
  const vectorSearch = {
3154
- property: String(query.vector_search),
3125
+ property: String(vectorSearchVal),
3155
3126
  vector: queryVector,
3156
3127
  distance: distanceParam
3157
3128
  };
3158
- if (query.vector_threshold) {
3159
- const threshold = parseFloat(String(query.vector_threshold));
3129
+ const thresholdVal = getLastValue(query.vector_threshold);
3130
+ if (thresholdVal) {
3131
+ const threshold = parseFloat(String(thresholdVal));
3160
3132
  if (isNaN(threshold)) {
3161
3133
  throw new Error("Invalid vector_threshold. Expected a number.");
3162
3134
  }
@@ -3251,9 +3223,9 @@ class RestApiGenerator {
3251
3223
  const resolvedCollection = collection;
3252
3224
  this.router.get(`${basePath}/count`, async (c) => {
3253
3225
  this.enforceApiKeyPermission(c, collection.slug);
3254
- const queryDict = c.req.query();
3226
+ const queryDict = c.req.queries();
3255
3227
  const queryOptions = parseQueryOptions(queryDict);
3256
- const searchString = queryDict.searchString;
3228
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3257
3229
  const driver = c.get("driver") || this.driver;
3258
3230
  const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
3259
3231
  return c.json({
@@ -3262,9 +3234,9 @@ class RestApiGenerator {
3262
3234
  });
3263
3235
  this.router.get(basePath, async (c) => {
3264
3236
  this.enforceApiKeyPermission(c, collection.slug);
3265
- const queryDict = c.req.query();
3237
+ const queryDict = c.req.queries();
3266
3238
  const queryOptions = parseQueryOptions(queryDict);
3267
- const searchString = queryDict.searchString;
3239
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3268
3240
  const driver = c.get("driver") || this.driver;
3269
3241
  const fetchService = this.getFetchService(driver);
3270
3242
  const hookCtx = this.buildHookContext(c, "GET");
@@ -3307,7 +3279,7 @@ class RestApiGenerator {
3307
3279
  this.router.get(`${basePath}/:id`, async (c) => {
3308
3280
  this.enforceApiKeyPermission(c, collection.slug);
3309
3281
  const id = c.req.param("id");
3310
- const queryDict = c.req.query();
3282
+ const queryDict = c.req.queries();
3311
3283
  const queryOptions = parseQueryOptions(queryDict);
3312
3284
  const driver = c.get("driver") || this.driver;
3313
3285
  const fetchService = this.getFetchService(driver);
@@ -3476,12 +3448,13 @@ class RestApiGenerator {
3476
3448
  const driver = c.get("driver") || this.driver;
3477
3449
  this.enforceApiKeyPermission(c, c.req.param("parent"));
3478
3450
  if (parsed.entityId === "count") {
3479
- const queryDict = c.req.query();
3451
+ const queryDict = c.req.queries();
3480
3452
  const queryOptions = parseQueryOptions(queryDict);
3453
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3481
3454
  const total = driver.countEntities ? await driver.countEntities({
3482
3455
  path: parsed.collectionPath,
3483
3456
  filter: queryOptions.where,
3484
- searchString: queryDict.searchString
3457
+ searchString
3485
3458
  }) : 0;
3486
3459
  return c.json({
3487
3460
  count: total
@@ -3494,15 +3467,16 @@ class RestApiGenerator {
3494
3467
  if (!entity) throw ApiError.notFound("Entity not found");
3495
3468
  return c.json(this.flattenEntity(entity));
3496
3469
  } else {
3497
- const queryDict = c.req.query();
3470
+ const queryDict = c.req.queries();
3498
3471
  const queryOptions = parseQueryOptions(queryDict);
3472
+ const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
3499
3473
  const entities = await driver.fetchCollection({
3500
3474
  path: parsed.collectionPath,
3501
3475
  filter: queryOptions.where,
3502
3476
  limit: queryOptions.limit,
3503
3477
  orderBy: queryOptions.orderBy?.[0]?.field,
3504
3478
  order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
3505
- searchString: queryDict.searchString
3479
+ searchString
3506
3480
  });
3507
3481
  return c.json({
3508
3482
  data: entities.map((e) => this.flattenEntity(e)),
@@ -26229,15 +26203,33 @@ function normalizeWhereValue(value) {
26229
26203
  if (value === null) return "eq.null";
26230
26204
  if (typeof value === "boolean") return `eq.${value}`;
26231
26205
  if (typeof value === "number") return String(value);
26232
- if (Array.isArray(value) && value.length === 2) {
26233
- const [rawOp, val] = value;
26234
- const op = OP_MAP[rawOp] ?? rawOp;
26235
- if (val === null) return `${op}.null`;
26236
- if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26237
- return `${op}.${val}`;
26206
+ if (Array.isArray(value)) {
26207
+ const conditions = Array.isArray(value[0]) ? value : [value];
26208
+ const [rawOp, val] = conditions[0] || [];
26209
+ if (rawOp) {
26210
+ const op = OP_MAP[rawOp] ?? rawOp;
26211
+ if (val === null) return `${op}.null`;
26212
+ if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
26213
+ return `${op}.${val}`;
26214
+ }
26238
26215
  }
26239
26216
  return String(value);
26240
26217
  }
26218
+ function serializeLogicalCondition(cond2) {
26219
+ if ("type" in cond2) {
26220
+ const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
26221
+ return `${cond2.type}(${sub})`;
26222
+ } else {
26223
+ const op = OP_MAP[cond2.operator] ?? cond2.operator;
26224
+ let formattedValue = cond2.value;
26225
+ if (Array.isArray(cond2.value)) {
26226
+ formattedValue = `(${cond2.value.join(",")})`;
26227
+ } else if (cond2.value === null) {
26228
+ formattedValue = "null";
26229
+ }
26230
+ return `${cond2.column}.${op}.${formattedValue}`;
26231
+ }
26232
+ }
26241
26233
  function buildQueryString(params) {
26242
26234
  if (!params) return "";
26243
26235
  const parts = [];
@@ -26253,10 +26245,22 @@ function buildQueryString(params) {
26253
26245
  if (params.include && params.include.length > 0) {
26254
26246
  parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
26255
26247
  }
26248
+ if (params.logical) {
26249
+ const root = params.logical;
26250
+ const serialized = root.conditions.map(serializeLogicalCondition).join(",");
26251
+ parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
26252
+ }
26256
26253
  if (params.where) {
26257
26254
  for (const [field, value] of Object.entries(params.where)) {
26258
- const normalized2 = normalizeWhereValue(value);
26259
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26255
+ if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
26256
+ for (const subVal of value) {
26257
+ const normalized2 = normalizeWhereValue(subVal);
26258
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26259
+ }
26260
+ } else {
26261
+ const normalized2 = normalizeWhereValue(value);
26262
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
26263
+ }
26260
26264
  }
26261
26265
  }
26262
26266
  return parts.length > 0 ? "?" + parts.join("&") : "";
@@ -27008,11 +27012,18 @@ function createCollectionClient(transport, slug, ws) {
27008
27012
  };
27009
27013
  },
27010
27014
  async findById(id) {
27011
- const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27012
- method: "GET"
27013
- });
27014
- if (!raw) return void 0;
27015
- return rowToEntity(raw, slug);
27015
+ try {
27016
+ const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
27017
+ method: "GET"
27018
+ });
27019
+ if (!raw) return void 0;
27020
+ return rowToEntity(raw, slug);
27021
+ } catch (err) {
27022
+ if (err instanceof RebaseApiError && err.status === 404) {
27023
+ return void 0;
27024
+ }
27025
+ throw err;
27026
+ }
27016
27027
  },
27017
27028
  async create(data, id) {
27018
27029
  const body = {
@@ -27052,8 +27063,12 @@ function createCollectionClient(transport, slug, ws) {
27052
27063
  return raw.count ?? 0;
27053
27064
  },
27054
27065
  // Fluent builder instantiation
27055
- where(column, operator, value) {
27056
- return new QueryBuilder(client).where(column, operator, value);
27066
+ where(columnOrCondition, operator, value) {
27067
+ const builder = new QueryBuilder(client);
27068
+ if (typeof columnOrCondition === "object") {
27069
+ return builder.where(columnOrCondition);
27070
+ }
27071
+ return builder.where(columnOrCondition, operator, value);
27057
27072
  },
27058
27073
  orderBy(column, ascending) {
27059
27074
  return new QueryBuilder(client).orderBy(column, ascending);
@@ -38331,13 +38346,35 @@ var createTransport = function(transporter, defaults) {
38331
38346
  mailer2 = new Mailer(transporter, options2, defaults);
38332
38347
  return mailer2;
38333
38348
  };
38349
+ function getHostname(urlStr) {
38350
+ try {
38351
+ const url = new URL(urlStr.includes("://") ? urlStr : `https://${urlStr}`);
38352
+ return url.hostname;
38353
+ } catch {
38354
+ return void 0;
38355
+ }
38356
+ }
38334
38357
  class SMTPEmailService {
38335
38358
  transporter = null;
38336
38359
  config;
38337
38360
  constructor(config) {
38338
38361
  this.config = config;
38339
38362
  if (config.smtp) {
38363
+ let smtpName = config.smtp.name;
38364
+ if (!smtpName) {
38365
+ const urlsToTry = [process.env.FRONTEND_URL, config.resetPasswordUrl, config.verifyEmailUrl];
38366
+ for (const urlStr of urlsToTry) {
38367
+ if (urlStr) {
38368
+ const hostname = getHostname(urlStr);
38369
+ if (hostname) {
38370
+ smtpName = hostname;
38371
+ break;
38372
+ }
38373
+ }
38374
+ }
38375
+ }
38340
38376
  this.transporter = createTransport({
38377
+ name: smtpName,
38341
38378
  host: config.smtp.host,
38342
38379
  port: config.smtp.port,
38343
38380
  secure: config.smtp.secure ?? config.smtp.port === 465,
@@ -38487,8 +38524,13 @@ async function _initializeRebaseBackend(config) {
38487
38524
  dir: config.collectionsDir
38488
38525
  });
38489
38526
  }
38490
- if (config.auth) {
38491
- activeCollections = Array.from(new Map([defaultUsersCollection, ...activeCollections].map((c) => [c.slug, c])).values());
38527
+ if (config.defaultSecurityRules?.length) {
38528
+ for (const collection of activeCollections) {
38529
+ if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
38530
+ collection.securityRules = config.defaultSecurityRules;
38531
+ }
38532
+ }
38533
+ logger.info("Default security rules applied to collections without explicit rules");
38492
38534
  }
38493
38535
  const realtimeServices = {};
38494
38536
  const delegates = {};
@@ -38903,6 +38945,19 @@ async function _initializeRebaseBackend(config) {
38903
38945
  logger.info("Email service attached to singleton", {
38904
38946
  configured: emailService.isConfigured()
38905
38947
  });
38948
+ if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
38949
+ emailService.verifyConnection().then((success) => {
38950
+ if (!success) {
38951
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
38952
+ } else {
38953
+ logger.info("SMTP connection verified successfully.");
38954
+ }
38955
+ }).catch((err) => {
38956
+ logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", {
38957
+ error: err
38958
+ });
38959
+ });
38960
+ }
38906
38961
  }
38907
38962
  const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
38908
38963
  if (isSQLAdmin(driverAdmin)) {