@rebasepro/client 0.6.1 → 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/dist/admin.d.ts +13 -0
- package/dist/api-keys.d.ts +66 -0
- package/dist/auth.d.ts +10 -0
- package/dist/collection.d.ts +3 -3
- package/dist/index.d.ts +24 -1
- package/dist/index.es.js +218 -167
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +222 -169
- 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 +5 -4
- package/src/admin.ts +19 -0
- package/src/api-keys.ts +110 -0
- package/src/auth.ts +30 -0
- package/src/collection.test.ts +2 -2
- package/src/collection.ts +41 -119
- package/src/index.ts +83 -2
- package/src/storage-registry.ts +102 -0
- package/src/storage.ts +30 -20
- package/src/transport.ts +8 -82
package/dist/index.umd.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
(function(global, factory) {
|
|
2
|
-
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/common"), require("@rebasepro/types"), require("@rebasepro/utils")) : typeof define === "function" && define.amd ? define([
|
|
3
3
|
"exports",
|
|
4
|
-
"@rebasepro/types",
|
|
5
4
|
"@rebasepro/common",
|
|
5
|
+
"@rebasepro/types",
|
|
6
6
|
"@rebasepro/utils"
|
|
7
|
-
], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase Client"] = {}, global.
|
|
8
|
-
})(this, function(exports,
|
|
7
|
+
], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase Client"] = {}, global._rebasepro_common, global._rebasepro_types, global._rebasepro_utils));
|
|
8
|
+
})(this, function(exports, _rebasepro_common, _rebasepro_types, _rebasepro_utils) {
|
|
9
9
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
10
10
|
//#region src/reviver.ts
|
|
11
11
|
function rebaseReviver(_key, value) {
|
|
@@ -48,61 +48,6 @@
|
|
|
48
48
|
this.details = details;
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
-
/**
|
|
52
|
-
* Maps a short operator alias to the PostgREST-style short code.
|
|
53
|
-
*/
|
|
54
|
-
var OP_MAP = {
|
|
55
|
-
"==": "eq",
|
|
56
|
-
"!=": "neq",
|
|
57
|
-
">": "gt",
|
|
58
|
-
">=": "gte",
|
|
59
|
-
"<": "lt",
|
|
60
|
-
"<=": "lte",
|
|
61
|
-
"not-in": "nin",
|
|
62
|
-
"array-contains": "cs",
|
|
63
|
-
"array-contains-any": "csa"
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Normalise a single `WhereFieldValue` into the PostgREST query-string
|
|
67
|
-
* representation the backend expects.
|
|
68
|
-
*
|
|
69
|
-
* Supports:
|
|
70
|
-
* - `null` → `"eq.null"`
|
|
71
|
-
* - `true`/`false` → `"eq.true"` / `"eq.false"`
|
|
72
|
-
* - `42` → `"42"` (plain equality)
|
|
73
|
-
* - `"active"` → `"active"` (plain equality, backward-compat)
|
|
74
|
-
* - `"gte.18"` → `"gte.18"` (pass-through PostgREST string)
|
|
75
|
-
* - `[">=", 18]` → `"gte.18"` (tuple syntax)
|
|
76
|
-
* - `["in", [1,2]]` → `"in.(1,2)"` (tuple with array value)
|
|
77
|
-
* - `["!=", null]` → `"neq.null"`
|
|
78
|
-
*/
|
|
79
|
-
function normalizeWhereValue(value) {
|
|
80
|
-
if (value === null) return "eq.null";
|
|
81
|
-
if (typeof value === "boolean") return `eq.${value}`;
|
|
82
|
-
if (typeof value === "number") return String(value);
|
|
83
|
-
if (Array.isArray(value)) {
|
|
84
|
-
const [rawOp, val] = (Array.isArray(value[0]) ? value : [value])[0] || [];
|
|
85
|
-
if (rawOp) {
|
|
86
|
-
const op = OP_MAP[rawOp] ?? rawOp;
|
|
87
|
-
if (val === null) return `${op}.null`;
|
|
88
|
-
if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
|
|
89
|
-
return `${op}.${val}`;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return String(value);
|
|
93
|
-
}
|
|
94
|
-
function serializeLogicalCondition(cond) {
|
|
95
|
-
if ("type" in cond) {
|
|
96
|
-
const sub = (cond.conditions ?? []).map(serializeLogicalCondition).join(",");
|
|
97
|
-
return `${cond.type}(${sub})`;
|
|
98
|
-
} else {
|
|
99
|
-
const op = OP_MAP[cond.operator] ?? cond.operator;
|
|
100
|
-
let formattedValue = cond.value;
|
|
101
|
-
if (Array.isArray(cond.value)) formattedValue = `(${cond.value.join(",")})`;
|
|
102
|
-
else if (cond.value === null) formattedValue = "null";
|
|
103
|
-
return `${cond.column}.${op}.${formattedValue}`;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
51
|
function buildQueryString(params) {
|
|
107
52
|
if (!params) return "";
|
|
108
53
|
const parts = [];
|
|
@@ -114,16 +59,13 @@
|
|
|
114
59
|
if (params.include && params.include.length > 0) parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
|
|
115
60
|
if (params.logical) {
|
|
116
61
|
const root = params.logical;
|
|
117
|
-
const serialized = (root.conditions ?? []).map(serializeLogicalCondition).join(",");
|
|
62
|
+
const serialized = (root.conditions ?? []).map(_rebasepro_common.serializeLogicalCondition).join(",");
|
|
118
63
|
parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
|
|
119
64
|
}
|
|
120
|
-
if (params.where)
|
|
121
|
-
const
|
|
122
|
-
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(
|
|
123
|
-
|
|
124
|
-
else {
|
|
125
|
-
const normalized = normalizeWhereValue(value);
|
|
126
|
-
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
|
|
65
|
+
if (params.where) {
|
|
66
|
+
const serialized = (0, _rebasepro_common.serializeFilter)(params.where);
|
|
67
|
+
for (const [field, value] of Object.entries(serialized)) if (Array.isArray(value)) for (const v of value) parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(v)}`);
|
|
68
|
+
else parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(value)}`);
|
|
127
69
|
}
|
|
128
70
|
return parts.length > 0 ? "?" + parts.join("&") : "";
|
|
129
71
|
}
|
|
@@ -589,6 +531,31 @@
|
|
|
589
531
|
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
590
532
|
return body;
|
|
591
533
|
}
|
|
534
|
+
async function sendMagicLink(email) {
|
|
535
|
+
const res = await getFetch()(authUrl("/magic-link"), {
|
|
536
|
+
method: "POST",
|
|
537
|
+
headers: { "Content-Type": "application/json" },
|
|
538
|
+
body: JSON.stringify({ email })
|
|
539
|
+
});
|
|
540
|
+
const body = await res.json().catch(() => ({}));
|
|
541
|
+
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
542
|
+
return body;
|
|
543
|
+
}
|
|
544
|
+
async function verifyMagicLink(token) {
|
|
545
|
+
const res = await getFetch()(authUrl("/magic-link/verify"), {
|
|
546
|
+
method: "POST",
|
|
547
|
+
headers: { "Content-Type": "application/json" },
|
|
548
|
+
body: JSON.stringify({ token })
|
|
549
|
+
});
|
|
550
|
+
const body = await res.json().catch(() => ({}));
|
|
551
|
+
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
552
|
+
const session = handleAuthResponse(body, "SIGNED_IN");
|
|
553
|
+
return {
|
|
554
|
+
user: session.user,
|
|
555
|
+
accessToken: session.accessToken,
|
|
556
|
+
refreshToken: session.refreshToken
|
|
557
|
+
};
|
|
558
|
+
}
|
|
592
559
|
async function getSessions() {
|
|
593
560
|
return (await transport.request(authPath + "/sessions", { method: "GET" })).sessions;
|
|
594
561
|
}
|
|
@@ -665,6 +632,8 @@
|
|
|
665
632
|
changePassword,
|
|
666
633
|
sendVerificationEmail,
|
|
667
634
|
verifyEmail,
|
|
635
|
+
sendMagicLink,
|
|
636
|
+
verifyMagicLink,
|
|
668
637
|
getSessions,
|
|
669
638
|
revokeSession,
|
|
670
639
|
revokeAllSessions,
|
|
@@ -745,6 +714,15 @@
|
|
|
745
714
|
async function deleteUser(userId) {
|
|
746
715
|
return transport.request(adminPath + "/users/" + encodeURIComponent(userId), { method: "DELETE" });
|
|
747
716
|
}
|
|
717
|
+
async function resetPassword(userId, options) {
|
|
718
|
+
return transport.request(adminPath + "/users/" + encodeURIComponent(userId) + "/reset-password", {
|
|
719
|
+
method: "POST",
|
|
720
|
+
...options?.password ? { body: JSON.stringify({ password: options.password }) } : {}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
async function listRoles() {
|
|
724
|
+
return transport.request(adminPath + "/roles", { method: "GET" });
|
|
725
|
+
}
|
|
748
726
|
async function bootstrap() {
|
|
749
727
|
return transport.request(adminPath + "/bootstrap", { method: "POST" });
|
|
750
728
|
}
|
|
@@ -755,6 +733,8 @@
|
|
|
755
733
|
createUser,
|
|
756
734
|
updateUser,
|
|
757
735
|
deleteUser,
|
|
736
|
+
resetPassword,
|
|
737
|
+
listRoles,
|
|
758
738
|
bootstrap
|
|
759
739
|
};
|
|
760
740
|
}
|
|
@@ -792,95 +772,51 @@
|
|
|
792
772
|
};
|
|
793
773
|
}
|
|
794
774
|
//#endregion
|
|
795
|
-
//#region src/
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
"
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
case "eq":
|
|
837
|
-
op = "==";
|
|
838
|
-
break;
|
|
839
|
-
case "neq":
|
|
840
|
-
op = "!=";
|
|
841
|
-
break;
|
|
842
|
-
case "gt":
|
|
843
|
-
op = ">";
|
|
844
|
-
break;
|
|
845
|
-
case "gte":
|
|
846
|
-
op = ">=";
|
|
847
|
-
break;
|
|
848
|
-
case "lt":
|
|
849
|
-
op = "<";
|
|
850
|
-
break;
|
|
851
|
-
case "lte":
|
|
852
|
-
op = "<=";
|
|
853
|
-
break;
|
|
854
|
-
case "in":
|
|
855
|
-
op = "in";
|
|
856
|
-
val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
|
|
857
|
-
break;
|
|
858
|
-
case "nin":
|
|
859
|
-
op = "not-in";
|
|
860
|
-
val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
|
|
861
|
-
break;
|
|
862
|
-
case "cs":
|
|
863
|
-
op = "array-contains";
|
|
864
|
-
break;
|
|
865
|
-
case "csa":
|
|
866
|
-
op = "array-contains-any";
|
|
867
|
-
val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
|
|
868
|
-
break;
|
|
869
|
-
default:
|
|
870
|
-
op = "==";
|
|
871
|
-
val = value;
|
|
872
|
-
}
|
|
873
|
-
if (val === "true") val = true;
|
|
874
|
-
else if (val === "false") val = false;
|
|
875
|
-
else if (val === "null") val = null;
|
|
876
|
-
else if (typeof val === "string" && /^[0-9]+(\.[0-9]+)?$/.test(val) && fieldKey !== "id" && !fieldKey.endsWith("_id")) val = Number(val);
|
|
877
|
-
return [op, val];
|
|
878
|
-
} else return ["==", value];
|
|
775
|
+
//#region src/api-keys.ts
|
|
776
|
+
/**
|
|
777
|
+
* Creates a client for managing API keys via the admin routes.
|
|
778
|
+
*
|
|
779
|
+
* @param transport - The shared HTTP transport created by `createTransport`.
|
|
780
|
+
* @param options - Optional overrides (e.g. a custom base path).
|
|
781
|
+
*/
|
|
782
|
+
function createApiKeys(transport, options) {
|
|
783
|
+
const apiKeysPath = options?.apiKeysPath || "/admin/api-keys";
|
|
784
|
+
/** List all API keys (masked). */
|
|
785
|
+
async function listKeys() {
|
|
786
|
+
return transport.request(apiKeysPath, { method: "GET" });
|
|
787
|
+
}
|
|
788
|
+
/** Get a single API key by ID (masked). */
|
|
789
|
+
async function getKey(id) {
|
|
790
|
+
return transport.request(apiKeysPath + "/" + encodeURIComponent(id), { method: "GET" });
|
|
791
|
+
}
|
|
792
|
+
/** Create a new API key. The full secret is included in the response. */
|
|
793
|
+
async function createKey(data) {
|
|
794
|
+
return transport.request(apiKeysPath, {
|
|
795
|
+
method: "POST",
|
|
796
|
+
body: JSON.stringify(data)
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
/** Update an existing API key. */
|
|
800
|
+
async function updateKey(id, data) {
|
|
801
|
+
return transport.request(apiKeysPath + "/" + encodeURIComponent(id), {
|
|
802
|
+
method: "PUT",
|
|
803
|
+
body: JSON.stringify(data)
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
/** Revoke (soft-delete) an API key. */
|
|
807
|
+
async function revokeKey(id) {
|
|
808
|
+
return transport.request(apiKeysPath + "/" + encodeURIComponent(id), { method: "DELETE" });
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
listKeys,
|
|
812
|
+
getKey,
|
|
813
|
+
createKey,
|
|
814
|
+
updateKey,
|
|
815
|
+
revokeKey
|
|
879
816
|
};
|
|
880
|
-
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));
|
|
881
|
-
else filters[key] = parseSingle(rawValue, key);
|
|
882
|
-
return filters;
|
|
883
817
|
}
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region src/collection.ts
|
|
884
820
|
/**
|
|
885
821
|
* Wrap a flat row (returned by the REST API as `{ id, ...fields }`) into
|
|
886
822
|
* a proper `Entity<M>` structure expected by the core framework.
|
|
@@ -945,8 +881,8 @@
|
|
|
945
881
|
if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
|
|
946
882
|
return builder.where(columnOrCondition, operator, value);
|
|
947
883
|
},
|
|
948
|
-
orderBy(column,
|
|
949
|
-
return new _rebasepro_common.QueryBuilder(client).orderBy(column,
|
|
884
|
+
orderBy(column, direction) {
|
|
885
|
+
return new _rebasepro_common.QueryBuilder(client).orderBy(column, direction);
|
|
950
886
|
},
|
|
951
887
|
limit(count) {
|
|
952
888
|
return new _rebasepro_common.QueryBuilder(client).limit(count);
|
|
@@ -963,26 +899,45 @@
|
|
|
963
899
|
};
|
|
964
900
|
if (ws) {
|
|
965
901
|
client.listen = (params, onUpdate, onError) => {
|
|
966
|
-
|
|
902
|
+
let active = true;
|
|
903
|
+
let lastUpdateId = 0;
|
|
904
|
+
const unsub = ws.listenCollection({
|
|
967
905
|
path: slug,
|
|
968
|
-
filter:
|
|
906
|
+
filter: params?.where,
|
|
969
907
|
limit: params?.limit,
|
|
970
908
|
startAfter: params?.offset ? String(params.offset) : void 0,
|
|
971
909
|
orderBy: params?.orderBy?.split(":")[0],
|
|
972
910
|
order: params?.orderBy?.split(":")[1],
|
|
973
911
|
searchString: params?.searchString
|
|
974
912
|
}, (entities) => {
|
|
913
|
+
const currentUpdateId = ++lastUpdateId;
|
|
975
914
|
const requestedLimit = params?.limit || 20;
|
|
915
|
+
const offset = params?.offset || 0;
|
|
976
916
|
onUpdate({
|
|
977
917
|
data: entities,
|
|
978
918
|
meta: {
|
|
979
919
|
total: entities.length,
|
|
980
920
|
limit: requestedLimit,
|
|
981
|
-
offset
|
|
921
|
+
offset,
|
|
982
922
|
hasMore: entities.length >= requestedLimit
|
|
983
923
|
}
|
|
984
924
|
});
|
|
925
|
+
if (client.count) client.count(params).then((total) => {
|
|
926
|
+
if (active && currentUpdateId === lastUpdateId) onUpdate({
|
|
927
|
+
data: entities,
|
|
928
|
+
meta: {
|
|
929
|
+
total,
|
|
930
|
+
limit: requestedLimit,
|
|
931
|
+
offset,
|
|
932
|
+
hasMore: offset + entities.length < total
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}).catch(() => {});
|
|
985
936
|
}, onError);
|
|
937
|
+
return () => {
|
|
938
|
+
active = false;
|
|
939
|
+
unsub();
|
|
940
|
+
};
|
|
986
941
|
};
|
|
987
942
|
client.listenById = (id, onUpdate, onError) => {
|
|
988
943
|
return ws.listenEntity({
|
|
@@ -1022,17 +977,31 @@
|
|
|
1022
977
|
}
|
|
1023
978
|
//#endregion
|
|
1024
979
|
//#region src/storage.ts
|
|
1025
|
-
|
|
980
|
+
/**
|
|
981
|
+
* Create a StorageSource that talks to the Rebase backend REST API.
|
|
982
|
+
*
|
|
983
|
+
* @param transport - HTTP transport instance
|
|
984
|
+
* @param storageId - Optional storage-source key for multi-backend routing.
|
|
985
|
+
* When set, it is forwarded to the server so the correct
|
|
986
|
+
* `StorageController` is resolved from the registry.
|
|
987
|
+
*/
|
|
988
|
+
function createStorage(transport, storageId) {
|
|
1026
989
|
const urlsCache = /* @__PURE__ */ new Map();
|
|
990
|
+
/** Append ?storageId=... to a path when multi-backend routing is active. */
|
|
991
|
+
const withStorageId = (path) => {
|
|
992
|
+
if (!storageId) return path;
|
|
993
|
+
return `${path}${path.includes("?") ? "&" : "?"}storageId=${encodeURIComponent(storageId)}`;
|
|
994
|
+
};
|
|
1027
995
|
async function putObject({ file, key, metadata, bucket }) {
|
|
1028
996
|
const formData = new FormData();
|
|
1029
997
|
formData.append("file", file);
|
|
1030
998
|
if (key) formData.append("key", key);
|
|
1031
999
|
if (bucket) formData.append("bucket", bucket);
|
|
1000
|
+
if (storageId) formData.append("storageId", storageId);
|
|
1032
1001
|
if (metadata) {
|
|
1033
1002
|
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));
|
|
1034
1003
|
}
|
|
1035
|
-
return (await transport.request("/storage/upload", {
|
|
1004
|
+
return (await transport.request(withStorageId("/storage/upload"), {
|
|
1036
1005
|
method: "POST",
|
|
1037
1006
|
body: formData,
|
|
1038
1007
|
headers: {}
|
|
@@ -1043,18 +1012,18 @@
|
|
|
1043
1012
|
const cached = urlsCache.get(cacheKey);
|
|
1044
1013
|
if (cached) return cached;
|
|
1045
1014
|
let filePath = keyOrUrl;
|
|
1046
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1015
|
+
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1047
1016
|
if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
|
|
1048
1017
|
if (!filePath || filePath.trim() === "" || filePath === "/") return {
|
|
1049
1018
|
url: null,
|
|
1050
1019
|
fileNotFound: true
|
|
1051
1020
|
};
|
|
1052
1021
|
try {
|
|
1053
|
-
const result = await transport.request(`/storage/metadata/${filePath}`);
|
|
1022
|
+
const result = await transport.request(withStorageId(`/storage/metadata/${filePath}`));
|
|
1054
1023
|
const activeToken = await transport.resolveToken();
|
|
1055
1024
|
const tokenQuery = activeToken ? `?token=${activeToken}` : "";
|
|
1056
1025
|
const downloadConfig = {
|
|
1057
|
-
url: `${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}
|
|
1026
|
+
url: withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}`),
|
|
1058
1027
|
metadata: result.data
|
|
1059
1028
|
};
|
|
1060
1029
|
urlsCache.set(cacheKey, downloadConfig);
|
|
@@ -1069,10 +1038,10 @@
|
|
|
1069
1038
|
}
|
|
1070
1039
|
async function getObject(key, bucket) {
|
|
1071
1040
|
let filePath = key;
|
|
1072
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1041
|
+
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1073
1042
|
if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
|
|
1074
1043
|
if (!filePath || filePath.trim() === "" || filePath === "/") return null;
|
|
1075
|
-
const url = `${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}
|
|
1044
|
+
const url = withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}`);
|
|
1076
1045
|
const response = await transport.fetchFn(url, { headers: transport.getHeaders ? transport.getHeaders() : {} });
|
|
1077
1046
|
if (response.status === 404) return null;
|
|
1078
1047
|
if (!response.ok) throw new Error("Failed to get file");
|
|
@@ -1082,11 +1051,11 @@
|
|
|
1082
1051
|
}
|
|
1083
1052
|
async function deleteObject(key, bucket) {
|
|
1084
1053
|
let filePath = key;
|
|
1085
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1054
|
+
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1086
1055
|
if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
|
|
1087
1056
|
if (!filePath || filePath.trim() === "" || filePath === "/") return;
|
|
1088
1057
|
try {
|
|
1089
|
-
await transport.request(`/storage/file/${filePath}
|
|
1058
|
+
await transport.request(withStorageId(`/storage/file/${filePath}`), { method: "DELETE" });
|
|
1090
1059
|
} catch (e) {
|
|
1091
1060
|
if (!(e instanceof Error && "status" in e && e.status === 404)) throw e;
|
|
1092
1061
|
}
|
|
@@ -1098,6 +1067,7 @@
|
|
|
1098
1067
|
if (options?.bucket) params.set("bucket", options.bucket);
|
|
1099
1068
|
if (options?.maxResults) params.set("maxResults", String(options.maxResults));
|
|
1100
1069
|
if (options?.pageToken) params.set("pageToken", options.pageToken);
|
|
1070
|
+
if (storageId) params.set("storageId", storageId);
|
|
1101
1071
|
return (await transport.request(`/storage/list?${params.toString()}`)).data;
|
|
1102
1072
|
}
|
|
1103
1073
|
return {
|
|
@@ -1109,6 +1079,62 @@
|
|
|
1109
1079
|
};
|
|
1110
1080
|
}
|
|
1111
1081
|
//#endregion
|
|
1082
|
+
//#region src/storage-registry.ts
|
|
1083
|
+
/**
|
|
1084
|
+
* Default implementation of the client-side `StorageSourceRegistry`.
|
|
1085
|
+
*/
|
|
1086
|
+
var ClientStorageSourceRegistry = class ClientStorageSourceRegistry {
|
|
1087
|
+
sources = /* @__PURE__ */ new Map();
|
|
1088
|
+
/**
|
|
1089
|
+
* Register a storage source.
|
|
1090
|
+
* @param key - Unique key matching a `StorageSourceDefinition.key`
|
|
1091
|
+
* @param source - The `StorageSource` instance
|
|
1092
|
+
*/
|
|
1093
|
+
register(key, source) {
|
|
1094
|
+
this.sources.set(key, source);
|
|
1095
|
+
}
|
|
1096
|
+
getDefault() {
|
|
1097
|
+
const source = this.sources.get(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY);
|
|
1098
|
+
if (!source) throw new Error(`[StorageSourceRegistry] No default storage source registered. Register one with key "${_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY}".`);
|
|
1099
|
+
return source;
|
|
1100
|
+
}
|
|
1101
|
+
get(key) {
|
|
1102
|
+
if (key === void 0 || key === null) return this.sources.get(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY);
|
|
1103
|
+
return this.sources.get(key);
|
|
1104
|
+
}
|
|
1105
|
+
getOrDefault(key) {
|
|
1106
|
+
if (key === void 0 || key === null) return this.getDefault();
|
|
1107
|
+
const source = this.sources.get(key);
|
|
1108
|
+
if (source) return source;
|
|
1109
|
+
console.warn(`[StorageSourceRegistry] Storage source "${key}" not found, falling back to "${_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY}".`);
|
|
1110
|
+
return this.getDefault();
|
|
1111
|
+
}
|
|
1112
|
+
has(key) {
|
|
1113
|
+
return this.sources.has(key);
|
|
1114
|
+
}
|
|
1115
|
+
list() {
|
|
1116
|
+
return Array.from(this.sources.keys());
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Build a registry from `StorageSourceDefinition[]` and an HTTP transport.
|
|
1120
|
+
*
|
|
1121
|
+
* - Sources with `transport: "server"` are auto-wired via `createStorage(transport, key)`.
|
|
1122
|
+
* - Sources with `transport: "direct"` are **not** auto-wired — they must
|
|
1123
|
+
* be registered manually after this call (e.g. via a Firebase hook).
|
|
1124
|
+
*
|
|
1125
|
+
* @param definitions - Array of storage source definitions
|
|
1126
|
+
* @param transport - HTTP transport for server-backed sources
|
|
1127
|
+
*/
|
|
1128
|
+
static fromDefinitions(definitions, transport) {
|
|
1129
|
+
const registry = new ClientStorageSourceRegistry();
|
|
1130
|
+
for (const def of definitions) if (def.transport === "server") {
|
|
1131
|
+
const source = createStorage(transport, def.key === _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY ? void 0 : def.key);
|
|
1132
|
+
registry.register(def.key, source);
|
|
1133
|
+
}
|
|
1134
|
+
return registry;
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
//#endregion
|
|
1112
1138
|
//#region src/websocket.ts
|
|
1113
1139
|
/**
|
|
1114
1140
|
* Extract error message and code from a WebSocket message payload.
|
|
@@ -2043,8 +2069,26 @@
|
|
|
2043
2069
|
const auth = createAuth(transport, options.auth);
|
|
2044
2070
|
const admin = createAdmin(transport, options.admin);
|
|
2045
2071
|
const cron = createCron(transport, options.cron);
|
|
2072
|
+
const apiKeys = createApiKeys(transport, options.apiKeys);
|
|
2046
2073
|
const storage = createStorage(transport);
|
|
2047
2074
|
const functions = createFunctionsClient(transport);
|
|
2075
|
+
const createStorageSource = (storageId) => storageId === _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY ? storage : createStorage(transport, storageId);
|
|
2076
|
+
const storageRegistry = new ClientStorageSourceRegistry();
|
|
2077
|
+
storageRegistry.register(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY, storage);
|
|
2078
|
+
for (const def of options.storageSources ?? []) if (def.transport === "server" && def.key !== _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY) storageRegistry.register(def.key, createStorageSource(def.key));
|
|
2079
|
+
let storageSourcesPromise;
|
|
2080
|
+
const fetchStorageSources = () => {
|
|
2081
|
+
if (storageSourcesPromise) return storageSourcesPromise;
|
|
2082
|
+
storageSourcesPromise = transport.request("/storage/sources").then((res) => {
|
|
2083
|
+
const defs = res.data ?? [];
|
|
2084
|
+
for (const def of defs) if (def.transport === "server" && def.key !== _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY && !storageRegistry.has(def.key)) storageRegistry.register(def.key, createStorageSource(def.key));
|
|
2085
|
+
return defs;
|
|
2086
|
+
}).catch((e) => {
|
|
2087
|
+
storageSourcesPromise = void 0;
|
|
2088
|
+
throw e;
|
|
2089
|
+
});
|
|
2090
|
+
return storageSourcesPromise;
|
|
2091
|
+
};
|
|
2048
2092
|
const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
|
|
2049
2093
|
let ws;
|
|
2050
2094
|
if (resolvedWsUrl) {
|
|
@@ -2090,14 +2134,21 @@
|
|
|
2090
2134
|
const dataProxy = new Proxy({ collection }, { get(_target, prop) {
|
|
2091
2135
|
if (prop === "collection") return collection;
|
|
2092
2136
|
if (typeof prop === "symbol") return void 0;
|
|
2093
|
-
if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof")
|
|
2137
|
+
if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof") {
|
|
2138
|
+
if (options.collections && prop in options.collections) return collection(options.collections[prop]);
|
|
2139
|
+
return collection((0, _rebasepro_utils.toSnakeCase)(prop));
|
|
2140
|
+
}
|
|
2094
2141
|
} });
|
|
2095
2142
|
return {
|
|
2096
2143
|
auth,
|
|
2097
2144
|
admin,
|
|
2098
2145
|
cron,
|
|
2146
|
+
apiKeys,
|
|
2099
2147
|
functions,
|
|
2100
2148
|
storage,
|
|
2149
|
+
storageRegistry,
|
|
2150
|
+
createStorageSource,
|
|
2151
|
+
fetchStorageSources,
|
|
2101
2152
|
ws,
|
|
2102
2153
|
setToken: transport.setToken,
|
|
2103
2154
|
setAuthTokenGetter: transport.setAuthTokenGetter,
|
|
@@ -2119,6 +2170,7 @@
|
|
|
2119
2170
|
}
|
|
2120
2171
|
//#endregion
|
|
2121
2172
|
exports.ApiError = ApiError;
|
|
2173
|
+
exports.ClientStorageSourceRegistry = ClientStorageSourceRegistry;
|
|
2122
2174
|
Object.defineProperty(exports, "QueryBuilder", {
|
|
2123
2175
|
enumerable: true,
|
|
2124
2176
|
get: function() {
|
|
@@ -2141,6 +2193,7 @@
|
|
|
2141
2193
|
}
|
|
2142
2194
|
});
|
|
2143
2195
|
exports.createAdmin = createAdmin;
|
|
2196
|
+
exports.createApiKeys = createApiKeys;
|
|
2144
2197
|
exports.createAuth = createAuth;
|
|
2145
2198
|
exports.createCollectionClient = createCollectionClient;
|
|
2146
2199
|
exports.createCookieStorage = createCookieStorage;
|