@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/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
- import { EntityReference, EntityRelation, GeoPoint, Vector } from "@rebasepro/types";
2
- import { QueryBuilder, and, cond, or } from "@rebasepro/common";
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) for (const [field, value] of Object.entries(params.where)) if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) for (const subVal of value) {
115
- const normalized = normalizeWhereValue(subVal);
116
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
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
  }
@@ -583,6 +525,31 @@ function createAuth(transport, options) {
583
525
  if (!res.ok) throwApiError(res.status, body, res.statusText);
584
526
  return body;
585
527
  }
528
+ async function sendMagicLink(email) {
529
+ const res = await getFetch()(authUrl("/magic-link"), {
530
+ method: "POST",
531
+ headers: { "Content-Type": "application/json" },
532
+ body: JSON.stringify({ email })
533
+ });
534
+ const body = await res.json().catch(() => ({}));
535
+ if (!res.ok) throwApiError(res.status, body, res.statusText);
536
+ return body;
537
+ }
538
+ async function verifyMagicLink(token) {
539
+ const res = await getFetch()(authUrl("/magic-link/verify"), {
540
+ method: "POST",
541
+ headers: { "Content-Type": "application/json" },
542
+ body: JSON.stringify({ token })
543
+ });
544
+ const body = await res.json().catch(() => ({}));
545
+ if (!res.ok) throwApiError(res.status, body, res.statusText);
546
+ const session = handleAuthResponse(body, "SIGNED_IN");
547
+ return {
548
+ user: session.user,
549
+ accessToken: session.accessToken,
550
+ refreshToken: session.refreshToken
551
+ };
552
+ }
586
553
  async function getSessions() {
587
554
  return (await transport.request(authPath + "/sessions", { method: "GET" })).sessions;
588
555
  }
@@ -659,6 +626,8 @@ function createAuth(transport, options) {
659
626
  changePassword,
660
627
  sendVerificationEmail,
661
628
  verifyEmail,
629
+ sendMagicLink,
630
+ verifyMagicLink,
662
631
  getSessions,
663
632
  revokeSession,
664
633
  revokeAllSessions,
@@ -739,6 +708,15 @@ function createAdmin(transport, options) {
739
708
  async function deleteUser(userId) {
740
709
  return transport.request(adminPath + "/users/" + encodeURIComponent(userId), { method: "DELETE" });
741
710
  }
711
+ async function resetPassword(userId, options) {
712
+ return transport.request(adminPath + "/users/" + encodeURIComponent(userId) + "/reset-password", {
713
+ method: "POST",
714
+ ...options?.password ? { body: JSON.stringify({ password: options.password }) } : {}
715
+ });
716
+ }
717
+ async function listRoles() {
718
+ return transport.request(adminPath + "/roles", { method: "GET" });
719
+ }
742
720
  async function bootstrap() {
743
721
  return transport.request(adminPath + "/bootstrap", { method: "POST" });
744
722
  }
@@ -749,6 +727,8 @@ function createAdmin(transport, options) {
749
727
  createUser,
750
728
  updateUser,
751
729
  deleteUser,
730
+ resetPassword,
731
+ listRoles,
752
732
  bootstrap
753
733
  };
754
734
  }
@@ -786,95 +766,51 @@ function createCron(transport, options) {
786
766
  };
787
767
  }
788
768
  //#endregion
