@noy-db/in-react 0.1.0-pre.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vLannaAi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @noy-db/in-react
2
+
3
+ [![npm](https://img.shields.io/npm/v/%40noy-db/in-react.svg)](https://www.npmjs.com/package/@noy-db/in-react)
4
+
5
+ > React hooks for noy-db
6
+
7
+ Part of [**`@noy-db/hub`**](https://www.npmjs.com/package/@noy-db/hub) — the zero-knowledge, offline-first, encrypted document store.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add @noy-db/hub @noy-db/in-react
13
+ ```
14
+
15
+ ## What it is
16
+
17
+ React hooks for noy-db — useNoydb, useVault, useCollection, useQuery, useSync. Uses useSyncExternalStore for reactive subscriptions. Zero deps beyond React.
18
+
19
+ ## Status
20
+
21
+ **Pre-release** (`0.1.0-pre.1`). API may change before `1.0`.
22
+
23
+ ## Documentation
24
+
25
+ See the [main repository](https://github.com/vLannaAi/noy-db#readme) for setup, examples, and the full subsystem catalog.
26
+
27
+ - Source — [`packages/in-react`](https://github.com/vLannaAi/noy-db/tree/main/packages/in-react)
28
+ - Issues — [github.com/vLannaAi/noy-db/issues](https://github.com/vLannaAi/noy-db/issues)
29
+ - Spec — [`SPEC.md`](https://github.com/vLannaAi/noy-db/blob/main/SPEC.md)
30
+
31
+ ## License
32
+
33
+ [MIT](./LICENSE) © vLannaAi
package/dist/index.cjs ADDED
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ NoydbProvider: () => NoydbProvider,
24
+ useCollection: () => useCollection,
25
+ useNoydb: () => useNoydb,
26
+ useQuery: () => useQuery,
27
+ useSync: () => useSync,
28
+ useVault: () => useVault
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+ var import_react = require("react");
32
+ var NoydbContext = (0, import_react.createContext)(null);
33
+ function NoydbProvider(props) {
34
+ return (0, import_react.createElement)(NoydbContext.Provider, { value: props.db }, props.children);
35
+ }
36
+ function useNoydb() {
37
+ const db = (0, import_react.useContext)(NoydbContext);
38
+ if (!db) {
39
+ throw new Error(
40
+ "[@noy-db/in-react] useNoydb(): no NoydbProvider found in the React tree. Wrap your app with <NoydbProvider db={db}>."
41
+ );
42
+ }
43
+ return db;
44
+ }
45
+ function useVault(name, options) {
46
+ const db = useNoydb();
47
+ const [state, setState] = (0, import_react.useState)({ vault: null, loading: true, error: null });
48
+ (0, import_react.useEffect)(() => {
49
+ let cancelled = false;
50
+ setState({ vault: null, loading: true, error: null });
51
+ db.openVault(name, options).then((v) => {
52
+ if (!cancelled) setState({ vault: v, loading: false, error: null });
53
+ }).catch((err) => {
54
+ if (!cancelled) setState({ vault: null, loading: false, error: err });
55
+ });
56
+ return () => {
57
+ cancelled = true;
58
+ };
59
+ }, [db, name, options?.locale]);
60
+ return state;
61
+ }
62
+ function useCollection(vault, collectionName) {
63
+ const [state, setState] = (0, import_react.useState)(() => ({
64
+ data: [],
65
+ loading: true,
66
+ error: null
67
+ }));
68
+ const coll = (0, import_react.useMemo)(
69
+ () => vault ? vault.collection(collectionName) : null,
70
+ [vault, collectionName]
71
+ );
72
+ (0, import_react.useEffect)(() => {
73
+ if (!coll) {
74
+ setState({ data: [], loading: true, error: null });
75
+ return;
76
+ }
77
+ let cancelled = false;
78
+ const refresh = async () => {
79
+ try {
80
+ const records = await coll.list();
81
+ if (!cancelled) setState({ data: records, loading: false, error: null });
82
+ } catch (err) {
83
+ if (!cancelled) setState({ data: [], loading: false, error: err });
84
+ }
85
+ };
86
+ void refresh();
87
+ const unsubscribe = coll.subscribe(() => {
88
+ void refresh();
89
+ });
90
+ return () => {
91
+ cancelled = true;
92
+ unsubscribe();
93
+ };
94
+ }, [coll]);
95
+ return state;
96
+ }
97
+ function useQuery(vault, collectionName, builder, deps = []) {
98
+ const [state, setState] = (0, import_react.useState)(() => ({
99
+ data: null,
100
+ loading: true,
101
+ error: null
102
+ }));
103
+ const coll = (0, import_react.useMemo)(
104
+ () => vault ? vault.collection(collectionName) : null,
105
+ [vault, collectionName]
106
+ );
107
+ (0, import_react.useEffect)(() => {
108
+ if (!coll) return;
109
+ let cancelled = false;
110
+ const run = async () => {
111
+ try {
112
+ const result = await Promise.resolve(builder(coll.query()));
113
+ if (!cancelled) setState({ data: result, loading: false, error: null });
114
+ } catch (err) {
115
+ if (!cancelled) setState({ data: null, loading: false, error: err });
116
+ }
117
+ };
118
+ void run();
119
+ const unsubscribe = coll.subscribe(() => {
120
+ void run();
121
+ });
122
+ return () => {
123
+ cancelled = true;
124
+ unsubscribe();
125
+ };
126
+ }, [coll, ...deps]);
127
+ return state;
128
+ }
129
+ function useSync(db) {
130
+ const ctx = (0, import_react.useContext)(NoydbContext);
131
+ const instance = db ?? ctx;
132
+ if (!instance) {
133
+ throw new Error("[@noy-db/in-react] useSync(): no Noydb instance (pass explicitly or wrap in <NoydbProvider>).");
134
+ }
135
+ const [state, setState] = (0, import_react.useState)({ lastEvent: null, error: null });
136
+ (0, import_react.useEffect)(() => {
137
+ const handler = (event) => {
138
+ setState({ lastEvent: event, error: null });
139
+ };
140
+ instance.on("change", handler);
141
+ return () => {
142
+ instance.off("change", handler);
143
+ };
144
+ }, [instance]);
145
+ return state;
146
+ }
147
+ // Annotate the CommonJS export names for ESM import in node:
148
+ 0 && (module.exports = {
149
+ NoydbProvider,
150
+ useCollection,
151
+ useNoydb,
152
+ useQuery,
153
+ useSync,
154
+ useVault
155
+ });
156
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/in-react** — React hooks for noy-db.\n *\n * Five hooks that live on top of React 18+'s `useSyncExternalStore`:\n *\n * - {@link useNoydb} — read the `Noydb` instance from context\n * - {@link useVault} — open a vault, subscribed for lifecycle\n * - {@link useCollection} — reactive record list\n * - {@link useQuery} — reactive result of a query builder\n * - {@link useSync} — reactive sync state\n *\n * Change events drive re-renders via `useSyncExternalStore`, which\n * concurrent-mode-safely bridges an external subscription into React's\n * scheduling. No extra state library needed.\n *\n * @packageDocumentation\n */\n\nimport {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react'\nimport type { Noydb, Vault, ChangeEvent, Query } from '@noy-db/hub'\n\n// ─── Context ────────────────────────────────────────────────────────────\n\nconst NoydbContext = createContext<Noydb | null>(null)\n\nexport interface NoydbProviderProps {\n readonly db: Noydb\n readonly children: ReactNode\n}\n\n/** Provides a `Noydb` instance to every component under this tree. */\nexport function NoydbProvider(props: NoydbProviderProps): ReactNode {\n return createElement(NoydbContext.Provider, { value: props.db }, props.children)\n}\n\n/** Access the Noydb instance supplied by the nearest `<NoydbProvider>`. */\nexport function useNoydb(): Noydb {\n const db = useContext(NoydbContext)\n if (!db) {\n throw new Error(\n '[@noy-db/in-react] useNoydb(): no NoydbProvider found in the React tree. ' +\n 'Wrap your app with <NoydbProvider db={db}>.',\n )\n }\n return db\n}\n\n// ─── useVault ───────────────────────────────────────────────────────────\n\nexport interface UseVaultState {\n readonly vault: Vault | null\n readonly loading: boolean\n readonly error: Error | null\n}\n\n/**\n * Open a vault by name and track its lifecycle. The vault is opened\n * on mount (re-opened when `name` changes). Optional `locale` is\n * forwarded to the hub; secret / biometric unlock happens out-of-band\n * (create the Noydb instance with `secret`, or use `@noy-db/on-*`).\n */\nexport function useVault(name: string, options?: { locale?: string }): UseVaultState {\n const db = useNoydb()\n const [state, setState] = useState<UseVaultState>({ vault: null, loading: true, error: null })\n\n useEffect(() => {\n let cancelled = false\n setState({ vault: null, loading: true, error: null })\n db.openVault(name, options)\n .then((v) => {\n if (!cancelled) setState({ vault: v, loading: false, error: null })\n })\n .catch((err: Error) => {\n if (!cancelled) setState({ vault: null, loading: false, error: err })\n })\n return () => {\n cancelled = true\n }\n }, [db, name, options?.locale])\n\n return state\n}\n\n// ─── useCollection ──────────────────────────────────────────────────────\n\n/**\n * Reactive list of every record in a collection. Auto-refreshes on\n * any change event from the hub — put, delete, or sync.\n */\nexport function useCollection<T>(\n vault: Vault | null,\n collectionName: string,\n): { data: T[]; loading: boolean; error: Error | null } {\n const [state, setState] = useState<{ data: T[]; loading: boolean; error: Error | null }>(() => ({\n data: [], loading: true, error: null,\n }))\n\n const coll = useMemo(\n () => (vault ? vault.collection<T>(collectionName) : null),\n [vault, collectionName],\n )\n\n useEffect(() => {\n if (!coll) {\n setState({ data: [], loading: true, error: null })\n return\n }\n let cancelled = false\n const refresh = async (): Promise<void> => {\n try {\n const records = await coll.list()\n if (!cancelled) setState({ data: records, loading: false, error: null })\n } catch (err) {\n if (!cancelled) setState({ data: [], loading: false, error: err as Error })\n }\n }\n void refresh()\n const unsubscribe = coll.subscribe(() => { void refresh() })\n return () => {\n cancelled = true\n unsubscribe()\n }\n }, [coll])\n\n return state\n}\n\n// ─── useQuery ───────────────────────────────────────────────────────────\n\n/**\n * Reactive result of a query builder. The builder is re-executed\n * whenever the collection's change stream fires.\n */\nexport function useQuery<T, R>(\n vault: Vault | null,\n collectionName: string,\n builder: (q: Query<T>) => Promise<R> | R,\n deps: readonly unknown[] = [],\n): { data: R | null; loading: boolean; error: Error | null } {\n const [state, setState] = useState<{ data: R | null; loading: boolean; error: Error | null }>(() => ({\n data: null, loading: true, error: null,\n }))\n\n const coll = useMemo(\n () => (vault ? vault.collection<T>(collectionName) : null),\n [vault, collectionName],\n )\n\n useEffect(() => {\n if (!coll) return\n let cancelled = false\n const run = async (): Promise<void> => {\n try {\n const result = await Promise.resolve(builder(coll.query() as unknown as Query<T>))\n if (!cancelled) setState({ data: result, loading: false, error: null })\n } catch (err) {\n if (!cancelled) setState({ data: null, loading: false, error: err as Error })\n }\n }\n void run()\n const unsubscribe = coll.subscribe(() => { void run() })\n return () => {\n cancelled = true\n unsubscribe()\n }\n }, [coll, ...deps])\n\n return state\n}\n\n// ─── useSync ────────────────────────────────────────────────────────────\n\nexport interface UseSyncState {\n readonly lastEvent: ChangeEvent | null\n readonly error: Error | null\n}\n\n/**\n * Subscribe to the hub's cross-collection change stream. Useful for\n * top-level status indicators (\"unsynced changes\", last-update time).\n */\nexport function useSync(db?: Noydb): UseSyncState {\n const ctx = useContext(NoydbContext)\n const instance = db ?? ctx\n if (!instance) {\n throw new Error('[@noy-db/in-react] useSync(): no Noydb instance (pass explicitly or wrap in <NoydbProvider>).')\n }\n const [state, setState] = useState<UseSyncState>({ lastEvent: null, error: null })\n\n useEffect(() => {\n const handler = (event: ChangeEvent): void => {\n setState({ lastEvent: event, error: null })\n }\n instance.on('change', handler)\n return () => {\n instance.off('change', handler)\n }\n }, [instance])\n\n return state\n}\n\n// ─── Re-exports for convenience ─────────────────────────────────────────\n\nexport type { Noydb, Vault, Collection, ChangeEvent } from '@noy-db/hub'\n"],"mappings":"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","names":[]}
@@ -0,0 +1,73 @@
1
+ import { ReactNode } from 'react';
2
+ import { Noydb, ChangeEvent, Vault, Query } from '@noy-db/hub';
3
+ export { ChangeEvent, Collection, Noydb, Vault } from '@noy-db/hub';
4
+
5
+ /**
6
+ * **@noy-db/in-react** — React hooks for noy-db.
7
+ *
8
+ * Five hooks that live on top of React 18+'s `useSyncExternalStore`:
9
+ *
10
+ * - {@link useNoydb} — read the `Noydb` instance from context
11
+ * - {@link useVault} — open a vault, subscribed for lifecycle
12
+ * - {@link useCollection} — reactive record list
13
+ * - {@link useQuery} — reactive result of a query builder
14
+ * - {@link useSync} — reactive sync state
15
+ *
16
+ * Change events drive re-renders via `useSyncExternalStore`, which
17
+ * concurrent-mode-safely bridges an external subscription into React's
18
+ * scheduling. No extra state library needed.
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+
23
+ interface NoydbProviderProps {
24
+ readonly db: Noydb;
25
+ readonly children: ReactNode;
26
+ }
27
+ /** Provides a `Noydb` instance to every component under this tree. */
28
+ declare function NoydbProvider(props: NoydbProviderProps): ReactNode;
29
+ /** Access the Noydb instance supplied by the nearest `<NoydbProvider>`. */
30
+ declare function useNoydb(): Noydb;
31
+ interface UseVaultState {
32
+ readonly vault: Vault | null;
33
+ readonly loading: boolean;
34
+ readonly error: Error | null;
35
+ }
36
+ /**
37
+ * Open a vault by name and track its lifecycle. The vault is opened
38
+ * on mount (re-opened when `name` changes). Optional `locale` is
39
+ * forwarded to the hub; secret / biometric unlock happens out-of-band
40
+ * (create the Noydb instance with `secret`, or use `@noy-db/on-*`).
41
+ */
42
+ declare function useVault(name: string, options?: {
43
+ locale?: string;
44
+ }): UseVaultState;
45
+ /**
46
+ * Reactive list of every record in a collection. Auto-refreshes on
47
+ * any change event from the hub — put, delete, or sync.
48
+ */
49
+ declare function useCollection<T>(vault: Vault | null, collectionName: string): {
50
+ data: T[];
51
+ loading: boolean;
52
+ error: Error | null;
53
+ };
54
+ /**
55
+ * Reactive result of a query builder. The builder is re-executed
56
+ * whenever the collection's change stream fires.
57
+ */
58
+ declare function useQuery<T, R>(vault: Vault | null, collectionName: string, builder: (q: Query<T>) => Promise<R> | R, deps?: readonly unknown[]): {
59
+ data: R | null;
60
+ loading: boolean;
61
+ error: Error | null;
62
+ };
63
+ interface UseSyncState {
64
+ readonly lastEvent: ChangeEvent | null;
65
+ readonly error: Error | null;
66
+ }
67
+ /**
68
+ * Subscribe to the hub's cross-collection change stream. Useful for
69
+ * top-level status indicators ("unsynced changes", last-update time).
70
+ */
71
+ declare function useSync(db?: Noydb): UseSyncState;
72
+
73
+ export { NoydbProvider, type NoydbProviderProps, type UseSyncState, type UseVaultState, useCollection, useNoydb, useQuery, useSync, useVault };
@@ -0,0 +1,73 @@
1
+ import { ReactNode } from 'react';
2
+ import { Noydb, ChangeEvent, Vault, Query } from '@noy-db/hub';
3
+ export { ChangeEvent, Collection, Noydb, Vault } from '@noy-db/hub';
4
+
5
+ /**
6
+ * **@noy-db/in-react** — React hooks for noy-db.
7
+ *
8
+ * Five hooks that live on top of React 18+'s `useSyncExternalStore`:
9
+ *
10
+ * - {@link useNoydb} — read the `Noydb` instance from context
11
+ * - {@link useVault} — open a vault, subscribed for lifecycle
12
+ * - {@link useCollection} — reactive record list
13
+ * - {@link useQuery} — reactive result of a query builder
14
+ * - {@link useSync} — reactive sync state
15
+ *
16
+ * Change events drive re-renders via `useSyncExternalStore`, which
17
+ * concurrent-mode-safely bridges an external subscription into React's
18
+ * scheduling. No extra state library needed.
19
+ *
20
+ * @packageDocumentation
21
+ */
22
+
23
+ interface NoydbProviderProps {
24
+ readonly db: Noydb;
25
+ readonly children: ReactNode;
26
+ }
27
+ /** Provides a `Noydb` instance to every component under this tree. */
28
+ declare function NoydbProvider(props: NoydbProviderProps): ReactNode;
29
+ /** Access the Noydb instance supplied by the nearest `<NoydbProvider>`. */
30
+ declare function useNoydb(): Noydb;
31
+ interface UseVaultState {
32
+ readonly vault: Vault | null;
33
+ readonly loading: boolean;
34
+ readonly error: Error | null;
35
+ }
36
+ /**
37
+ * Open a vault by name and track its lifecycle. The vault is opened
38
+ * on mount (re-opened when `name` changes). Optional `locale` is
39
+ * forwarded to the hub; secret / biometric unlock happens out-of-band
40
+ * (create the Noydb instance with `secret`, or use `@noy-db/on-*`).
41
+ */
42
+ declare function useVault(name: string, options?: {
43
+ locale?: string;
44
+ }): UseVaultState;
45
+ /**
46
+ * Reactive list of every record in a collection. Auto-refreshes on
47
+ * any change event from the hub — put, delete, or sync.
48
+ */
49
+ declare function useCollection<T>(vault: Vault | null, collectionName: string): {
50
+ data: T[];
51
+ loading: boolean;
52
+ error: Error | null;
53
+ };
54
+ /**
55
+ * Reactive result of a query builder. The builder is re-executed
56
+ * whenever the collection's change stream fires.
57
+ */
58
+ declare function useQuery<T, R>(vault: Vault | null, collectionName: string, builder: (q: Query<T>) => Promise<R> | R, deps?: readonly unknown[]): {
59
+ data: R | null;
60
+ loading: boolean;
61
+ error: Error | null;
62
+ };
63
+ interface UseSyncState {
64
+ readonly lastEvent: ChangeEvent | null;
65
+ readonly error: Error | null;
66
+ }
67
+ /**
68
+ * Subscribe to the hub's cross-collection change stream. Useful for
69
+ * top-level status indicators ("unsynced changes", last-update time).
70
+ */
71
+ declare function useSync(db?: Noydb): UseSyncState;
72
+
73
+ export { NoydbProvider, type NoydbProviderProps, type UseSyncState, type UseVaultState, useCollection, useNoydb, useQuery, useSync, useVault };
package/dist/index.js ADDED
@@ -0,0 +1,133 @@
1
+ // src/index.ts
2
+ import {
3
+ createContext,
4
+ createElement,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useState
9
+ } from "react";
10
+ var NoydbContext = createContext(null);
11
+ function NoydbProvider(props) {
12
+ return createElement(NoydbContext.Provider, { value: props.db }, props.children);
13
+ }
14
+ function useNoydb() {
15
+ const db = useContext(NoydbContext);
16
+ if (!db) {
17
+ throw new Error(
18
+ "[@noy-db/in-react] useNoydb(): no NoydbProvider found in the React tree. Wrap your app with <NoydbProvider db={db}>."
19
+ );
20
+ }
21
+ return db;
22
+ }
23
+ function useVault(name, options) {
24
+ const db = useNoydb();
25
+ const [state, setState] = useState({ vault: null, loading: true, error: null });
26
+ useEffect(() => {
27
+ let cancelled = false;
28
+ setState({ vault: null, loading: true, error: null });
29
+ db.openVault(name, options).then((v) => {
30
+ if (!cancelled) setState({ vault: v, loading: false, error: null });
31
+ }).catch((err) => {
32
+ if (!cancelled) setState({ vault: null, loading: false, error: err });
33
+ });
34
+ return () => {
35
+ cancelled = true;
36
+ };
37
+ }, [db, name, options?.locale]);
38
+ return state;
39
+ }
40
+ function useCollection(vault, collectionName) {
41
+ const [state, setState] = useState(() => ({
42
+ data: [],
43
+ loading: true,
44
+ error: null
45
+ }));
46
+ const coll = useMemo(
47
+ () => vault ? vault.collection(collectionName) : null,
48
+ [vault, collectionName]
49
+ );
50
+ useEffect(() => {
51
+ if (!coll) {
52
+ setState({ data: [], loading: true, error: null });
53
+ return;
54
+ }
55
+ let cancelled = false;
56
+ const refresh = async () => {
57
+ try {
58
+ const records = await coll.list();
59
+ if (!cancelled) setState({ data: records, loading: false, error: null });
60
+ } catch (err) {
61
+ if (!cancelled) setState({ data: [], loading: false, error: err });
62
+ }
63
+ };
64
+ void refresh();
65
+ const unsubscribe = coll.subscribe(() => {
66
+ void refresh();
67
+ });
68
+ return () => {
69
+ cancelled = true;
70
+ unsubscribe();
71
+ };
72
+ }, [coll]);
73
+ return state;
74
+ }
75
+ function useQuery(vault, collectionName, builder, deps = []) {
76
+ const [state, setState] = useState(() => ({
77
+ data: null,
78
+ loading: true,
79
+ error: null
80
+ }));
81
+ const coll = useMemo(
82
+ () => vault ? vault.collection(collectionName) : null,
83
+ [vault, collectionName]
84
+ );
85
+ useEffect(() => {
86
+ if (!coll) return;
87
+ let cancelled = false;
88
+ const run = async () => {
89
+ try {
90
+ const result = await Promise.resolve(builder(coll.query()));
91
+ if (!cancelled) setState({ data: result, loading: false, error: null });
92
+ } catch (err) {
93
+ if (!cancelled) setState({ data: null, loading: false, error: err });
94
+ }
95
+ };
96
+ void run();
97
+ const unsubscribe = coll.subscribe(() => {
98
+ void run();
99
+ });
100
+ return () => {
101
+ cancelled = true;
102
+ unsubscribe();
103
+ };
104
+ }, [coll, ...deps]);
105
+ return state;
106
+ }
107
+ function useSync(db) {
108
+ const ctx = useContext(NoydbContext);
109
+ const instance = db ?? ctx;
110
+ if (!instance) {
111
+ throw new Error("[@noy-db/in-react] useSync(): no Noydb instance (pass explicitly or wrap in <NoydbProvider>).");
112
+ }
113
+ const [state, setState] = useState({ lastEvent: null, error: null });
114
+ useEffect(() => {
115
+ const handler = (event) => {
116
+ setState({ lastEvent: event, error: null });
117
+ };
118
+ instance.on("change", handler);
119
+ return () => {
120
+ instance.off("change", handler);
121
+ };
122
+ }, [instance]);
123
+ return state;
124
+ }
125
+ export {
126
+ NoydbProvider,
127
+ useCollection,
128
+ useNoydb,
129
+ useQuery,
130
+ useSync,
131
+ useVault
132
+ };
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/in-react** — React hooks for noy-db.\n *\n * Five hooks that live on top of React 18+'s `useSyncExternalStore`:\n *\n * - {@link useNoydb} — read the `Noydb` instance from context\n * - {@link useVault} — open a vault, subscribed for lifecycle\n * - {@link useCollection} — reactive record list\n * - {@link useQuery} — reactive result of a query builder\n * - {@link useSync} — reactive sync state\n *\n * Change events drive re-renders via `useSyncExternalStore`, which\n * concurrent-mode-safely bridges an external subscription into React's\n * scheduling. No extra state library needed.\n *\n * @packageDocumentation\n */\n\nimport {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react'\nimport type { Noydb, Vault, ChangeEvent, Query } from '@noy-db/hub'\n\n// ─── Context ────────────────────────────────────────────────────────────\n\nconst NoydbContext = createContext<Noydb | null>(null)\n\nexport interface NoydbProviderProps {\n readonly db: Noydb\n readonly children: ReactNode\n}\n\n/** Provides a `Noydb` instance to every component under this tree. */\nexport function NoydbProvider(props: NoydbProviderProps): ReactNode {\n return createElement(NoydbContext.Provider, { value: props.db }, props.children)\n}\n\n/** Access the Noydb instance supplied by the nearest `<NoydbProvider>`. */\nexport function useNoydb(): Noydb {\n const db = useContext(NoydbContext)\n if (!db) {\n throw new Error(\n '[@noy-db/in-react] useNoydb(): no NoydbProvider found in the React tree. ' +\n 'Wrap your app with <NoydbProvider db={db}>.',\n )\n }\n return db\n}\n\n// ─── useVault ───────────────────────────────────────────────────────────\n\nexport interface UseVaultState {\n readonly vault: Vault | null\n readonly loading: boolean\n readonly error: Error | null\n}\n\n/**\n * Open a vault by name and track its lifecycle. The vault is opened\n * on mount (re-opened when `name` changes). Optional `locale` is\n * forwarded to the hub; secret / biometric unlock happens out-of-band\n * (create the Noydb instance with `secret`, or use `@noy-db/on-*`).\n */\nexport function useVault(name: string, options?: { locale?: string }): UseVaultState {\n const db = useNoydb()\n const [state, setState] = useState<UseVaultState>({ vault: null, loading: true, error: null })\n\n useEffect(() => {\n let cancelled = false\n setState({ vault: null, loading: true, error: null })\n db.openVault(name, options)\n .then((v) => {\n if (!cancelled) setState({ vault: v, loading: false, error: null })\n })\n .catch((err: Error) => {\n if (!cancelled) setState({ vault: null, loading: false, error: err })\n })\n return () => {\n cancelled = true\n }\n }, [db, name, options?.locale])\n\n return state\n}\n\n// ─── useCollection ──────────────────────────────────────────────────────\n\n/**\n * Reactive list of every record in a collection. Auto-refreshes on\n * any change event from the hub — put, delete, or sync.\n */\nexport function useCollection<T>(\n vault: Vault | null,\n collectionName: string,\n): { data: T[]; loading: boolean; error: Error | null } {\n const [state, setState] = useState<{ data: T[]; loading: boolean; error: Error | null }>(() => ({\n data: [], loading: true, error: null,\n }))\n\n const coll = useMemo(\n () => (vault ? vault.collection<T>(collectionName) : null),\n [vault, collectionName],\n )\n\n useEffect(() => {\n if (!coll) {\n setState({ data: [], loading: true, error: null })\n return\n }\n let cancelled = false\n const refresh = async (): Promise<void> => {\n try {\n const records = await coll.list()\n if (!cancelled) setState({ data: records, loading: false, error: null })\n } catch (err) {\n if (!cancelled) setState({ data: [], loading: false, error: err as Error })\n }\n }\n void refresh()\n const unsubscribe = coll.subscribe(() => { void refresh() })\n return () => {\n cancelled = true\n unsubscribe()\n }\n }, [coll])\n\n return state\n}\n\n// ─── useQuery ───────────────────────────────────────────────────────────\n\n/**\n * Reactive result of a query builder. The builder is re-executed\n * whenever the collection's change stream fires.\n */\nexport function useQuery<T, R>(\n vault: Vault | null,\n collectionName: string,\n builder: (q: Query<T>) => Promise<R> | R,\n deps: readonly unknown[] = [],\n): { data: R | null; loading: boolean; error: Error | null } {\n const [state, setState] = useState<{ data: R | null; loading: boolean; error: Error | null }>(() => ({\n data: null, loading: true, error: null,\n }))\n\n const coll = useMemo(\n () => (vault ? vault.collection<T>(collectionName) : null),\n [vault, collectionName],\n )\n\n useEffect(() => {\n if (!coll) return\n let cancelled = false\n const run = async (): Promise<void> => {\n try {\n const result = await Promise.resolve(builder(coll.query() as unknown as Query<T>))\n if (!cancelled) setState({ data: result, loading: false, error: null })\n } catch (err) {\n if (!cancelled) setState({ data: null, loading: false, error: err as Error })\n }\n }\n void run()\n const unsubscribe = coll.subscribe(() => { void run() })\n return () => {\n cancelled = true\n unsubscribe()\n }\n }, [coll, ...deps])\n\n return state\n}\n\n// ─── useSync ────────────────────────────────────────────────────────────\n\nexport interface UseSyncState {\n readonly lastEvent: ChangeEvent | null\n readonly error: Error | null\n}\n\n/**\n * Subscribe to the hub's cross-collection change stream. Useful for\n * top-level status indicators (\"unsynced changes\", last-update time).\n */\nexport function useSync(db?: Noydb): UseSyncState {\n const ctx = useContext(NoydbContext)\n const instance = db ?? ctx\n if (!instance) {\n throw new Error('[@noy-db/in-react] useSync(): no Noydb instance (pass explicitly or wrap in <NoydbProvider>).')\n }\n const [state, setState] = useState<UseSyncState>({ lastEvent: null, error: null })\n\n useEffect(() => {\n const handler = (event: ChangeEvent): void => {\n setState({ lastEvent: event, error: null })\n }\n instance.on('change', handler)\n return () => {\n instance.off('change', handler)\n }\n }, [instance])\n\n return state\n}\n\n// ─── Re-exports for convenience ─────────────────────────────────────────\n\nexport type { Noydb, Vault, Collection, ChangeEvent } from '@noy-db/hub'\n"],"mappings":";AAkBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAKP,IAAM,eAAe,cAA4B,IAAI;AAQ9C,SAAS,cAAc,OAAsC;AAClE,SAAO,cAAc,aAAa,UAAU,EAAE,OAAO,MAAM,GAAG,GAAG,MAAM,QAAQ;AACjF;AAGO,SAAS,WAAkB;AAChC,QAAM,KAAK,WAAW,YAAY;AAClC,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;AAgBO,SAAS,SAAS,MAAc,SAA8C;AACnF,QAAM,KAAK,SAAS;AACpB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,EAAE,OAAO,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AAE7F,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,aAAS,EAAE,OAAO,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AACpD,OAAG,UAAU,MAAM,OAAO,EACvB,KAAK,CAAC,MAAM;AACX,UAAI,CAAC,UAAW,UAAS,EAAE,OAAO,GAAG,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IACpE,CAAC,EACA,MAAM,CAAC,QAAe;AACrB,UAAI,CAAC,UAAW,UAAS,EAAE,OAAO,MAAM,SAAS,OAAO,OAAO,IAAI,CAAC;AAAA,IACtE,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,MAAM,SAAS,MAAM,CAAC;AAE9B,SAAO;AACT;AAQO,SAAS,cACd,OACA,gBACsD;AACtD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA+D,OAAO;AAAA,IAC9F,MAAM,CAAC;AAAA,IAAG,SAAS;AAAA,IAAM,OAAO;AAAA,EAClC,EAAE;AAEF,QAAM,OAAO;AAAA,IACX,MAAO,QAAQ,MAAM,WAAc,cAAc,IAAI;AAAA,IACrD,CAAC,OAAO,cAAc;AAAA,EACxB;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,MAAM;AACT,eAAS,EAAE,MAAM,CAAC,GAAG,SAAS,MAAM,OAAO,KAAK,CAAC;AACjD;AAAA,IACF;AACA,QAAI,YAAY;AAChB,UAAM,UAAU,YAA2B;AACzC,UAAI;AACF,cAAM,UAAU,MAAM,KAAK,KAAK;AAChC,YAAI,CAAC,UAAW,UAAS,EAAE,MAAM,SAAS,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,MACzE,SAAS,KAAK;AACZ,YAAI,CAAC,UAAW,UAAS,EAAE,MAAM,CAAC,GAAG,SAAS,OAAO,OAAO,IAAa,CAAC;AAAA,MAC5E;AAAA,IACF;AACA,SAAK,QAAQ;AACb,UAAM,cAAc,KAAK,UAAU,MAAM;AAAE,WAAK,QAAQ;AAAA,IAAE,CAAC;AAC3D,WAAO,MAAM;AACX,kBAAY;AACZ,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AACT;AAQO,SAAS,SACd,OACA,gBACA,SACA,OAA2B,CAAC,GAC+B;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAoE,OAAO;AAAA,IACnG,MAAM;AAAA,IAAM,SAAS;AAAA,IAAM,OAAO;AAAA,EACpC,EAAE;AAEF,QAAM,OAAO;AAAA,IACX,MAAO,QAAQ,MAAM,WAAc,cAAc,IAAI;AAAA,IACrD,CAAC,OAAO,cAAc;AAAA,EACxB;AAEA,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,QAAI,YAAY;AAChB,UAAM,MAAM,YAA2B;AACrC,UAAI;AACF,cAAM,SAAS,MAAM,QAAQ,QAAQ,QAAQ,KAAK,MAAM,CAAwB,CAAC;AACjF,YAAI,CAAC,UAAW,UAAS,EAAE,MAAM,QAAQ,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,MACxE,SAAS,KAAK;AACZ,YAAI,CAAC,UAAW,UAAS,EAAE,MAAM,MAAM,SAAS,OAAO,OAAO,IAAa,CAAC;AAAA,MAC9E;AAAA,IACF;AACA,SAAK,IAAI;AACT,UAAM,cAAc,KAAK,UAAU,MAAM;AAAE,WAAK,IAAI;AAAA,IAAE,CAAC;AACvD,WAAO,MAAM;AACX,kBAAY;AACZ,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC;AAElB,SAAO;AACT;AAaO,SAAS,QAAQ,IAA0B;AAChD,QAAM,MAAM,WAAW,YAAY;AACnC,QAAM,WAAW,MAAM;AACvB,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,+FAA+F;AAAA,EACjH;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAEjF,YAAU,MAAM;AACd,UAAM,UAAU,CAAC,UAA6B;AAC5C,eAAS,EAAE,WAAW,OAAO,OAAO,KAAK,CAAC;AAAA,IAC5C;AACA,aAAS,GAAG,UAAU,OAAO;AAC7B,WAAO,MAAM;AACX,eAAS,IAAI,UAAU,OAAO;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO;AACT;","names":[]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@noy-db/in-react",
3
+ "version": "0.1.0-pre.3",
4
+ "description": "React hooks for noy-db — useNoydb, useVault, useCollection, useQuery, useSync. Uses useSyncExternalStore for reactive subscriptions. Zero deps beyond React.",
5
+ "license": "MIT",
6
+ "author": "vLannaAi <vicio@lanna.ai>",
7
+ "homepage": "https://github.com/vLannaAi/noy-db/tree/main/packages/in-react#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vLannaAi/noy-db.git",
11
+ "directory": "packages/in-react"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/vLannaAi/noy-db/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
28
+ }
29
+ },
30
+ "main": "./dist/index.cjs",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "files": [
34
+ "dist",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "^18.0.0 || ^19.0.0",
43
+ "@noy-db/hub": "0.1.0-pre.3"
44
+ },
45
+ "devDependencies": {
46
+ "@testing-library/react": "^16.0.0",
47
+ "@types/react": "^18.0.0",
48
+ "happy-dom": "^15.11.7",
49
+ "react": "^18.3.0",
50
+ "react-dom": "^18.3.0",
51
+ "@noy-db/hub": "0.1.0-pre.3"
52
+ },
53
+ "keywords": [
54
+ "noy-db",
55
+ "in-react",
56
+ "react",
57
+ "hooks",
58
+ "zero-knowledge"
59
+ ],
60
+ "publishConfig": {
61
+ "access": "public",
62
+ "tag": "latest"
63
+ },
64
+ "scripts": {
65
+ "build": "tsup",
66
+ "test": "vitest run",
67
+ "lint": "eslint src/",
68
+ "typecheck": "tsc --noEmit"
69
+ }
70
+ }