gjendje 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.ts"],"names":["state","useSyncExternalStore","useCallback","useReactState","useEffect","collection"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useCallback, useEffect, useState as useReactState, useSyncExternalStore } from 'react'\nimport type { BucketOptions, CollectionInstance, StateInstance, StateOptions } from '../index.js'\nimport { collection, state } from '../index.js'\n\n// ---------------------------------------------------------------------------\n// useStore — primary hook\n//\n// Returns [value, setter] just like React's useState, but scoped and\n// persistent. The same key + scope returns the same instance everywhere —\n// safe to call from multiple components simultaneously.\n//\n// For bucket scope, the hook starts with the default value and updates\n// automatically once the bucket has initialized.\n//\n// ```tsx\n// const [theme, setTheme] = useStore('theme', {\n// default: 'light',\n// scope: 'local',\n// })\n//\n// // With storage bucket — isolated, expirable storage\n// const [prefs, setPrefs] = useStore('prefs', {\n// default: { theme: 'light' },\n// scope: 'bucket',\n// bucket: { name: 'user-prefs', expires: '30d', persisted: true },\n// })\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useStore<T>(\n\tkey: string,\n\toptions: StateOptions<T>,\n): [T, (value: T | ((prev: T) => T)) => void] {\n\tconst instance = state(key, options)\n\n\tconst value = useSyncExternalStore(\n\t\tuseCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),\n\t\t() => instance.get(),\n\t\t() => options.default,\n\t)\n\n\tconst set = useCallback(\n\t\t(valueOrUpdater: T | ((prev: T) => T)) => {\n\t\t\tinstance.set(valueOrUpdater)\n\t\t},\n\t\t[instance],\n\t)\n\n\treturn [value, set]\n}\n\n// ---------------------------------------------------------------------------\n// useStateInstance — access the full instance\n//\n// Use when you need peek(), watch(), reset(), ready, or want to pass\n// the instance to child components or utility functions.\n// ---------------------------------------------------------------------------\n\nexport function useStateInstance<T>(key: string, options: StateOptions<T>): StateInstance<T> {\n\tconst instance = state(key, options)\n\n\tuseSyncExternalStore(\n\t\tuseCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),\n\t\t() => instance.get(),\n\t\t() => options.default,\n\t)\n\n\treturn instance\n}\n\n// ---------------------------------------------------------------------------\n// useSharedState — consume a module-level instance\n//\n// The recommended pattern for app-wide shared state. Define once at\n// module level, consume anywhere.\n//\n// ```ts\n// // state.ts\n// export const themeState = state('theme', {\n// default: 'light',\n// scope: 'local',\n// })\n//\n// // ThemeToggle.tsx\n// const [theme, setTheme] = useSharedState(themeState)\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useSharedState<T>(\n\tinstance: StateInstance<T>,\n): [T, (value: T | ((prev: T) => T)) => void] {\n\tconst value = useSyncExternalStore(\n\t\tuseCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),\n\t\t() => instance.get(),\n\t\t() => instance.get(),\n\t)\n\n\tconst set = useCallback(\n\t\t(valueOrUpdater: T | ((prev: T) => T)) => {\n\t\t\tinstance.set(valueOrUpdater)\n\t\t},\n\t\t[instance],\n\t)\n\n\treturn [value, set]\n}\n\n// ---------------------------------------------------------------------------\n// useWatch — subscribe to a specific key within object state\n//\n// Only triggers a re-render when the watched key changes, not on every\n// update to the parent object.\n//\n// ```tsx\n// const theme = useWatch(prefsState, 'theme')\n// // re-renders only when prefs.theme changes\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useWatch<T extends object, K extends keyof T>(\n\tinstance: StateInstance<T>,\n\tkey: K,\n): T[K] {\n\tconst getSnapshot = useCallback(() => instance.get()[key], [instance, key])\n\n\treturn useSyncExternalStore(\n\t\tuseCallback(\n\t\t\t(onStoreChange) => {\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: conditional keyof type doesn't narrow here\n\t\t\t\treturn (instance.watch as any)(key, onStoreChange)\n\t\t\t},\n\t\t\t[instance, key],\n\t\t),\n\t\tgetSnapshot,\n\t)\n}\n\n// ---------------------------------------------------------------------------\n// useReady — resolves to true once an async scope (e.g. bucket) is ready\n//\n// Useful for showing loading states while a bucket initializes.\n//\n// ```tsx\n// const isReady = useReady(prefsState)\n//\n// if (!isReady) return <Skeleton />\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useReady(instance: StateInstance<unknown>): boolean {\n\tconst [isReady, setIsReady] = useReactState(false)\n\n\tuseEffect(() => {\n\t\tlet cancelled = false\n\n\t\tinstance.ready\n\t\t\t.then(() => {\n\t\t\t\tif (!cancelled) setIsReady(true)\n\t\t\t})\n\t\t\t.catch(() => {})\n\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [instance])\n\n\treturn isReady\n}\n\n// ---------------------------------------------------------------------------\n// useSettled — resolves to true once the most recent write has persisted\n//\n// ```tsx\n// const isSettled = useSettled(prefsState)\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useSettled(instance: StateInstance<unknown>): boolean {\n\tconst [isSettled, setIsSettled] = useReactState(false)\n\n\tuseEffect(() => {\n\t\tlet cancelled = false\n\n\t\tsetIsSettled(false)\n\n\t\tinstance.settled\n\t\t\t.then(() => {\n\t\t\t\tif (!cancelled) setIsSettled(true)\n\t\t\t})\n\t\t\t.catch(() => {})\n\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [instance])\n\n\treturn isSettled\n}\n\n// ---------------------------------------------------------------------------\n// useHydrated — resolves to true once SSR hydration is complete\n//\n// ```tsx\n// const isHydrated = useHydrated(themeState)\n//\n// if (!isHydrated) return <Skeleton />\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useHydrated(instance: StateInstance<unknown>): boolean {\n\tconst [isHydrated, setIsHydrated] = useReactState(false)\n\n\tuseEffect(() => {\n\t\tlet cancelled = false\n\n\t\tinstance.hydrated\n\t\t\t.then(() => {\n\t\t\t\tif (!cancelled) setIsHydrated(true)\n\t\t\t})\n\t\t\t.catch(() => {})\n\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [instance])\n\n\treturn isHydrated\n}\n\n// ---------------------------------------------------------------------------\n// useBucket — open a named storage bucket and get state bound to it\n//\n// A convenience hook that combines useStore with bucket scope, making\n// the bucket options co-located with the component that needs them.\n//\n// Returns [value, setter, isReady] — isReady is false until the bucket\n// has initialized and real stored values are available.\n//\n// ```tsx\n// const [prefs, setPrefs, isReady] = useBucket('prefs', {\n// default: { theme: 'light', fontSize: 14 },\n// bucket: { name: 'user-prefs', expires: '30d', persisted: true },\n// })\n//\n// if (!isReady) return <Skeleton />\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useBucket<T>(\n\tkey: string,\n\toptions: Omit<StateOptions<T>, 'scope'> & { bucket: BucketOptions },\n): [T, (value: T | ((prev: T) => T)) => void, boolean] {\n\tconst mergedOptions = { ...options, scope: 'bucket' as const }\n\n\t// state() returns the same instance for the same key+scope via the registry\n\tconst instance = state(key, mergedOptions)\n\n\tconst [value, set] = useStore(key, mergedOptions)\n\n\tconst isReady = useReady(instance as StateInstance<unknown>)\n\n\treturn [value, set, isReady]\n}\n\n// ---------------------------------------------------------------------------\n// useCollection — reactive array state with mutation methods\n//\n// Returns the full CollectionInstance so add, remove, update etc. are\n// all directly available. Re-renders when the array changes.\n//\n// ```tsx\n// const todos = useCollection('todos', {\n// default: [] as Todo[],\n// scope: 'local',\n// })\n//\n// todos.add({ id: '1', text: 'hello', done: false })\n// todos.remove((t) => t.done)\n// todos.get() // Todo[]\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useCollection<T>(key: string, options: StateOptions<T[]>): CollectionInstance<T> {\n\tconst col = collection(key, options)\n\n\tuseSyncExternalStore(\n\t\tuseCallback((onStoreChange) => col.subscribe(onStoreChange), [col]),\n\t\t() => col.get(),\n\t\t() => options.default,\n\t)\n\n\treturn col\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport type {\n\tBucketOptions,\n\tReadonlyInstance,\n\tScope,\n\tStateInstance,\n\tStateOptions,\n} from '../index.js'\nexport { batch, collection, computed, effect, state, withServerSession } from '../index.js'\n"]}
@@ -0,0 +1,16 @@
1
+ import { f as StateOptions, a as BucketOptions, C as CollectionInstance, e as StateInstance } from '../factory-CIj-6WlO.cjs';
2
+ export { R as ReadonlyInstance, S as Scope, g as batch, h as collection, i as computed, j as effect, s as state, w as withServerSession } from '../factory-CIj-6WlO.cjs';
3
+
4
+ declare function useStore<T>(key: string, options: StateOptions<T>): [T, (value: T | ((prev: T) => T)) => void];
5
+ declare function useStateInstance<T>(key: string, options: StateOptions<T>): StateInstance<T>;
6
+ declare function useSharedState<T>(instance: StateInstance<T>): [T, (value: T | ((prev: T) => T)) => void];
7
+ declare function useWatch<T extends object, K extends keyof T>(instance: StateInstance<T>, key: K): T[K];
8
+ declare function useReady(instance: StateInstance<unknown>): boolean;
9
+ declare function useSettled(instance: StateInstance<unknown>): boolean;
10
+ declare function useHydrated(instance: StateInstance<unknown>): boolean;
11
+ declare function useBucket<T>(key: string, options: Omit<StateOptions<T>, 'scope'> & {
12
+ bucket: BucketOptions;
13
+ }): [T, (value: T | ((prev: T) => T)) => void, boolean];
14
+ declare function useCollection<T>(key: string, options: StateOptions<T[]>): CollectionInstance<T>;
15
+
16
+ export { BucketOptions, StateInstance, StateOptions, useBucket, useCollection, useHydrated, useReady, useSettled, useSharedState, useStateInstance, useStore, useWatch };
@@ -0,0 +1,16 @@
1
+ import { f as StateOptions, a as BucketOptions, C as CollectionInstance, e as StateInstance } from '../factory-CIj-6WlO.js';
2
+ export { R as ReadonlyInstance, S as Scope, g as batch, h as collection, i as computed, j as effect, s as state, w as withServerSession } from '../factory-CIj-6WlO.js';
3
+
4
+ declare function useStore<T>(key: string, options: StateOptions<T>): [T, (value: T | ((prev: T) => T)) => void];
5
+ declare function useStateInstance<T>(key: string, options: StateOptions<T>): StateInstance<T>;
6
+ declare function useSharedState<T>(instance: StateInstance<T>): [T, (value: T | ((prev: T) => T)) => void];
7
+ declare function useWatch<T extends object, K extends keyof T>(instance: StateInstance<T>, key: K): T[K];
8
+ declare function useReady(instance: StateInstance<unknown>): boolean;
9
+ declare function useSettled(instance: StateInstance<unknown>): boolean;
10
+ declare function useHydrated(instance: StateInstance<unknown>): boolean;
11
+ declare function useBucket<T>(key: string, options: Omit<StateOptions<T>, 'scope'> & {
12
+ bucket: BucketOptions;
13
+ }): [T, (value: T | ((prev: T) => T)) => void, boolean];
14
+ declare function useCollection<T>(key: string, options: StateOptions<T[]>): CollectionInstance<T>;
15
+
16
+ export { BucketOptions, StateInstance, StateOptions, useBucket, useCollection, useHydrated, useReady, useSettled, useSharedState, useStateInstance, useStore, useWatch };
@@ -0,0 +1,117 @@
1
+ import { state, collection } from '../chunk-347V4L7H.js';
2
+ export { batch, collection, computed, effect, state, withServerSession } from '../chunk-347V4L7H.js';
3
+ import { useSyncExternalStore, useCallback, useState, useEffect } from 'react';
4
+
5
+ function useStore(key, options) {
6
+ const instance = state(key, options);
7
+ const value = useSyncExternalStore(
8
+ useCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),
9
+ () => instance.get(),
10
+ () => options.default
11
+ );
12
+ const set = useCallback(
13
+ (valueOrUpdater) => {
14
+ instance.set(valueOrUpdater);
15
+ },
16
+ [instance]
17
+ );
18
+ return [value, set];
19
+ }
20
+ function useStateInstance(key, options) {
21
+ const instance = state(key, options);
22
+ useSyncExternalStore(
23
+ useCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),
24
+ () => instance.get(),
25
+ () => options.default
26
+ );
27
+ return instance;
28
+ }
29
+ function useSharedState(instance) {
30
+ const value = useSyncExternalStore(
31
+ useCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),
32
+ () => instance.get(),
33
+ () => instance.get()
34
+ );
35
+ const set = useCallback(
36
+ (valueOrUpdater) => {
37
+ instance.set(valueOrUpdater);
38
+ },
39
+ [instance]
40
+ );
41
+ return [value, set];
42
+ }
43
+ function useWatch(instance, key) {
44
+ const getSnapshot = useCallback(() => instance.get()[key], [instance, key]);
45
+ return useSyncExternalStore(
46
+ useCallback(
47
+ (onStoreChange) => {
48
+ return instance.watch(key, onStoreChange);
49
+ },
50
+ [instance, key]
51
+ ),
52
+ getSnapshot
53
+ );
54
+ }
55
+ function useReady(instance) {
56
+ const [isReady, setIsReady] = useState(false);
57
+ useEffect(() => {
58
+ let cancelled = false;
59
+ instance.ready.then(() => {
60
+ if (!cancelled) setIsReady(true);
61
+ }).catch(() => {
62
+ });
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }, [instance]);
67
+ return isReady;
68
+ }
69
+ function useSettled(instance) {
70
+ const [isSettled, setIsSettled] = useState(false);
71
+ useEffect(() => {
72
+ let cancelled = false;
73
+ setIsSettled(false);
74
+ instance.settled.then(() => {
75
+ if (!cancelled) setIsSettled(true);
76
+ }).catch(() => {
77
+ });
78
+ return () => {
79
+ cancelled = true;
80
+ };
81
+ }, [instance]);
82
+ return isSettled;
83
+ }
84
+ function useHydrated(instance) {
85
+ const [isHydrated, setIsHydrated] = useState(false);
86
+ useEffect(() => {
87
+ let cancelled = false;
88
+ instance.hydrated.then(() => {
89
+ if (!cancelled) setIsHydrated(true);
90
+ }).catch(() => {
91
+ });
92
+ return () => {
93
+ cancelled = true;
94
+ };
95
+ }, [instance]);
96
+ return isHydrated;
97
+ }
98
+ function useBucket(key, options) {
99
+ const mergedOptions = { ...options, scope: "bucket" };
100
+ const instance = state(key, mergedOptions);
101
+ const [value, set] = useStore(key, mergedOptions);
102
+ const isReady = useReady(instance);
103
+ return [value, set, isReady];
104
+ }
105
+ function useCollection(key, options) {
106
+ const col = collection(key, options);
107
+ useSyncExternalStore(
108
+ useCallback((onStoreChange) => col.subscribe(onStoreChange), [col]),
109
+ () => col.get(),
110
+ () => options.default
111
+ );
112
+ return col;
113
+ }
114
+
115
+ export { useBucket, useCollection, useHydrated, useReady, useSettled, useSharedState, useStateInstance, useStore, useWatch };
116
+ //# sourceMappingURL=index.js.map
117
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.ts"],"names":["useReactState"],"mappings":"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","file":"index.js","sourcesContent":["import { useCallback, useEffect, useState as useReactState, useSyncExternalStore } from 'react'\nimport type { BucketOptions, CollectionInstance, StateInstance, StateOptions } from '../index.js'\nimport { collection, state } from '../index.js'\n\n// ---------------------------------------------------------------------------\n// useStore — primary hook\n//\n// Returns [value, setter] just like React's useState, but scoped and\n// persistent. The same key + scope returns the same instance everywhere —\n// safe to call from multiple components simultaneously.\n//\n// For bucket scope, the hook starts with the default value and updates\n// automatically once the bucket has initialized.\n//\n// ```tsx\n// const [theme, setTheme] = useStore('theme', {\n// default: 'light',\n// scope: 'local',\n// })\n//\n// // With storage bucket — isolated, expirable storage\n// const [prefs, setPrefs] = useStore('prefs', {\n// default: { theme: 'light' },\n// scope: 'bucket',\n// bucket: { name: 'user-prefs', expires: '30d', persisted: true },\n// })\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useStore<T>(\n\tkey: string,\n\toptions: StateOptions<T>,\n): [T, (value: T | ((prev: T) => T)) => void] {\n\tconst instance = state(key, options)\n\n\tconst value = useSyncExternalStore(\n\t\tuseCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),\n\t\t() => instance.get(),\n\t\t() => options.default,\n\t)\n\n\tconst set = useCallback(\n\t\t(valueOrUpdater: T | ((prev: T) => T)) => {\n\t\t\tinstance.set(valueOrUpdater)\n\t\t},\n\t\t[instance],\n\t)\n\n\treturn [value, set]\n}\n\n// ---------------------------------------------------------------------------\n// useStateInstance — access the full instance\n//\n// Use when you need peek(), watch(), reset(), ready, or want to pass\n// the instance to child components or utility functions.\n// ---------------------------------------------------------------------------\n\nexport function useStateInstance<T>(key: string, options: StateOptions<T>): StateInstance<T> {\n\tconst instance = state(key, options)\n\n\tuseSyncExternalStore(\n\t\tuseCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),\n\t\t() => instance.get(),\n\t\t() => options.default,\n\t)\n\n\treturn instance\n}\n\n// ---------------------------------------------------------------------------\n// useSharedState — consume a module-level instance\n//\n// The recommended pattern for app-wide shared state. Define once at\n// module level, consume anywhere.\n//\n// ```ts\n// // state.ts\n// export const themeState = state('theme', {\n// default: 'light',\n// scope: 'local',\n// })\n//\n// // ThemeToggle.tsx\n// const [theme, setTheme] = useSharedState(themeState)\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useSharedState<T>(\n\tinstance: StateInstance<T>,\n): [T, (value: T | ((prev: T) => T)) => void] {\n\tconst value = useSyncExternalStore(\n\t\tuseCallback((onStoreChange) => instance.subscribe(onStoreChange), [instance]),\n\t\t() => instance.get(),\n\t\t() => instance.get(),\n\t)\n\n\tconst set = useCallback(\n\t\t(valueOrUpdater: T | ((prev: T) => T)) => {\n\t\t\tinstance.set(valueOrUpdater)\n\t\t},\n\t\t[instance],\n\t)\n\n\treturn [value, set]\n}\n\n// ---------------------------------------------------------------------------\n// useWatch — subscribe to a specific key within object state\n//\n// Only triggers a re-render when the watched key changes, not on every\n// update to the parent object.\n//\n// ```tsx\n// const theme = useWatch(prefsState, 'theme')\n// // re-renders only when prefs.theme changes\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useWatch<T extends object, K extends keyof T>(\n\tinstance: StateInstance<T>,\n\tkey: K,\n): T[K] {\n\tconst getSnapshot = useCallback(() => instance.get()[key], [instance, key])\n\n\treturn useSyncExternalStore(\n\t\tuseCallback(\n\t\t\t(onStoreChange) => {\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: conditional keyof type doesn't narrow here\n\t\t\t\treturn (instance.watch as any)(key, onStoreChange)\n\t\t\t},\n\t\t\t[instance, key],\n\t\t),\n\t\tgetSnapshot,\n\t)\n}\n\n// ---------------------------------------------------------------------------\n// useReady — resolves to true once an async scope (e.g. bucket) is ready\n//\n// Useful for showing loading states while a bucket initializes.\n//\n// ```tsx\n// const isReady = useReady(prefsState)\n//\n// if (!isReady) return <Skeleton />\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useReady(instance: StateInstance<unknown>): boolean {\n\tconst [isReady, setIsReady] = useReactState(false)\n\n\tuseEffect(() => {\n\t\tlet cancelled = false\n\n\t\tinstance.ready\n\t\t\t.then(() => {\n\t\t\t\tif (!cancelled) setIsReady(true)\n\t\t\t})\n\t\t\t.catch(() => {})\n\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [instance])\n\n\treturn isReady\n}\n\n// ---------------------------------------------------------------------------\n// useSettled — resolves to true once the most recent write has persisted\n//\n// ```tsx\n// const isSettled = useSettled(prefsState)\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useSettled(instance: StateInstance<unknown>): boolean {\n\tconst [isSettled, setIsSettled] = useReactState(false)\n\n\tuseEffect(() => {\n\t\tlet cancelled = false\n\n\t\tsetIsSettled(false)\n\n\t\tinstance.settled\n\t\t\t.then(() => {\n\t\t\t\tif (!cancelled) setIsSettled(true)\n\t\t\t})\n\t\t\t.catch(() => {})\n\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [instance])\n\n\treturn isSettled\n}\n\n// ---------------------------------------------------------------------------\n// useHydrated — resolves to true once SSR hydration is complete\n//\n// ```tsx\n// const isHydrated = useHydrated(themeState)\n//\n// if (!isHydrated) return <Skeleton />\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useHydrated(instance: StateInstance<unknown>): boolean {\n\tconst [isHydrated, setIsHydrated] = useReactState(false)\n\n\tuseEffect(() => {\n\t\tlet cancelled = false\n\n\t\tinstance.hydrated\n\t\t\t.then(() => {\n\t\t\t\tif (!cancelled) setIsHydrated(true)\n\t\t\t})\n\t\t\t.catch(() => {})\n\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t}\n\t}, [instance])\n\n\treturn isHydrated\n}\n\n// ---------------------------------------------------------------------------\n// useBucket — open a named storage bucket and get state bound to it\n//\n// A convenience hook that combines useStore with bucket scope, making\n// the bucket options co-located with the component that needs them.\n//\n// Returns [value, setter, isReady] — isReady is false until the bucket\n// has initialized and real stored values are available.\n//\n// ```tsx\n// const [prefs, setPrefs, isReady] = useBucket('prefs', {\n// default: { theme: 'light', fontSize: 14 },\n// bucket: { name: 'user-prefs', expires: '30d', persisted: true },\n// })\n//\n// if (!isReady) return <Skeleton />\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useBucket<T>(\n\tkey: string,\n\toptions: Omit<StateOptions<T>, 'scope'> & { bucket: BucketOptions },\n): [T, (value: T | ((prev: T) => T)) => void, boolean] {\n\tconst mergedOptions = { ...options, scope: 'bucket' as const }\n\n\t// state() returns the same instance for the same key+scope via the registry\n\tconst instance = state(key, mergedOptions)\n\n\tconst [value, set] = useStore(key, mergedOptions)\n\n\tconst isReady = useReady(instance as StateInstance<unknown>)\n\n\treturn [value, set, isReady]\n}\n\n// ---------------------------------------------------------------------------\n// useCollection — reactive array state with mutation methods\n//\n// Returns the full CollectionInstance so add, remove, update etc. are\n// all directly available. Re-renders when the array changes.\n//\n// ```tsx\n// const todos = useCollection('todos', {\n// default: [] as Todo[],\n// scope: 'local',\n// })\n//\n// todos.add({ id: '1', text: 'hello', done: false })\n// todos.remove((t) => t.done)\n// todos.get() // Todo[]\n// ```\n// ---------------------------------------------------------------------------\n\nexport function useCollection<T>(key: string, options: StateOptions<T[]>): CollectionInstance<T> {\n\tconst col = collection(key, options)\n\n\tuseSyncExternalStore(\n\t\tuseCallback((onStoreChange) => col.subscribe(onStoreChange), [col]),\n\t\t() => col.get(),\n\t\t() => options.default,\n\t)\n\n\treturn col\n}\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport type {\n\tBucketOptions,\n\tReadonlyInstance,\n\tScope,\n\tStateInstance,\n\tStateOptions,\n} from '../index.js'\nexport { batch, collection, computed, effect, state, withServerSession } from '../index.js'\n"]}
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "gjendje",
3
+ "version": "0.1.0",
4
+ "description": "gjendje brings state management to multiple storage backends through one consistent API",
5
+ "keywords": [
6
+ "state",
7
+ "storage",
8
+ "reactive",
9
+ "persistence",
10
+ "typescript",
11
+ "localStorage",
12
+ "storageBuckets",
13
+ "react"
14
+ ],
15
+ "author": "Charlie Beckstrand",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/charliebeckstrand/gjendje.git"
20
+ },
21
+ "homepage": "https://github.com/charliebeckstrand/gjendje#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/charliebeckstrand/gjendje/issues"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "packageManager": "pnpm@9.0.0",
29
+ "type": "module",
30
+ "exports": {
31
+ ".": {
32
+ "import": {
33
+ "types": "./dist/index.d.ts",
34
+ "default": "./dist/index.js"
35
+ },
36
+ "require": {
37
+ "types": "./dist/index.d.cts",
38
+ "default": "./dist/index.cjs"
39
+ }
40
+ },
41
+ "./react": {
42
+ "import": {
43
+ "types": "./dist/react/index.d.ts",
44
+ "default": "./dist/react/index.js"
45
+ },
46
+ "require": {
47
+ "types": "./dist/react/index.d.cts",
48
+ "default": "./dist/react/index.cjs"
49
+ }
50
+ }
51
+ },
52
+ "main": "./dist/index.cjs",
53
+ "module": "./dist/index.js",
54
+ "types": "./dist/index.d.ts",
55
+ "files": [
56
+ "dist",
57
+ "README.md"
58
+ ],
59
+ "scripts": {
60
+ "build": "tsup",
61
+ "dev": "tsup --watch",
62
+ "format": "biome format --write src/ __tests__/",
63
+ "lint": "biome check src/ __tests__/",
64
+ "lint:check": "biome ci src/ __tests__/",
65
+ "lint:fix": "biome check src/ __tests__/ --fix",
66
+ "prepare": "lefthook install",
67
+ "prepublishOnly": "pnpm typecheck && pnpm lint:check && pnpm test && pnpm build && pnpm publint",
68
+ "release": "pnpm build && pnpm changeset publish",
69
+ "size": "size-limit",
70
+ "test": "vitest run",
71
+ "test:coverage": "vitest run --coverage",
72
+ "test:watch": "vitest",
73
+ "typecheck": "tsc --project tsconfig.test.json --skipLibCheck"
74
+ },
75
+ "peerDependencies": {
76
+ "react": "^18.0.0 || ^19.0.0"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "react": {
80
+ "optional": true
81
+ }
82
+ },
83
+ "devDependencies": {
84
+ "@biomejs/biome": "^2.4.6",
85
+ "@changesets/cli": "^2.27.0",
86
+ "@size-limit/preset-small-lib": "^11.0.0",
87
+ "@testing-library/react": "^16.0.0",
88
+ "@types/node": "^20.0.0",
89
+ "@types/react": "^19.0.0",
90
+ "@types/react-dom": "^19.0.0",
91
+ "@vitest/coverage-v8": "^4.0.0",
92
+ "happy-dom": "^14.0.0",
93
+ "publint": "^0.3.0",
94
+ "react": "^19.0.0",
95
+ "react-dom": "^19.0.0",
96
+ "size-limit": "^11.0.0",
97
+ "tsup": "^8.0.0",
98
+ "typescript": "^5.4.0",
99
+ "vite": "^6.0.0",
100
+ "vitest": "^4.0.0",
101
+ "lefthook": "^1.6.0"
102
+ }
103
+ }