@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.umd.js CHANGED
@@ -1,11 +1,11 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/types"), require("@rebasepro/common"), require("@rebasepro/utils")) : typeof define === "function" && define.amd ? define([
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._rebasepro_types, global._rebasepro_common, global._rebasepro_utils));
8
- })(this, function(exports, _rebasepro_types, _rebasepro_common, _rebasepro_utils) {
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) 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) {
121
- const normalized = normalizeWhereValue(subVal);
122
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
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/collection.ts
796
- function parseWhereFilter(where) {
797
- if (!where) return void 0;
798
- const filters = {};
799
- const OP_TO_FILTER = {
800
- "eq": "==",
801
- "neq": "!=",
802
- "gt": ">",
803
- "gte": ">=",
804
- "lt": "<",
805
- "lte": "<=",
806
- "==": "==",
807
- "!=": "!=",
808
- ">": ">",
809
- ">=": ">=",
810
- "<": "<",
811
- "<=": "<=",
812
- "in": "in",
813
- "nin": "not-in",
814
- "not-in": "not-in",
815
- "cs": "array-contains",
816
- "csa": "array-contains-any",
817
- "array-contains": "array-contains",
818
- "array-contains-any": "array-contains-any"
819
- };
820
- const parseSingle = (rawValue, fieldKey) => {
821
- if (rawValue === null) return ["==", null];
822
- if (typeof rawValue === "boolean") return ["==", rawValue];
823
- if (typeof rawValue === "number") return ["==", rawValue];
824
- if (Array.isArray(rawValue) && rawValue.length === 2 && typeof rawValue[0] === "string") {
825
- const [rawOp, val] = rawValue;
826
- return [OP_TO_FILTER[rawOp] ?? "==", val];
827
- }
828
- const value = String(rawValue);
829
- const dotIndex = value.indexOf(".");
830
- if (dotIndex > 0) {
831
- const opStr = value.substring(0, dotIndex);
832
- const valStr = value.substring(dotIndex + 1);
833
- let op = "==";
834
- let val = valStr;
835
- switch (opStr) {
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, ascending) {
949
- return new _rebasepro_common.QueryBuilder(client).orderBy(column, ascending);
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
- return ws.listenCollection({
902
+ let active = true;
903
+ let lastUpdateId = 0;
904
+ const unsub = ws.listenCollection({
967
905
  path: slug,
968
- filter: parseWhereFilter(params?.where),
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: params?.offset || 0,
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
- function createStorage(transport) {
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}`, { method: "DELETE" });
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") return collection((0, _rebasepro_utils.toSnakeCase)(prop));
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;