@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.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
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
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
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
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
|
-
|
|
3046
|
-
if (
|
|
3047
|
-
|
|
3048
|
-
|
|
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
|
|
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
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
if (
|
|
3060
|
-
const
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
if (
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
const
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
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 (
|
|
3084
|
-
|
|
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
|
-
|
|
3064
|
+
const orderByVal = getLastValue(query.orderBy);
|
|
3065
|
+
if (orderByVal) {
|
|
3100
3066
|
try {
|
|
3101
|
-
options2.orderBy = typeof
|
|
3067
|
+
options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
|
|
3102
3068
|
} catch {
|
|
3103
|
-
if (typeof
|
|
3104
|
-
const [field, direction] =
|
|
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
|
-
|
|
3114
|
-
|
|
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
|
-
|
|
3122
|
-
|
|
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
|
-
|
|
3126
|
-
|
|
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
|
|
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(
|
|
3112
|
+
property: String(vectorSearchVal),
|
|
3142
3113
|
vector: queryVector,
|
|
3143
3114
|
distance: distanceParam
|
|
3144
3115
|
};
|
|
3145
|
-
|
|
3146
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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)
|
|
26267
|
-
const [
|
|
26268
|
-
const
|
|
26269
|
-
if (
|
|
26270
|
-
|
|
26271
|
-
|
|
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
|
-
|
|
26293
|
-
|
|
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
|
-
|
|
27046
|
-
|
|
27047
|
-
|
|
27048
|
-
|
|
27049
|
-
|
|
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(
|
|
27090
|
-
|
|
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.
|
|
38525
|
-
|
|
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)) {
|