789
- //#region src/collection.ts
790
- function parseWhereFilter(where) {
791
- if (!where) return void 0;
792
- const filters = {};
793
- const OP_TO_FILTER = {
794
- "eq": "==",
795
- "neq": "!=",
796
- "gt": ">",
797
- "gte": ">=",
798
- "lt": "<",
799
- "lte": "<=",
800
- "==": "==",
801
- "!=": "!=",
802
- ">": ">",
803
- ">=": ">=",
804
- "<": "<",
805
- "<=": "<=",
806
- "in": "in",
807
- "nin": "not-in",
808
- "not-in": "not-in",
809
- "cs": "array-contains",
810
- "csa": "array-contains-any",
811
- "array-contains": "array-contains",
812
- "array-contains-any": "array-contains-any"
813
- };
814
- const parseSingle = (rawValue, fieldKey) => {
815
- if (rawValue === null) return ["==", null];
816
- if (typeof rawValue === "boolean") return ["==", rawValue];
817
- if (typeof rawValue === "number") return ["==", rawValue];
818
- if (Array.isArray(rawValue) && rawValue.length === 2 && typeof rawValue[0] === "string") {
819
- const [rawOp, val] = rawValue;
820
- return [OP_TO_FILTER[rawOp] ?? "==", val];
821
- }
822
- const value = String(rawValue);
823
- const dotIndex = value.indexOf(".");
824
- if (dotIndex > 0) {
825
- const opStr = value.substring(0, dotIndex);
826
- const valStr = value.substring(dotIndex + 1);
827
- let op = "==";
828
- let val = valStr;
829
- switch (opStr) {
830
- case "eq":
831
- op = "==";
832
- break;
833
- case "neq":
834
- op = "!=";
835
- break;
836
- case "gt":
837
- op = ">";
838
- break;
839
- case "gte":
840
- op = ">=";
841
- break;
842
- case "lt":
843
- op = "<";
844
- break;
845
- case "lte":
846
- op = "<=";
847
- break;
848
- case "in":
849
- op = "in";
850
- val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
851
- break;
852
- case "nin":
853
- op = "not-in";
854
- val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
855
- break;
856
- case "cs":
857
- op = "array-contains";
858
- break;
859
- case "csa":
860
- op = "array-contains-any";
861
- val = valStr.startsWith("(") && valStr.endsWith(")") ? valStr.slice(1, -1).split(",").map((v) => v.trim()) : valStr.split(",");
862
- break;
863
- default:
864
- op = "==";
865
- val = value;
866
- }
867
- if (val === "true") val = true;
868
- else if (val === "false") val = false;
869
- else if (val === "null") val = null;
870
- else if (typeof val === "string" && /^[0-9]+(\.[0-9]+)?$/.test(val) && fieldKey !== "id" && !fieldKey.endsWith("_id")) val = Number(val);
871
- return [op, val];
872
- } else return ["==", value];
769
+ //#region src/api-keys.ts
770
+ /**
771
+ * Creates a client for managing API keys via the admin routes.
772
+ *
773
+ * @param transport - The shared HTTP transport created by `createTransport`.
774
+ * @param options - Optional overrides (e.g. a custom base path).
775
+ */
776
+ function createApiKeys(transport, options) {
777
+ const apiKeysPath = options?.apiKeysPath || "/admin/api-keys";
778
+ /** List all API keys (masked). */
779
+ async function listKeys() {
780
+ return transport.request(apiKeysPath, { method: "GET" });
781
+ }
782
+ /** Get a single API key by ID (masked). */
783
+ async function getKey(id) {
784
+ return transport.request(apiKeysPath + "/" + encodeURIComponent(id), { method: "GET" });
785
+ }
786
+ /** Create a new API key. The full secret is included in the response. */
787
+ async function createKey(data) {
788
+ return transport.request(apiKeysPath, {
789
+ method: "POST",
790
+ body: JSON.stringify(data)
791
+ });
792
+ }
793
+ /** Update an existing API key. */
794
+ async function updateKey(id, data) {
795
+ return transport.request(apiKeysPath + "/" + encodeURIComponent(id), {
796
+ method: "PUT",
797
+ body: JSON.stringify(data)
798
+ });
799
+ }
800
+ /** Revoke (soft-delete) an API key. */
801
+ async function revokeKey(id) {
802
+ return transport.request(apiKeysPath + "/" + encodeURIComponent(id), { method: "DELETE" });
803
+ }
804
+ return {
805
+ listKeys,
806
+ getKey,
807
+ createKey,
808
+ updateKey,
809
+ revokeKey
873
810
  };
874
- 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));
875
- else filters[key] = parseSingle(rawValue, key);
876
- return filters;
877
811
  }
812
+ //#endregion
813
+ //#region src/collection.ts
878
814
  /**
879
815
  * Wrap a flat row (returned by the REST API as `{ id, ...fields }`) into
880
816
  * a proper `Entity<M>` structure expected by the core framework.
@@ -939,8 +875,8 @@ function createCollectionClient(transport, slug, ws) {
939
875
  if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
940
876
  return builder.where(columnOrCondition, operator, value);
941
877
  },
942
- orderBy(column, ascending) {
943
- return new QueryBuilder(client).orderBy(column, ascending);
878
+ orderBy(column, direction) {
879
+ return new QueryBuilder(client).orderBy(column, direction);
944
880
  },
945
881
  limit(count) {
946
882
  return new QueryBuilder(client).limit(count);
@@ -957,26 +893,45 @@ function createCollectionClient(transport, slug, ws) {
957
893
  };
958
894
  if (ws) {
959
895
  client.listen = (params, onUpdate, onError) => {
960
- return ws.listenCollection({
896
+ let active = true;
897
+ let lastUpdateId = 0;
898
+ const unsub = ws.listenCollection({
961
899
  path: slug,
962
- filter: parseWhereFilter(params?.where),
900
+ filter: params?.where,
963
901
  limit: params?.limit,
964
902
  startAfter: params?.offset ? String(params.offset) : void 0,
965
903
  orderBy: params?.orderBy?.split(":")[0],
966
904
  order: params?.orderBy?.split(":")[1],
967
905
  searchString: params?.searchString
968
906
  }, (entities) => {
907
+ const currentUpdateId = ++lastUpdateId;
969
908
  const requestedLimit = params?.limit || 20;
909
+ const offset = params?.offset || 0;
970
910
  onUpdate({
971
911
  data: entities,
972
912
  meta: {
973
913
  total: entities.length,
974
914
  limit: requestedLimit,
975
- offset: params?.offset || 0,
915
+ offset,
976
916
  hasMore: entities.length >= requestedLimit
977
917
  }
978
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(() => {});
979
930
  }, onError);
931
+ return () => {
932
+ active = false;
933
+ unsub();
934
+ };
980
935
  };
981
936
  client.listenById = (id, onUpdate, onError) => {
982
937
  return ws.listenEntity({
@@ -1016,17 +971,31 @@ function createFunctionsClient(transport) {
1016
971
  }
1017
972
  //#endregion
1018
973
  //#region src/storage.ts
1019
- function createStorage(transport) {
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) {
1020
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
+ };
1021
989
  async function putObject({ file, key, metadata, bucket }) {
1022
990
  const formData = new FormData();
1023
991
  formData.append("file", file);
1024
992
  if (key) formData.append("key", key);
1025
993
  if (bucket) formData.append("bucket", bucket);
994
+ if (storageId) formData.append("storageId", storageId);
1026
995
  if (metadata) {
1027
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));
1028
997
  }
1029
- return (await transport.request("/storage/upload", {
998
+ return (await transport.request(withStorageId("/storage/upload"), {
1030
999
  method: "POST",
1031
1000
  body: formData,
1032
1001
  headers: {}
@@ -1037,18 +1006,18 @@ function createStorage(transport) {
1037
1006
  const cached = urlsCache.get(cacheKey);
1038
1007
  if (cached) return cached;
1039
1008
  let filePath = keyOrUrl;
1040
- 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);
1041
1010
  if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
1042
1011
  if (!filePath || filePath.trim() === "" || filePath === "/") return {
1043
1012
  url: null,
1044
1013
  fileNotFound: true
1045
1014
  };
1046
1015
  try {
1047
- const result = await transport.request(`/storage/metadata/${filePath}`);
1016
+ const result = await transport.request(withStorageId(`/storage/metadata/${filePath}`));
1048
1017
  const activeToken = await transport.resolveToken();
1049
1018
  const tokenQuery = activeToken ? `?token=${activeToken}` : "";
1050
1019
  const downloadConfig = {
1051
- url: `${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}`,
1020
+ url: withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}`),
1052
1021
  metadata: result.data
1053
1022
  };
1054
1023
  urlsCache.set(cacheKey, downloadConfig);
@@ -1063,10 +1032,10 @@ function createStorage(transport) {
1063
1032
  }
1064
1033
  async function getObject(key, bucket) {
1065
1034
  let filePath = key;
1066
- 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);
1067
1036
  if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
1068
1037
  if (!filePath || filePath.trim() === "" || filePath === "/") return null;
1069
- const url = `${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}`;
1038
+ const url = withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}`);
1070
1039
  const response = await transport.fetchFn(url, { headers: transport.getHeaders ? transport.getHeaders() : {} });
1071
1040
  if (response.status === 404) return null;
1072
1041
  if (!response.ok) throw new Error("Failed to get file");
@@ -1076,11 +1045,11 @@ function createStorage(transport) {
1076
1045
  }
1077
1046
  async function deleteObject(key, bucket) {
1078
1047
  let filePath = key;
1079
- 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);
1080
1049
  if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
1081
1050
  if (!filePath || filePath.trim() === "" || filePath === "/") return;
1082
1051
  try {
1083
- await transport.request(`/storage/file/${filePath}`, { method: "DELETE" });
1052
+ await transport.request(withStorageId(`/storage/file/${filePath}`), { method: "DELETE" });
1084
1053
  } catch (e) {
1085
1054
  if (!(e instanceof Error && "status" in e && e.status === 404)) throw e;
1086
1055
  }
@@ -1092,6 +1061,7 @@ function createStorage(transport) {
1092
1061
  if (options?.bucket) params.set("bucket", options.bucket);
1093
1062
  if (options?.maxResults) params.set("maxResults", String(options.maxResults));
1094
1063
  if (options?.pageToken) params.set("pageToken", options.pageToken);
1064
+ if (storageId) params.set("storageId", storageId);
1095
1065
  return (await transport.request(`/storage/list?${params.toString()}`)).data;
1096
1066
  }
1097
1067
  return {
@@ -1103,6 +1073,62 @@ function createStorage(transport) {
1103
1073
  };
1104
1074
  }
1105
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
1106
1132
  //#region src/websocket.ts
1107
1133
  /**
1108
1134
  * Extract error message and code from a WebSocket message payload.
@@ -2037,8 +2063,26 @@ function createRebaseClient(options) {
2037
2063
  const auth = createAuth(transport, options.auth);
2038
2064
  const admin = createAdmin(transport, options.admin);
2039
2065
  const cron = createCron(transport, options.cron);
2066
+ const apiKeys = createApiKeys(transport, options.apiKeys);
2040
2067
  const storage = createStorage(transport);
2041
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
+ };
2042
2086
  const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
2043
2087
  let ws;
2044
2088
  if (resolvedWsUrl) {
@@ -2084,14 +2128,21 @@ function createRebaseClient(options) {
2084
2128
  const dataProxy = new Proxy({ collection }, { get(_target, prop) {
2085
2129
  if (prop === "collection") return collection;
2086
2130
  if (typeof prop === "symbol") return void 0;
2087
- if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof") return collection(toSnakeCase(prop));
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
+ }
2088
2135
  } });
2089
2136
  return {
2090
2137
  auth,
2091
2138
  admin,
2092
2139
  cron,
2140
+ apiKeys,
2093
2141
  functions,
2094
2142
  storage,
2143
+ storageRegistry,
2144
+ createStorageSource,
2145
+ fetchStorageSources,
2095
2146
  ws,
2096
2147
  setToken: transport.setToken,
2097
2148
  setAuthTokenGetter: transport.setAuthTokenGetter,
@@ -2112,6 +2163,6 @@ function createRebaseClient(options) {
2112
2163
  };
2113
2164
  }
2114
2165
  //#endregion
2115
- export { ApiError, QueryBuilder, RebaseApiError, RebaseWebSocketClient, and, buildQueryString, cond, createAdmin, 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 };
2116
2167
 
2117
2168
  //# sourceMappingURL=index.es.js.map