@rebasepro/client 0.7.0 → 0.8.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/LICENSE +21 -0
- package/dist/api-keys.d.ts +1 -0
- package/dist/collection.d.ts +3 -3
- package/dist/index.d.ts +20 -1
- package/dist/index.es.js +135 -168
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +138 -170
- package/dist/index.umd.js.map +1 -1
- package/dist/storage-registry.d.ts +42 -0
- package/dist/storage.d.ts +9 -1
- package/package.json +13 -12
- package/src/api-keys.ts +1 -0
- package/src/collection.test.ts +2 -2
- package/src/collection.ts +41 -119
- package/src/index.ts +77 -2
- package/src/storage-registry.ts +102 -0
- package/src/storage.ts +30 -20
- package/src/transport.ts +8 -82
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rebase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/api-keys.d.ts
CHANGED
package/dist/collection.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FindParams, Transport } from "./transport";
|
|
2
2
|
import { RebaseWebSocketClient } from "./websocket";
|
|
3
|
-
import { CollectionAccessor,
|
|
3
|
+
import { CollectionAccessor, LogicalCondition, WhereFilterOp, WhereValue } from "@rebasepro/types";
|
|
4
4
|
import { QueryBuilder } from "./query_builder";
|
|
5
5
|
/**
|
|
6
6
|
* CollectionClient extends `CollectionAccessor` from `@rebasepro/types` so that
|
|
@@ -9,9 +9,9 @@ import { QueryBuilder } from "./query_builder";
|
|
|
9
9
|
* Additionally it exposes fluent query builder methods like `.where()`, `.orderBy()`.
|
|
10
10
|
*/
|
|
11
11
|
export interface CollectionClient<M extends Record<string, unknown> = Record<string, unknown>> extends CollectionAccessor<M> {
|
|
12
|
-
where<K extends keyof M & string>(column: K, operator:
|
|
12
|
+
where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): QueryBuilder<M>;
|
|
13
13
|
where(logicalCondition: LogicalCondition): QueryBuilder<M>;
|
|
14
|
-
orderBy(column: keyof M & string,
|
|
14
|
+
orderBy(column: keyof M & string, direction?: "asc" | "desc"): QueryBuilder<M>;
|
|
15
15
|
limit(count: number): QueryBuilder<M>;
|
|
16
16
|
offset(count: number): QueryBuilder<M>;
|
|
17
17
|
search(searchString: string): QueryBuilder<M>;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { createApiKeys, CreateApiKeysOptions } from "./api-keys";
|
|
|
6
6
|
import { CollectionClient } from "./collection";
|
|
7
7
|
import { createFunctionsClient } from "./functions";
|
|
8
8
|
import { RebaseWebSocketClient } from "./websocket";
|
|
9
|
-
import { RebaseClient, RebaseData, StorageSource } from "@rebasepro/types";
|
|
9
|
+
import { RebaseClient, RebaseData, StorageSource, StorageSourceDefinition, StorageSourceRegistry } from "@rebasepro/types";
|
|
10
10
|
export * from "./transport";
|
|
11
11
|
export * from "./auth";
|
|
12
12
|
export * from "./admin";
|
|
@@ -16,6 +16,7 @@ export * from "./collection";
|
|
|
16
16
|
export * from "./query_builder";
|
|
17
17
|
export * from "./websocket";
|
|
18
18
|
export * from "./storage";
|
|
19
|
+
export * from "./storage-registry";
|
|
19
20
|
export * from "./reviver";
|
|
20
21
|
export * from "./functions";
|
|
21
22
|
export type { Entity, FindResponse } from "@rebasepro/types";
|
|
@@ -24,6 +25,21 @@ export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
|
24
25
|
admin?: CreateAdminOptions;
|
|
25
26
|
cron?: CreateCronOptions;
|
|
26
27
|
apiKeys?: CreateApiKeysOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Declared storage sources for multi-backend support. Server-transport
|
|
30
|
+
* entries are auto-wired into `client.storageRegistry`; `direct` sources
|
|
31
|
+
* are registered app-side (e.g. via a Firebase Storage hook). The default
|
|
32
|
+
* source (`storage`) is always registered under
|
|
33
|
+
* {@link DEFAULT_STORAGE_SOURCE_KEY}.
|
|
34
|
+
*/
|
|
35
|
+
storageSources?: StorageSourceDefinition[];
|
|
36
|
+
/**
|
|
37
|
+
* Maps camelCase property names / safe identifiers to the actual
|
|
38
|
+
* collection slugs on the server (e.g. `{ companyMembers: "company-members" }`).
|
|
39
|
+
* If provided, the data layer proxy will resolve property accessors to their
|
|
40
|
+
* correct slugs via this map before falling back to automatic snake_casing.
|
|
41
|
+
*/
|
|
42
|
+
collections?: Record<string, string>;
|
|
27
43
|
}
|
|
28
44
|
type KebabToCamelCase<S extends string> = S extends `${infer T}-${infer U}` ? `${T}${Capitalize<KebabToCamelCase<U>>}` : S;
|
|
29
45
|
type TypedDataLayer<DB> = {
|
|
@@ -54,6 +70,9 @@ export type CreateRebaseClientResult<DB = Record<string, unknown>> = Omit<Rebase
|
|
|
54
70
|
functions: ReturnType<typeof createFunctionsClient>;
|
|
55
71
|
ws?: RebaseWebSocketClient;
|
|
56
72
|
storage: StorageSource;
|
|
73
|
+
storageRegistry: StorageSourceRegistry;
|
|
74
|
+
createStorageSource: (storageId: string) => StorageSource;
|
|
75
|
+
fetchStorageSources: () => Promise<StorageSourceDefinition[]>;
|
|
57
76
|
call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
|
|
58
77
|
data: TypedDataLayer<DB>;
|
|
59
78
|
};
|
package/dist/index.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { QueryBuilder, and, cond, or, serializeFilter, serializeLogicalCondition } from "@rebasepro/common";
|
|
2
|
+
import { DEFAULT_STORAGE_SOURCE_KEY, EntityReference, EntityRelation, GeoPoint, Vector } from "@rebasepro/types";
|
|
3
3
|
import { toSnakeCase } from "@rebasepro/utils";
|
|
4
4
|
//#region src/reviver.ts
|
|
5
5
|
function rebaseReviver(_key, value) {
|
|
@@ -42,61 +42,6 @@ var RebaseApiError = class extends Error {
|
|
|
42
42
|
this.details = details;
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
|
-
/**
|
|
46
|
-
* Maps a short operator alias to the PostgREST-style short code.
|
|
47
|
-
*/
|
|
48
|
-
var OP_MAP = {
|
|
49
|
-
"==": "eq",
|
|
50
|
-
"!=": "neq",
|
|
51
|
-
">": "gt",
|
|
52
|
-
">=": "gte",
|
|
53
|
-
"<": "lt",
|
|
54
|
-
"<=": "lte",
|
|
55
|
-
"not-in": "nin",
|
|
56
|
-
"array-contains": "cs",
|
|
57
|
-
"array-contains-any": "csa"
|
|
58
|
-
};
|
|
59
|
-
/**
|
|
60
|
-
* Normalise a single `WhereFieldValue` into the PostgREST query-string
|
|
61
|
-
* representation the backend expects.
|
|
62
|
-
*
|
|
63
|
-
* Supports:
|
|
64
|
-
* - `null` → `"eq.null"`
|
|
65
|
-
* - `true`/`false` → `"eq.true"` / `"eq.false"`
|
|
66
|
-
* - `42` → `"42"` (plain equality)
|
|
67
|
-
* - `"active"` → `"active"` (plain equality, backward-compat)
|
|
68
|
-
* - `"gte.18"` → `"gte.18"` (pass-through PostgREST string)
|
|
69
|
-
* - `[">=", 18]` → `"gte.18"` (tuple syntax)
|
|
70
|
-
* - `["in", [1,2]]` → `"in.(1,2)"` (tuple with array value)
|
|
71
|
-
* - `["!=", null]` → `"neq.null"`
|
|
72
|
-
*/
|
|
73
|
-
function normalizeWhereValue(value) {
|
|
74
|
-
if (value === null) return "eq.null";
|
|
75
|
-
if (typeof value === "boolean") return `eq.${value}`;
|
|
76
|
-
if (typeof value === "number") return String(value);
|
|
77
|
-
if (Array.isArray(value)) {
|
|
78
|
-
const [rawOp, val] = (Array.isArray(value[0]) ? value : [value])[0] || [];
|
|
79
|
-
if (rawOp) {
|
|
80
|
-
const op = OP_MAP[rawOp] ?? rawOp;
|
|
81
|
-
if (val === null) return `${op}.null`;
|
|
82
|
-
if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
|
|
83
|
-
return `${op}.${val}`;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return String(value);
|
|
87
|
-
}
|
|
88
|
-
function serializeLogicalCondition(cond) {
|
|
89
|
-
if ("type" in cond) {
|
|
90
|
-
const sub = (cond.conditions ?? []).map(serializeLogicalCondition).join(",");
|
|
91
|
-
return `${cond.type}(${sub})`;
|
|
92
|
-
} else {
|
|
93
|
-
const op = OP_MAP[cond.operator] ?? cond.operator;
|
|
94
|
-
let formattedValue = cond.value;
|
|
95
|
-
if (Array.isArray(cond.value)) formattedValue = `(${cond.value.join(",")})`;
|
|
96
|
-
else if (cond.value === null) formattedValue = "null";
|
|
97
|
-
return `${cond.column}.${op}.${formattedValue}`;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
45
|
function buildQueryString(params) {
|
|
101
46
|
if (!params) return "";
|
|
102
47
|
const parts = [];
|
|
@@ -111,13 +56,10 @@ function buildQueryString(params) {
|
|
|
111
56
|
const serialized = (root.conditions ?? []).map(serializeLogicalCondition).join(",");
|
|
112
57
|
parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
|
|
113
58
|
}
|
|
114
|
-
if (params.where)
|
|
115
|
-
const
|
|
116
|
-
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(
|
|
117
|
-
|
|
118
|
-
else {
|
|
119
|
-
const normalized = normalizeWhereValue(value);
|
|
120
|
-
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
|
|
59
|
+
if (params.where) {
|
|
60
|
+
const serialized = serializeFilter(params.where);
|
|
61
|
+
for (const [field, value] of Object.entries(serialized)) if (Array.isArray(value)) for (const v of value) parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(v)}`);
|
|
62
|
+
else parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(value)}`);
|
|
121
63
|
}
|
|
122
64
|
return parts.length > 0 ? "?" + parts.join("&") : "";
|
|
123
65
|
}
|
|
@@ -869,94 +811,6 @@ function createApiKeys(transport, options) {
|
|
|
869
811
|
}
|
|
870
812
|
//#endregion
|
|
871
813
|
//#region src/collection.ts
|
|
872
|
-
function parseWhereFilter(where) {
|
|
873
|
-
if (!where) return void 0;
|
|
874
|
-
const filters = {};
|
|
875
|
-
const OP_TO_FILTER = {
|
|
876
|
-
"eq": "==",
|
|
877
|
-
"neq": "!=",
|
|
878
|
-
"gt": ">",
|
|
879
|
-
"gte": ">=",
|
|
880
|
-
"lt": "<",
|
|
881
|
-
"lte": "<=",
|
|
882
|
-
"==": "==",
|
|
883
|
-
"!=": "!=",
|
|
884
|
-
">": ">",
|
|
885
|
-
">=": ">=",
|
|
886
|
-
"<": "<",
|
|
887
|
-
"<=": "<=",
|
|
888
|
-
"in": "in",
|
|
889
|
-
"nin": "not-in",
|
|
890
|
-
"not-in": "not-in",
|
|
891
|
-
"cs": "array-contains",
|
|
892
|
-
"csa": "array-contains-any",
|
|
893
|
-
"array-contains": "array-contains",
|
|
894
|
-
"array-contains-any": "array-contains-any"
|
|
895
|
-
};
|
|
896
|
-
const parseSingle = (rawValue, fieldKey) => {
|
|
897
|
-
if (rawValue === null) return ["==", null];
|
|
898
|
-
if (typeof rawValue === "boolean") return ["==", rawValue];
|
|
899
|
-
if (typeof rawValue === "number") return ["==", rawValue];
|
|
900
|
-
if (Array.isArray(rawValue) && rawValue.length === 2 && typeof rawValue[0] === "string") {
|
|
901
|
-
const [rawOp, val] = rawValue;
|
|
902
|
-
return [OP_TO_FILTER[rawOp] ?? "==", val];
|
|
903
|
-
}
|
|
904
|
-
const value = String(rawValue);
|
|
905
|
-
const dotIndex = value.indexOf(".");
|
|
906
|
-
if (dotIndex > 0) {
|
|
907
|
-
const opStr = value.substring(0, dotIndex);
|
|
908
|
-
const valStr = value.substring(dotIndex + 1);
|
|
909
|
-
let op = "==";
|
|
910
|
-
let val = valStr;
|
|
911
|
-
switch (opStr) {
|
|
912
|
-
case "eq":
|
|
913
|
-
op = "==";
|
|
914
|
-
break;
|
|
915
|
-
case "neq":
|
|
916
|
-
op = "!=";
|
|
917
|
-
break;
|
|
918
|
-
case "gt":
|
|
919
|
-
op = ">";
|
|
920
|
-
break;
|
|
921
|
-
case "gte":
|
|
922
|
-
op = ">=";
|
|
923
|
-
break;
|
|
924
|
-
case "lt":
|
|
925
|
-
op = "<";
|
|
926
|
-
break;
|
|
927
|
-
case "lte":
|
|
928
|
-
op = "<=";
|
|
929
|
-
break;
|
|
930
|
-
case "in":
|
|
931
|
-
op = "in";
|
|
932
|
-
val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
|
|
933
|
-
break;
|
|
934
|
-
case "nin":
|
|
935
|
-
op = "not-in";
|
|
936
|
-
val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
|
|
937
|
-
break;
|
|
938
|
-
case "cs":
|
|
939
|
-
op = "array-contains";
|
|
940
|
-
break;
|
|
941
|
-
case "csa":
|
|
942
|
-
op = "array-contains-any";
|
|
943
|
-
val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
|
|
944
|
-
break;
|
|
945
|
-
default:
|
|
946
|
-
op = "==";
|
|
947
|
-
val = value;
|
|
948
|
-
}
|
|
949
|
-
if (val === "true") val = true;
|
|
950
|
-
else if (val === "false") val = false;
|
|
951
|
-
else if (val === "null") val = null;
|
|
952
|
-
else if (typeof val === "string" && /^[0-9]+(\.[0-9]+)?$/.test(val) && fieldKey !== "id" && !fieldKey.endsWith("_id")) val = Number(val);
|
|
953
|
-
return [op, val];
|
|
954
|
-
} else return ["==", value];
|
|
955
|
-
};
|
|
956
|
-
for (const [key, rawValue] of Object.entries(where)) if (Array.isArray(rawValue) && rawValue.length > 0 && Array.isArray(rawValue[0])) filters[key] = rawValue.map((r) => parseSingle(r, key));
|
|
957
|
-
else filters[key] = parseSingle(rawValue, key);
|
|
958
|
-
return filters;
|
|
959
|
-
}
|
|
960
814
|
/**
|
|
961
815
|
* Wrap a flat row (returned by the REST API as `{ id, ...fields }`) into
|
|
962
816
|
* a proper `Entity<M>` structure expected by the core framework.
|
|
@@ -1021,8 +875,8 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
1021
875
|
if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
|
|
1022
876
|
return builder.where(columnOrCondition, operator, value);
|
|
1023
877
|
},
|
|
1024
|
-
orderBy(column,
|
|
1025
|
-
return new QueryBuilder(client).orderBy(column,
|
|
878
|
+
orderBy(column, direction) {
|
|
879
|
+
return new QueryBuilder(client).orderBy(column, direction);
|
|
1026
880
|
},
|
|
1027
881
|
limit(count) {
|
|
1028
882
|
return new QueryBuilder(client).limit(count);
|
|
@@ -1039,26 +893,45 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
1039
893
|
};
|
|
1040
894
|
if (ws) {
|
|
1041
895
|
client.listen = (params, onUpdate, onError) => {
|
|
1042
|
-
|
|
896
|
+
let active = true;
|
|
897
|
+
let lastUpdateId = 0;
|
|
898
|
+
const unsub = ws.listenCollection({
|
|
1043
899
|
path: slug,
|
|
1044
|
-
filter:
|
|
900
|
+
filter: params?.where,
|
|
1045
901
|
limit: params?.limit,
|
|
1046
902
|
startAfter: params?.offset ? String(params.offset) : void 0,
|
|
1047
903
|
orderBy: params?.orderBy?.split(":")[0],
|
|
1048
904
|
order: params?.orderBy?.split(":")[1],
|
|
1049
905
|
searchString: params?.searchString
|
|
1050
906
|
}, (entities) => {
|
|
907
|
+
const currentUpdateId = ++lastUpdateId;
|
|
1051
908
|
const requestedLimit = params?.limit || 20;
|
|
909
|
+
const offset = params?.offset || 0;
|
|
1052
910
|
onUpdate({
|
|
1053
911
|
data: entities,
|
|
1054
912
|
meta: {
|
|
1055
913
|
total: entities.length,
|
|
1056
914
|
limit: requestedLimit,
|
|
1057
|
-
offset
|
|
915
|
+
offset,
|
|
1058
916
|
hasMore: entities.length >= requestedLimit
|
|
1059
917
|
}
|
|
1060
918
|
});
|
|
919
|
+
if (client.count) client.count(params).then((total) => {
|
|
920
|
+
if (active && currentUpdateId === lastUpdateId) onUpdate({
|
|
921
|
+
data: entities,
|
|
922
|
+
meta: {
|
|
923
|
+
total,
|
|
924
|
+
limit: requestedLimit,
|
|
925
|
+
offset,
|
|
926
|
+
hasMore: offset + entities.length < total
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}).catch(() => {});
|
|
1061
930
|
}, onError);
|
|
931
|
+
return () => {
|
|
932
|
+
active = false;
|
|
933
|
+
unsub();
|
|
934
|
+
};
|
|
1062
935
|
};
|
|
1063
936
|
client.listenById = (id, onUpdate, onError) => {
|
|
1064
937
|
return ws.listenEntity({
|
|
@@ -1098,17 +971,31 @@ function createFunctionsClient(transport) {
|
|
|
1098
971
|
}
|
|
1099
972
|
//#endregion
|
|
1100
973
|
//#region src/storage.ts
|
|
1101
|
-
|
|
974
|
+
/**
|
|
975
|
+
* Create a StorageSource that talks to the Rebase backend REST API.
|
|
976
|
+
*
|
|
977
|
+
* @param transport - HTTP transport instance
|
|
978
|
+
* @param storageId - Optional storage-source key for multi-backend routing.
|
|
979
|
+
* When set, it is forwarded to the server so the correct
|
|
980
|
+
* `StorageController` is resolved from the registry.
|
|
981
|
+
*/
|
|
982
|
+
function createStorage(transport, storageId) {
|
|
1102
983
|
const urlsCache = /* @__PURE__ */ new Map();
|
|
984
|
+
/** Append ?storageId=... to a path when multi-backend routing is active. */
|
|
985
|
+
const withStorageId = (path) => {
|
|
986
|
+
if (!storageId) return path;
|
|
987
|
+
return `${path}${path.includes("?") ? "&" : "?"}storageId=${encodeURIComponent(storageId)}`;
|
|
988
|
+
};
|
|
1103
989
|
async function putObject({ file, key, metadata, bucket }) {
|
|
1104
990
|
const formData = new FormData();
|
|
1105
991
|
formData.append("file", file);
|
|
1106
992
|
if (key) formData.append("key", key);
|
|
1107
993
|
if (bucket) formData.append("bucket", bucket);
|
|
994
|
+
if (storageId) formData.append("storageId", storageId);
|
|
1108
995
|
if (metadata) {
|
|
1109
996
|
for (const [key, value] of Object.entries(metadata)) if (value !== void 0 && value !== null) formData.append(`metadata_${key}`, typeof value === "string" ? value : JSON.stringify(value));
|
|
1110
997
|
}
|
|
1111
|
-
return (await transport.request("/storage/upload", {
|
|
998
|
+
return (await transport.request(withStorageId("/storage/upload"), {
|
|
1112
999
|
method: "POST",
|
|
1113
1000
|
body: formData,
|
|
1114
1001
|
headers: {}
|
|
@@ -1119,18 +1006,18 @@ function createStorage(transport) {
|
|
|
1119
1006
|
const cached = urlsCache.get(cacheKey);
|
|
1120
1007
|
if (cached) return cached;
|
|
1121
1008
|
let filePath = keyOrUrl;
|
|
1122
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1009
|
+
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1123
1010
|
if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
|
|
1124
1011
|
if (!filePath || filePath.trim() === "" || filePath === "/") return {
|
|
1125
1012
|
url: null,
|
|
1126
1013
|
fileNotFound: true
|
|
1127
1014
|
};
|
|
1128
1015
|
try {
|
|
1129
|
-
const result = await transport.request(`/storage/metadata/${filePath}`);
|
|
1016
|
+
const result = await transport.request(withStorageId(`/storage/metadata/${filePath}`));
|
|
1130
1017
|
const activeToken = await transport.resolveToken();
|
|
1131
1018
|
const tokenQuery = activeToken ? `?token=${activeToken}` : "";
|
|
1132
1019
|
const downloadConfig = {
|
|
1133
|
-
url: `${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}
|
|
1020
|
+
url: withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}`),
|
|
1134
1021
|
metadata: result.data
|
|
1135
1022
|
};
|
|
1136
1023
|
urlsCache.set(cacheKey, downloadConfig);
|
|
@@ -1145,10 +1032,10 @@ function createStorage(transport) {
|
|
|
1145
1032
|
}
|
|
1146
1033
|
async function getObject(key, bucket) {
|
|
1147
1034
|
let filePath = key;
|
|
1148
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1035
|
+
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1149
1036
|
if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
|
|
1150
1037
|
if (!filePath || filePath.trim() === "" || filePath === "/") return null;
|
|
1151
|
-
const url = `${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}
|
|
1038
|
+
const url = withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}`);
|
|
1152
1039
|
const response = await transport.fetchFn(url, { headers: transport.getHeaders ? transport.getHeaders() : {} });
|
|
1153
1040
|
if (response.status === 404) return null;
|
|
1154
1041
|
if (!response.ok) throw new Error("Failed to get file");
|
|
@@ -1158,11 +1045,11 @@ function createStorage(transport) {
|
|
|
1158
1045
|
}
|
|
1159
1046
|
async function deleteObject(key, bucket) {
|
|
1160
1047
|
let filePath = key;
|
|
1161
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1048
|
+
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1162
1049
|
if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
|
|
1163
1050
|
if (!filePath || filePath.trim() === "" || filePath === "/") return;
|
|
1164
1051
|
try {
|
|
1165
|
-
await transport.request(`/storage/file/${filePath}
|
|
1052
|
+
await transport.request(withStorageId(`/storage/file/${filePath}`), { method: "DELETE" });
|
|
1166
1053
|
} catch (e) {
|
|
1167
1054
|
if (!(e instanceof Error && "status" in e && e.status === 404)) throw e;
|
|
1168
1055
|
}
|
|
@@ -1174,6 +1061,7 @@ function createStorage(transport) {
|
|
|
1174
1061
|
if (options?.bucket) params.set("bucket", options.bucket);
|
|
1175
1062
|
if (options?.maxResults) params.set("maxResults", String(options.maxResults));
|
|
1176
1063
|
if (options?.pageToken) params.set("pageToken", options.pageToken);
|
|
1064
|
+
if (storageId) params.set("storageId", storageId);
|
|
1177
1065
|
return (await transport.request(`/storage/list?${params.toString()}`)).data;
|
|
1178
1066
|
}
|
|
1179
1067
|
return {
|
|
@@ -1185,6 +1073,62 @@ function createStorage(transport) {
|
|
|
1185
1073
|
};
|
|
1186
1074
|
}
|
|
1187
1075
|
//#endregion
|
|
1076
|
+
//#region src/storage-registry.ts
|
|
1077
|
+
/**
|
|
1078
|
+
* Default implementation of the client-side `StorageSourceRegistry`.
|
|
1079
|
+
*/
|
|
1080
|
+
var ClientStorageSourceRegistry = class ClientStorageSourceRegistry {
|
|
1081
|
+
sources = /* @__PURE__ */ new Map();
|
|
1082
|
+
/**
|
|
1083
|
+
* Register a storage source.
|
|
1084
|
+
* @param key - Unique key matching a `StorageSourceDefinition.key`
|
|
1085
|
+
* @param source - The `StorageSource` instance
|
|
1086
|
+
*/
|
|
1087
|
+
register(key, source) {
|
|
1088
|
+
this.sources.set(key, source);
|
|
1089
|
+
}
|
|
1090
|
+
getDefault() {
|
|
1091
|
+
const source = this.sources.get(DEFAULT_STORAGE_SOURCE_KEY);
|
|
1092
|
+
if (!source) throw new Error(`[StorageSourceRegistry] No default storage source registered. Register one with key "${DEFAULT_STORAGE_SOURCE_KEY}".`);
|
|
1093
|
+
return source;
|
|
1094
|
+
}
|
|
1095
|
+
get(key) {
|
|
1096
|
+
if (key === void 0 || key === null) return this.sources.get(DEFAULT_STORAGE_SOURCE_KEY);
|
|
1097
|
+
return this.sources.get(key);
|
|
1098
|
+
}
|
|
1099
|
+
getOrDefault(key) {
|
|
1100
|
+
if (key === void 0 || key === null) return this.getDefault();
|
|
1101
|
+
const source = this.sources.get(key);
|
|
1102
|
+
if (source) return source;
|
|
1103
|
+
console.warn(`[StorageSourceRegistry] Storage source "${key}" not found, falling back to "${DEFAULT_STORAGE_SOURCE_KEY}".`);
|
|
1104
|
+
return this.getDefault();
|
|
1105
|
+
}
|
|
1106
|
+
has(key) {
|
|
1107
|
+
return this.sources.has(key);
|
|
1108
|
+
}
|
|
1109
|
+
list() {
|
|
1110
|
+
return Array.from(this.sources.keys());
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Build a registry from `StorageSourceDefinition[]` and an HTTP transport.
|
|
1114
|
+
*
|
|
1115
|
+
* - Sources with `transport: "server"` are auto-wired via `createStorage(transport, key)`.
|
|
1116
|
+
* - Sources with `transport: "direct"` are **not** auto-wired — they must
|
|
1117
|
+
* be registered manually after this call (e.g. via a Firebase hook).
|
|
1118
|
+
*
|
|
1119
|
+
* @param definitions - Array of storage source definitions
|
|
1120
|
+
* @param transport - HTTP transport for server-backed sources
|
|
1121
|
+
*/
|
|
1122
|
+
static fromDefinitions(definitions, transport) {
|
|
1123
|
+
const registry = new ClientStorageSourceRegistry();
|
|
1124
|
+
for (const def of definitions) if (def.transport === "server") {
|
|
1125
|
+
const source = createStorage(transport, def.key === DEFAULT_STORAGE_SOURCE_KEY ? void 0 : def.key);
|
|
1126
|
+
registry.register(def.key, source);
|
|
1127
|
+
}
|
|
1128
|
+
return registry;
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
//#endregion
|
|
1188
1132
|
//#region src/websocket.ts
|
|
1189
1133
|
/**
|
|
1190
1134
|
* Extract error message and code from a WebSocket message payload.
|
|
@@ -2122,6 +2066,23 @@ function createRebaseClient(options) {
|
|
|
2122
2066
|
const apiKeys = createApiKeys(transport, options.apiKeys);
|
|
2123
2067
|
const storage = createStorage(transport);
|
|
2124
2068
|
const functions = createFunctionsClient(transport);
|
|
2069
|
+
const createStorageSource = (storageId) => storageId === DEFAULT_STORAGE_SOURCE_KEY ? storage : createStorage(transport, storageId);
|
|
2070
|
+
const storageRegistry = new ClientStorageSourceRegistry();
|
|
2071
|
+
storageRegistry.register(DEFAULT_STORAGE_SOURCE_KEY, storage);
|
|
2072
|
+
for (const def of options.storageSources ?? []) if (def.transport === "server" && def.key !== DEFAULT_STORAGE_SOURCE_KEY) storageRegistry.register(def.key, createStorageSource(def.key));
|
|
2073
|
+
let storageSourcesPromise;
|
|
2074
|
+
const fetchStorageSources = () => {
|
|
2075
|
+
if (storageSourcesPromise) return storageSourcesPromise;
|
|
2076
|
+
storageSourcesPromise = transport.request("/storage/sources").then((res) => {
|
|
2077
|
+
const defs = res.data ?? [];
|
|
2078
|
+
for (const def of defs) if (def.transport === "server" && def.key !== DEFAULT_STORAGE_SOURCE_KEY && !storageRegistry.has(def.key)) storageRegistry.register(def.key, createStorageSource(def.key));
|
|
2079
|
+
return defs;
|
|
2080
|
+
}).catch((e) => {
|
|
2081
|
+
storageSourcesPromise = void 0;
|
|
2082
|
+
throw e;
|
|
2083
|
+
});
|
|
2084
|
+
return storageSourcesPromise;
|
|
2085
|
+
};
|
|
2125
2086
|
const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
|
|
2126
2087
|
let ws;
|
|
2127
2088
|
if (resolvedWsUrl) {
|
|
@@ -2167,7 +2128,10 @@ function createRebaseClient(options) {
|
|
|
2167
2128
|
const dataProxy = new Proxy({ collection }, { get(_target, prop) {
|
|
2168
2129
|
if (prop === "collection") return collection;
|
|
2169
2130
|
if (typeof prop === "symbol") return void 0;
|
|
2170
|
-
if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof")
|
|
2131
|
+
if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof") {
|
|
2132
|
+
if (options.collections && prop in options.collections) return collection(options.collections[prop]);
|
|
2133
|
+
return collection(toSnakeCase(prop));
|
|
2134
|
+
}
|
|
2171
2135
|
} });
|
|
2172
2136
|
return {
|
|
2173
2137
|
auth,
|
|
@@ -2176,6 +2140,9 @@ function createRebaseClient(options) {
|
|
|
2176
2140
|
apiKeys,
|
|
2177
2141
|
functions,
|
|
2178
2142
|
storage,
|
|
2143
|
+
storageRegistry,
|
|
2144
|
+
createStorageSource,
|
|
2145
|
+
fetchStorageSources,
|
|
2179
2146
|
ws,
|
|
2180
2147
|
setToken: transport.setToken,
|
|
2181
2148
|
setAuthTokenGetter: transport.setAuthTokenGetter,
|
|
@@ -2196,6 +2163,6 @@ function createRebaseClient(options) {
|
|
|
2196
2163
|
};
|
|
2197
2164
|
}
|
|
2198
2165
|
//#endregion
|
|
2199
|
-
export { ApiError, QueryBuilder, RebaseApiError, RebaseWebSocketClient, and, buildQueryString, cond, createAdmin, createApiKeys, createAuth, createCollectionClient, createCookieStorage, createCron, createFunctionsClient, createMemoryStorage, createRebaseClient, createStorage, createTransport, or, rebaseReviver };
|
|
2166
|
+
export { ApiError, ClientStorageSourceRegistry, QueryBuilder, RebaseApiError, RebaseWebSocketClient, and, buildQueryString, cond, createAdmin, createApiKeys, createAuth, createCollectionClient, createCookieStorage, createCron, createFunctionsClient, createMemoryStorage, createRebaseClient, createStorage, createTransport, or, rebaseReviver };
|
|
2200
2167
|
|
|
2201
2168
|
//# sourceMappingURL=index.es.js.map
|