@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/common/src/data/query_builder.d.ts +6 -2
- package/dist/index.es.js +306 -251
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +306 -251
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/rest/query-parser.d.ts +2 -0
- package/dist/server-core/src/api/types.d.ts +2 -1
- package/dist/server-core/src/email/types.d.ts +1 -0
- package/dist/server-core/src/init.d.ts +31 -1
- package/dist/types/src/controllers/data.d.ts +17 -3
- package/dist/types/src/controllers/email.d.ts +2 -0
- package/dist/types/src/types/collections.d.ts +9 -5
- package/dist/types/src/types/entity_views.d.ts +19 -28
- package/dist/types/src/types/properties.d.ts +2 -2
- package/package.json +5 -5
- package/src/api/rest/api-generator.ts +11 -9
- package/src/api/rest/query-parser.ts +184 -63
- package/src/api/types.ts +2 -1
- package/src/email/smtp-email-service.ts +31 -0
- package/src/email/types.ts +1 -0
- package/src/init.ts +123 -29
- package/src/storage/image-transform.ts +2 -1
- package/test/smtp-email-service.test.ts +169 -0
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
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
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
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
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
|
-
|
|
3059
|
-
if (
|
|
3060
|
-
|
|
3061
|
-
|
|
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
|
|
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
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
if (
|
|
3073
|
-
const
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
if (
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
const
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
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 (
|
|
3097
|
-
|
|
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
|
-
|
|
3077
|
+
const orderByVal = getLastValue(query.orderBy);
|
|
3078
|
+
if (orderByVal) {
|
|
3113
3079
|
try {
|
|
3114
|
-
options2.orderBy = typeof
|
|
3080
|
+
options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
|
|
3115
3081
|
} catch {
|
|
3116
|
-
if (typeof
|
|
3117
|
-
const [field, direction] =
|
|
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
|
-
|
|
3127
|
-
|
|
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
|
-
|
|
3135
|
-
|
|
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
|
-
|
|
3139
|
-
|
|
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
|
|
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(
|
|
3125
|
+
property: String(vectorSearchVal),
|
|
3155
3126
|
vector: queryVector,
|
|
3156
3127
|
distance: distanceParam
|
|
3157
3128
|
};
|
|
3158
|
-
|
|
3159
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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)
|
|
26233
|
-
const [
|
|
26234
|
-
const
|
|
26235
|
-
if (
|
|
26236
|
-
|
|
26237
|
-
|
|
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
|
-
|
|
26259
|
-
|
|
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
|
-
|
|
27012
|
-
|
|
27013
|
-
|
|
27014
|
-
|
|
27015
|
-
|
|
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(
|
|
27056
|
-
|
|
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.
|
|
38491
|
-
|
|
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)) {
|