@basictech/react 0.12.0-beta.0 → 0.12.0-beta.1

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.js CHANGED
@@ -59,10 +59,21 @@ __export(index_exports, {
59
59
  });
60
60
  module.exports = __toCommonJS(index_exports);
61
61
 
62
+ // src/create-basic.tsx
63
+ var import_react12 = require("react");
64
+
65
+ // src/context.ts
66
+ var import_react = require("react");
67
+ var BasicClientContext = (0, import_react.createContext)(null);
68
+
62
69
  // src/create-client.ts
63
70
  var import_core4 = require("@basictech/core");
64
71
 
65
72
  // src/adapters/broadcast-channel.ts
73
+ function supportsAccessTokenAdoption(store) {
74
+ const candidate = store;
75
+ return typeof candidate?.accessToken === "function" && typeof candidate?.adoptAccessToken === "function";
76
+ }
66
77
  function createBrowserMessageChannel(name) {
67
78
  const channel = new BroadcastChannel(name);
68
79
  const adapter = {
@@ -73,12 +84,11 @@ function createBrowserMessageChannel(name) {
73
84
  channel.onmessage = (event) => adapter.onmessage?.({ data: event.data });
74
85
  return adapter;
75
86
  }
76
- function createBrowserAuthChannelFactory(clientId, tokenStore) {
77
- const prefix = `basic_auth:${clientId}:`;
78
- return (name) => {
87
+ function createBrowserAuthChannelFactory(tokenStore) {
88
+ return (name, context) => {
79
89
  const channel = createBrowserMessageChannel(name);
80
- const profileId = name.startsWith(prefix) ? name.slice(prefix.length) : "";
81
- const tokenKey = `u:${profileId}:basic_tokens`;
90
+ if (!context) return channel;
91
+ const { tokenKey } = context;
82
92
  const adapter = {
83
93
  postMessage: (message) => {
84
94
  const outgoing = message;
@@ -620,32 +630,27 @@ function browserReconcileTrigger(listener) {
620
630
  function createBasicClient(config) {
621
631
  const local = browserStorage("localStorage") ?? new import_core4.MemoryKeyValueStorage();
622
632
  const session = browserStorage("sessionStorage") ?? new import_core4.MemoryKeyValueStorage();
623
- const defaultTokenStore = config.tokenStore ? null : new BrowserTokenStore(local);
633
+ const tokenStore = config.tokenStore ?? new BrowserTokenStore(local);
624
634
  const canBroadcast = typeof BroadcastChannel !== "undefined";
625
635
  const canPersistReplica = typeof indexedDB !== "undefined";
636
+ const messageChannel = !canBroadcast ? void 0 : supportsAccessTokenAdoption(tokenStore) ? createBrowserAuthChannelFactory(tokenStore) : createBrowserMessageChannel;
626
637
  return (0, import_core4.createBasicClient)({
627
638
  ...config,
628
639
  kv: config.kv ?? local,
629
- tokenStore: config.tokenStore ?? defaultTokenStore,
640
+ tokenStore,
630
641
  sessionStorage: config.sessionStorage ?? session,
631
642
  replicaStore: config.replicaStore ?? (canPersistReplica ? new PersistenceStore(config.clientId) : new import_core4.MemoryReplicaStoreFactory()),
632
643
  navigate: config.navigate ?? browserNavigate,
633
644
  currentUrl: config.currentUrl ?? browserCurrentUrl,
634
645
  replaceUrl: config.replaceUrl ?? browserReplaceUrl,
635
646
  reconcileTrigger: config.reconcileTrigger ?? browserReconcileTrigger,
636
- createMessageChannel: config.createMessageChannel ?? (canBroadcast && defaultTokenStore ? createBrowserAuthChannelFactory(config.clientId, defaultTokenStore) : void 0),
647
+ createMessageChannel: config.createMessageChannel ?? messageChannel,
637
648
  uploadTransport: config.uploadTransport ?? (typeof XMLHttpRequest === "undefined" ? void 0 : browserUploadTransport)
638
649
  });
639
650
  }
640
651
 
641
652
  // src/provider.tsx
642
653
  var import_react2 = require("react");
643
-
644
- // src/context.ts
645
- var import_react = require("react");
646
- var BasicClientContext = (0, import_react.createContext)(null);
647
-
648
- // src/provider.tsx
649
654
  var import_jsx_runtime = require("react/jsx-runtime");
650
655
  var clientLifecycles = /* @__PURE__ */ new WeakMap();
651
656
  function retainClient(client) {
@@ -727,6 +732,10 @@ function useRequiredClient() {
727
732
  function useClientSnapshot(client) {
728
733
  return (0, import_react3.useSyncExternalStore)(client.subscribe, client.getSnapshot, client.getSnapshot);
729
734
  }
735
+ function useSignInGate(snapshot) {
736
+ const code = snapshot.isReady && !snapshot.isSignedIn ? snapshot.readOnlyReason ?? "AUTHORIZATION_REQUIRED" : null;
737
+ return (0, import_react3.useMemo)(() => code === null ? null : new import_core5.BasicError(code, code === "AUTH_EXPIRED" ? "Session expired. Sign in again to load this list." : "Sign in to load this list."), [code]);
738
+ }
730
739
  function toBasicError(error, fallback = "UNKNOWN_ERROR") {
731
740
  if (error instanceof import_core5.BasicError) return error;
732
741
  return new import_core5.BasicError(fallback, error instanceof Error ? error.message : String(error));
@@ -779,11 +788,11 @@ function useAccountsFor(client) {
779
788
  const removeAccount = (0, import_react4.useCallback)((id) => client.removeAccount(id), [client]);
780
789
  return (0, import_react4.useMemo)(() => ({
781
790
  accounts: snapshot.accounts,
782
- active: snapshot.activeProfile,
791
+ activeAccount: snapshot.activeAccount,
783
792
  switchAccount,
784
793
  addAccount,
785
794
  removeAccount
786
- }), [snapshot.accounts, snapshot.activeProfile, switchAccount, addAccount, removeAccount]);
795
+ }), [snapshot.accounts, snapshot.activeAccount, switchAccount, addAccount, removeAccount]);
787
796
  }
788
797
  function useAccounts() {
789
798
  return useAccountsFor(useRequiredClient());
@@ -791,7 +800,6 @@ function useAccounts() {
791
800
 
792
801
  // src/hooks/auth.ts
793
802
  var import_react5 = require("react");
794
- var import_core6 = require("@basictech/core");
795
803
  function useAuthFor(client) {
796
804
  const snapshot = useClientSnapshot(client);
797
805
  const signIn = (0, import_react5.useCallback)((input) => client.signIn(input), [client]);
@@ -804,7 +812,7 @@ function useAuthFor(client) {
804
812
  canWrite: snapshot.canWrite,
805
813
  readOnlyReason: snapshot.readOnlyReason,
806
814
  status: snapshot.authStatus,
807
- error: snapshot.authErrorCode ? new import_core6.BasicError(snapshot.authErrorCode) : null,
815
+ error: snapshot.authError,
808
816
  user: snapshot.user,
809
817
  did: snapshot.did,
810
818
  handle: snapshot.handle,
@@ -817,6 +825,9 @@ function useAuth() {
817
825
  return useAuthFor(useRequiredClient());
818
826
  }
819
827
 
828
+ // src/hooks/basic.ts
829
+ var import_react8 = require("react");
830
+
820
831
  // src/hooks/db.ts
821
832
  function useDbFor(client, source = "default") {
822
833
  return client.db(source);
@@ -893,51 +904,41 @@ function useBasicFor(client) {
893
904
  const auth = useAuthFor(client);
894
905
  const accounts = useAccountsFor(client);
895
906
  const sync = useSyncStatusFor(client);
896
- const repos = useReposFor(client);
897
- return {
907
+ const repos = useReposFor(client).repos;
908
+ const db = useDbFor(client);
909
+ return (0, import_react8.useMemo)(() => ({
898
910
  ...auth,
899
911
  client,
900
- db: useDbFor(client),
912
+ db,
901
913
  accounts,
902
914
  sync,
903
- repos: repos.repos
904
- };
915
+ repos
916
+ }), [auth, client, db, accounts, sync, repos]);
905
917
  }
906
918
  function useBasic() {
907
919
  return useBasicFor(useRequiredClient());
908
920
  }
909
921
 
910
922
  // src/hooks/files.ts
911
- var import_react8 = require("react");
923
+ var import_react9 = require("react");
912
924
  var zero = () => 0;
913
925
  function useFilesFor(client, query = {}, options = {}) {
914
926
  const source = options.source ?? "default";
915
927
  const sourceKey = JSON.stringify(source);
916
928
  const queryKey = JSON.stringify(query);
917
929
  const snapshot = useClientSnapshot(client);
918
- const subscription = (0, import_react8.useMemo)(
930
+ const subscription = (0, import_react9.useMemo)(
919
931
  () => client.queryStore.collection(source, "_files"),
920
932
  // eslint-disable-next-line react-hooks/exhaustive-deps
921
933
  [client, sourceKey]
922
934
  );
923
- const version = (0, import_react8.useSyncExternalStore)(subscription.subscribe, subscription.getSnapshot, zero);
935
+ const version = (0, import_react9.useSyncExternalStore)(subscription.subscribe, subscription.getSnapshot, zero);
924
936
  return useAsyncResult(
925
- async () => {
926
- const files = client.db(source).files;
927
- if (source !== "default" && "mountId" in source) {
928
- const page = await files.list({
929
- ...query.prefix !== void 0 ? { prefix: query.prefix } : {},
930
- ...query.limit !== void 0 ? { limit: query.limit } : {},
931
- ...query.cursor !== void 0 ? { cursor: query.cursor } : {}
932
- });
933
- return page.data;
934
- }
935
- return (await files.list(query)).data;
936
- },
937
+ async () => (await client.db(source).files.list(query)).data,
937
938
  [],
938
939
  snapshot.isReady,
939
940
  snapshot.isReady,
940
- [sourceKey, queryKey, version, snapshot.activeProfile?.id]
941
+ [sourceKey, queryKey, version, snapshot.activeAccount?.id]
941
942
  );
942
943
  }
943
944
  function useFiles(query = {}, options = {}) {
@@ -945,61 +946,62 @@ function useFiles(query = {}, options = {}) {
945
946
  }
946
947
  function useStorageInfoFor(client) {
947
948
  const snapshot = useClientSnapshot(client);
948
- return useAsyncResult(
949
+ const gate = useSignInGate(snapshot);
950
+ const result = useAsyncResult(
949
951
  () => client.storageInfo(client.resolveRepoId()),
950
952
  null,
951
953
  snapshot.isReady && snapshot.isSignedIn,
952
954
  snapshot.isReady,
953
- [snapshot.activeProfile?.id]
955
+ [snapshot.activeAccount?.id]
954
956
  );
957
+ return { ...result, error: result.error ?? gate };
955
958
  }
956
959
  function useStorageInfo() {
957
960
  return useStorageInfoFor(useRequiredClient());
958
961
  }
959
962
 
960
963
  // src/hooks/mounts.ts
961
- var import_react9 = require("react");
964
+ var import_react10 = require("react");
962
965
  function useMountsFor(client, query = {}) {
963
966
  const snapshot = useClientSnapshot(client);
967
+ const gate = useSignInGate(snapshot);
964
968
  const queryKey = JSON.stringify(query);
965
969
  const result = useAsyncResult(
966
970
  () => client.shares.mounts(query),
967
971
  [],
968
972
  snapshot.isReady && snapshot.isSignedIn,
969
973
  snapshot.isReady,
970
- [queryKey, snapshot.activeProfile?.id]
974
+ [queryKey, snapshot.activeAccount?.id]
971
975
  );
972
- const open = (0, import_react9.useCallback)(
976
+ const open = (0, import_react10.useCallback)(
973
977
  (mountId) => client.shares.mount(mountId),
974
978
  [client]
975
979
  );
976
- const manageUrl = (0, import_react9.useCallback)(() => client.shares.manageUrl(), [client]);
977
- const { data: mounts, ...state } = result;
978
- return { data: mounts, mounts, ...state, open, manageUrl };
980
+ const manageUrl = (0, import_react10.useCallback)(() => client.shares.manageUrl(), [client]);
981
+ return { ...result, error: result.error ?? gate, open, manageUrl };
979
982
  }
980
983
  function useMounts(query = {}) {
981
984
  return useMountsFor(useRequiredClient(), query);
982
985
  }
983
986
  function useOutgoingSharesFor(client) {
984
987
  const snapshot = useClientSnapshot(client);
988
+ const gate = useSignInGate(snapshot);
985
989
  const result = useAsyncResult(
986
990
  () => client.shares.listOutgoing(),
987
991
  [],
988
992
  snapshot.isReady && snapshot.isSignedIn,
989
993
  snapshot.isReady,
990
- [snapshot.activeProfile?.id]
994
+ [snapshot.activeAccount?.id]
991
995
  );
992
- const create = (0, import_react9.useCallback)((input) => client.shares.createOutgoing(input), [client]);
993
- const get = (0, import_react9.useCallback)((id) => client.shares.getOutgoing(id), [client]);
994
- const cancel = (0, import_react9.useCallback)((id) => client.shares.cancel(id), [client]);
995
- const revoke = (0, import_react9.useCallback)((id) => client.shares.revoke(id), [client]);
996
- const getContactHandle = (0, import_react9.useCallback)((did) => client.shares.getContactHandle(did), [client]);
997
- const manageUrl = (0, import_react9.useCallback)(() => client.shares.manageUrl(), [client]);
998
- const { data: outgoingShares, ...state } = result;
996
+ const create = (0, import_react10.useCallback)((input) => client.shares.createOutgoing(input), [client]);
997
+ const get = (0, import_react10.useCallback)((id) => client.shares.getOutgoing(id), [client]);
998
+ const cancel = (0, import_react10.useCallback)((id) => client.shares.cancel(id), [client]);
999
+ const revoke = (0, import_react10.useCallback)((id) => client.shares.revoke(id), [client]);
1000
+ const getContactHandle = (0, import_react10.useCallback)((did) => client.shares.getContactHandle(did), [client]);
1001
+ const manageUrl = (0, import_react10.useCallback)(() => client.shares.manageUrl(), [client]);
999
1002
  return {
1000
- data: outgoingShares,
1001
- outgoingShares,
1002
- ...state,
1003
+ ...result,
1004
+ error: result.error ?? gate,
1003
1005
  create,
1004
1006
  get,
1005
1007
  cancel,
@@ -1013,58 +1015,35 @@ function useOutgoingShares() {
1013
1015
  }
1014
1016
 
1015
1017
  // src/hooks/query.ts
1016
- var import_react10 = require("react");
1017
- var subscribeNever = () => () => {
1018
- };
1019
- var zero2 = () => 0;
1018
+ var import_react11 = require("react");
1020
1019
  function useQueryValueFor(client, collection, query = {}, options = {}) {
1021
1020
  const source = options.source ?? "default";
1022
1021
  const sourceKey = JSON.stringify(source);
1023
1022
  const queryKey = JSON.stringify(query);
1024
1023
  const snapshot = useClientSnapshot(client);
1025
- const subscription = (0, import_react10.useMemo)(
1026
- () => client.queryStore.collection(source, collection),
1027
- // sourceKey captures semantic source identity rather than object identity.
1028
- // eslint-disable-next-line react-hooks/exhaustive-deps
1029
- [client, sourceKey, collection]
1030
- );
1031
- const version = (0, import_react10.useSyncExternalStore)(
1032
- subscription?.subscribe ?? subscribeNever,
1033
- subscription?.getSnapshot ?? zero2,
1034
- zero2
1035
- );
1036
- const [result, setResult] = (0, import_react10.useState)({
1024
+ const [result, setResult] = (0, import_react11.useState)({
1037
1025
  data: [],
1038
1026
  isLoading: true,
1039
1027
  error: null
1040
1028
  });
1041
- (0, import_react10.useEffect)(() => {
1042
- let active = true;
1043
- if (!snapshot.isReady) {
1044
- setResult((current) => ({ ...current, isLoading: true, error: null }));
1045
- return () => {
1046
- active = false;
1047
- };
1048
- }
1029
+ (0, import_react11.useEffect)(() => {
1049
1030
  setResult((current) => ({ ...current, isLoading: true, error: null }));
1050
- const collectionApi = client.db(source).collection(collection);
1051
- const list = collectionApi.list;
1052
- void list.call(collectionApi, query).then(
1053
- (page) => {
1054
- if (active) setResult({ data: page.data, isLoading: false, error: null });
1055
- },
1056
- (error) => {
1057
- if (active) setResult((current) => ({
1031
+ if (!snapshot.isReady) return;
1032
+ try {
1033
+ return client.db(source).collection(collection).watch(
1034
+ query,
1035
+ (page) => setResult({ data: page.data, isLoading: false, error: null }),
1036
+ (error) => setResult((current) => ({
1058
1037
  ...current,
1059
1038
  isLoading: false,
1060
1039
  error: toBasicError(error, "QUERY_FAILED")
1061
- }));
1062
- }
1063
- );
1064
- return () => {
1065
- active = false;
1066
- };
1067
- }, [client, collection, sourceKey, queryKey, version, snapshot.isReady]);
1040
+ }))
1041
+ );
1042
+ } catch (error) {
1043
+ setResult((current) => ({ ...current, isLoading: false, error: toBasicError(error, "QUERY_FAILED") }));
1044
+ return;
1045
+ }
1046
+ }, [client, collection, sourceKey, queryKey, snapshot.isReady]);
1068
1047
  return result;
1069
1048
  }
1070
1049
  function useQueryFor(client, collection, query = {}, options = {}) {
@@ -1081,25 +1060,31 @@ function createBasic(config) {
1081
1060
  function Provider({ children, renderWhileLoading }) {
1082
1061
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BasicProvider, { client, renderWhileLoading, children });
1083
1062
  }
1063
+ function useBound() {
1064
+ if ((0, import_react12.useContext)(BasicClientContext) !== client) {
1065
+ throw new Error("Bound Basic hooks must be used within their own <basic.Provider>");
1066
+ }
1067
+ return client;
1068
+ }
1084
1069
  function useBoundDb(source = "default") {
1085
- return client.db(source);
1070
+ return useBound().db(source);
1086
1071
  }
1087
1072
  return {
1088
1073
  client,
1089
1074
  Provider,
1090
- useBasic: () => useBasicFor(client),
1091
- useAuth: () => useAuthFor(client),
1092
- useAccounts: () => useAccountsFor(client),
1075
+ useBasic: () => useBasicFor(useBound()),
1076
+ useAuth: () => useAuthFor(useBound()),
1077
+ useAccounts: () => useAccountsFor(useBound()),
1093
1078
  useDb: useBoundDb,
1094
- useCollection: (name, options = {}) => useCollectionFor(client, name, options),
1095
- useQuery: (collection, query = {}, options = {}) => useQueryFor(client, collection, query, options),
1096
- useSyncStatus: (source) => useSyncStatusFor(client, source),
1097
- useSchemaStatus: (source = "default") => useSchemaStatusFor(client, source),
1098
- useRepos: () => useReposFor(client),
1099
- useFiles: (query = {}, options = {}) => useFilesFor(client, query, options),
1100
- useStorageInfo: () => useStorageInfoFor(client),
1101
- useMounts: (query = {}) => useMountsFor(client, query),
1102
- useOutgoingShares: () => useOutgoingSharesFor(client)
1079
+ useCollection: (name, options = {}) => useCollectionFor(useBound(), name, options),
1080
+ useQuery: (collection, query = {}, options = {}) => useQueryFor(useBound(), collection, query, options),
1081
+ useSyncStatus: (source) => useSyncStatusFor(useBound(), source),
1082
+ useSchemaStatus: (source = "default") => useSchemaStatusFor(useBound(), source),
1083
+ useRepos: () => useReposFor(useBound()),
1084
+ useFiles: (query = {}, options = {}) => useFilesFor(useBound(), query, options),
1085
+ useStorageInfo: () => useStorageInfoFor(useBound()),
1086
+ useMounts: (query = {}) => useMountsFor(useBound(), query),
1087
+ useOutgoingShares: () => useOutgoingSharesFor(useBound())
1103
1088
  };
1104
1089
  }
1105
1090
  // Annotate the CommonJS export names for ESM import in node: