@bquery/bquery 1.1.2 → 1.2.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,229 @@
1
+ import { signal as D, computed as P, batch as m } from "./reactive.es.mjs";
2
+ const p = /* @__PURE__ */ new Map(), A = (e) => e !== null && typeof e == "object" && Object.getPrototypeOf(e) === Object.prototype, d = (e) => {
3
+ if (e === null || typeof e != "object")
4
+ return e;
5
+ if (Array.isArray(e))
6
+ return e.map(d);
7
+ if (e instanceof Date)
8
+ return new Date(e.getTime());
9
+ if (e instanceof Map)
10
+ return new Map(Array.from(e.entries()).map(([o, c]) => [o, d(c)]));
11
+ if (e instanceof Set)
12
+ return new Set(Array.from(e).map(d));
13
+ const n = {};
14
+ for (const o of Object.keys(e))
15
+ n[o] = d(e[o]);
16
+ return n;
17
+ }, v = (e, n) => {
18
+ if (e === n) return !0;
19
+ if (e === null || n === null || typeof e != "object" || typeof n != "object") return !1;
20
+ if (Array.isArray(e) && Array.isArray(n))
21
+ return e.length !== n.length ? !1 : e.every((i, u) => v(i, n[u]));
22
+ if (Array.isArray(e) !== Array.isArray(n)) return !1;
23
+ const o = Object.keys(e), c = Object.keys(n);
24
+ return o.length !== c.length ? !1 : o.every(
25
+ (i) => v(e[i], n[i])
26
+ );
27
+ }, V = (e, n, o) => {
28
+ const c = [];
29
+ for (const i of Object.keys(n)) {
30
+ const u = e[i], s = n[i];
31
+ o.get(i) === s && // Same reference as signal
32
+ A(u) && A(s) && !v(u, s) && c.push(i);
33
+ }
34
+ return c;
35
+ }, S = (() => {
36
+ try {
37
+ const e = globalThis.process;
38
+ return typeof e < "u" && e.env?.NODE_ENV !== "production";
39
+ } catch {
40
+ return !0;
41
+ }
42
+ })(), j = [], x = (e) => {
43
+ const { id: n, state: o, getters: c = {}, actions: i = {} } = e;
44
+ if (p.has(n))
45
+ return console.warn(`bQuery store: Store "${n}" already exists. Returning existing instance.`), p.get(n);
46
+ const u = o(), s = /* @__PURE__ */ new Map();
47
+ for (const t of Object.keys(u))
48
+ s.set(t, D(u[t]));
49
+ const _ = [], w = () => {
50
+ const t = O();
51
+ for (const r of _)
52
+ r(t);
53
+ typeof window < "u" && window.__BQUERY_DEVTOOLS__?.onStateChange && window.__BQUERY_DEVTOOLS__.onStateChange(n, t);
54
+ }, k = new Proxy({}, {
55
+ get: (t, r) => {
56
+ const a = r;
57
+ if (s.has(a))
58
+ return s.get(a).value;
59
+ },
60
+ ownKeys: () => Array.from(s.keys()),
61
+ getOwnPropertyDescriptor: (t, r) => {
62
+ if (s.has(r))
63
+ return { enumerable: !0, configurable: !0 };
64
+ },
65
+ has: (t, r) => s.has(r)
66
+ }), O = () => ({ ...k }), b = /* @__PURE__ */ new Map(), y = {};
67
+ for (const t of Object.keys(u))
68
+ Object.defineProperty(y, t, {
69
+ get: () => s.get(t).value,
70
+ set: (r) => {
71
+ s.get(t).value = r, w();
72
+ },
73
+ enumerable: !0,
74
+ configurable: !1
75
+ });
76
+ for (const t of Object.keys(c)) {
77
+ const r = c[t], a = P(() => {
78
+ const g = k, l = new Proxy({}, {
79
+ get: (f, h) => {
80
+ const E = h;
81
+ if (b.has(E))
82
+ return b.get(E).value;
83
+ }
84
+ });
85
+ return r(g, l);
86
+ });
87
+ b.set(t, a), Object.defineProperty(y, t, {
88
+ get: () => a.value,
89
+ enumerable: !0,
90
+ configurable: !1
91
+ });
92
+ }
93
+ for (const t of Object.keys(i)) {
94
+ const r = i[t];
95
+ y[t] = function(...a) {
96
+ const g = new Proxy(y, {
97
+ get: (l, f) => typeof f == "string" && s.has(f) ? s.get(f).value : l[f],
98
+ set: (l, f, h) => typeof f == "string" && s.has(f) ? (s.get(f).value = h, w(), !0) : !1
99
+ });
100
+ return r.apply(g, a);
101
+ };
102
+ }
103
+ Object.defineProperties(y, {
104
+ $id: {
105
+ value: n,
106
+ writable: !1,
107
+ enumerable: !1
108
+ },
109
+ $reset: {
110
+ value: () => {
111
+ const t = o();
112
+ m(() => {
113
+ for (const [r, a] of s)
114
+ a.value = t[r];
115
+ }), w();
116
+ },
117
+ writable: !1,
118
+ enumerable: !1
119
+ },
120
+ $subscribe: {
121
+ value: (t) => (_.push(t), () => {
122
+ const r = _.indexOf(t);
123
+ r > -1 && _.splice(r, 1);
124
+ }),
125
+ writable: !1,
126
+ enumerable: !1
127
+ },
128
+ $patch: {
129
+ value: (t) => {
130
+ m(() => {
131
+ if (typeof t == "function") {
132
+ const r = S ? d(O()) : null, a = S ? new Map(Array.from(s.entries()).map(([l, f]) => [l, f.value])) : null, g = O();
133
+ if (t(g), S && r && a) {
134
+ const l = V(r, g, a);
135
+ l.length > 0 && console.warn(
136
+ `[bQuery store "${n}"] Nested mutation detected in $patch() for keys: ${l.map(String).join(", ")}.
137
+ Nested object mutations do not trigger reactive updates because the store uses shallow reactivity.
138
+ To fix this, either:
139
+ 1. Replace the entire object: state.user = { ...state.user, name: "New" }
140
+ 2. Use $patchDeep() for automatic deep cloning
141
+ See: https://bquery.dev/guide/store#deep-reactivity`
142
+ );
143
+ }
144
+ for (const [l, f] of Object.entries(g))
145
+ s.has(l) && (s.get(l).value = f);
146
+ } else
147
+ for (const [r, a] of Object.entries(t))
148
+ s.has(r) && (s.get(r).value = a);
149
+ }), w();
150
+ },
151
+ writable: !1,
152
+ enumerable: !1
153
+ },
154
+ $patchDeep: {
155
+ value: (t) => {
156
+ m(() => {
157
+ if (typeof t == "function") {
158
+ const r = d(O());
159
+ t(r);
160
+ for (const [a, g] of Object.entries(r))
161
+ s.has(a) && (s.get(a).value = g);
162
+ } else
163
+ for (const [r, a] of Object.entries(t))
164
+ s.has(r) && (s.get(r).value = d(a));
165
+ }), w();
166
+ },
167
+ writable: !1,
168
+ enumerable: !1
169
+ },
170
+ $state: {
171
+ get: () => O(),
172
+ enumerable: !1
173
+ }
174
+ }), p.set(n, y);
175
+ for (const t of j) {
176
+ const r = t({ store: y, options: e });
177
+ r && Object.assign(y, r);
178
+ }
179
+ return typeof window < "u" && (window.__BQUERY_DEVTOOLS__ || (window.__BQUERY_DEVTOOLS__ = { stores: /* @__PURE__ */ new Map() }), window.__BQUERY_DEVTOOLS__.stores.set(n, y), window.__BQUERY_DEVTOOLS__.onStoreCreated?.(n, y)), y;
180
+ }, B = (e) => p.get(e), R = () => Array.from(p.keys()), T = (e) => {
181
+ p.delete(e), typeof window < "u" && window.__BQUERY_DEVTOOLS__ && window.__BQUERY_DEVTOOLS__.stores.delete(e);
182
+ }, Q = (e) => {
183
+ j.push(e);
184
+ }, U = (e, n) => {
185
+ const o = n ?? `bquery-store-${e.id}`, c = e.state;
186
+ e.state = () => {
187
+ const u = c();
188
+ if (typeof window < "u")
189
+ try {
190
+ const s = localStorage.getItem(o);
191
+ if (s)
192
+ return { ...u, ...JSON.parse(s) };
193
+ } catch {
194
+ }
195
+ return u;
196
+ };
197
+ const i = x(e);
198
+ return i.$subscribe((u) => {
199
+ if (typeof window < "u")
200
+ try {
201
+ localStorage.setItem(o, JSON.stringify(u));
202
+ } catch {
203
+ }
204
+ }), i;
205
+ }, L = (e, n) => {
206
+ const o = {};
207
+ for (const c of n)
208
+ Object.defineProperty(o, c, {
209
+ get: () => e[c],
210
+ enumerable: !0
211
+ });
212
+ return o;
213
+ }, M = (e, n) => {
214
+ const o = {};
215
+ for (const c of n)
216
+ o[c] = (...i) => e[c](...i);
217
+ return o;
218
+ };
219
+ export {
220
+ U as createPersistedStore,
221
+ x as createStore,
222
+ T as destroyStore,
223
+ B as getStore,
224
+ R as listStores,
225
+ M as mapActions,
226
+ L as mapState,
227
+ Q as registerPlugin
228
+ };
229
+ //# sourceMappingURL=store.es.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.es.mjs","sources":["../src/store/index.ts"],"sourcesContent":["/**\r\n * Minimal state management built on signals.\r\n *\r\n * This module provides a lightweight store pattern inspired by Pinia/Vuex\r\n * but built entirely on bQuery's reactive primitives. Features include:\r\n * - Signal-based reactive state\r\n * - Computed getters\r\n * - Actions with async support\r\n * - Devtools hooks for debugging\r\n * - Plugin system for extensions\r\n *\r\n * @module bquery/store\r\n *\r\n * @example\r\n * ```ts\r\n * import { createStore } from 'bquery/store';\r\n * import { effect } from 'bquery/reactive';\r\n *\r\n * const counterStore = createStore({\r\n * id: 'counter',\r\n * state: () => ({ count: 0 }),\r\n * getters: {\r\n * doubled: (state) => state.count * 2,\r\n * isPositive: (state) => state.count > 0,\r\n * },\r\n * actions: {\r\n * increment() {\r\n * this.count++;\r\n * },\r\n * async fetchAndSet(url: string) {\r\n * const response = await fetch(url);\r\n * const data = await response.json();\r\n * this.count = data.count;\r\n * },\r\n * },\r\n * });\r\n *\r\n * effect(() => {\r\n * console.log('Count:', counterStore.count);\r\n * console.log('Doubled:', counterStore.doubled);\r\n * });\r\n *\r\n * counterStore.increment();\r\n * ```\r\n */\r\n\r\nimport { batch, computed, signal, type ReadonlySignal, type Signal } from '../reactive/index';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\n/**\r\n * Store state factory function.\r\n */\r\nexport type StateFactory<S> = () => S;\r\n\r\n/**\r\n * Getter definition - derives computed values from state.\r\n */\r\nexport type Getters<S, G> = {\r\n [K in keyof G]: (state: S, getters: G) => G[K];\r\n};\r\n\r\n/**\r\n * Action definition - methods that can modify state.\r\n */\r\nexport type Actions<S, A> = {\r\n [K in keyof A]: A[K] extends (...args: infer P) => infer R\r\n ? (this: S & A, ...args: P) => R\r\n : never;\r\n};\r\n\r\n/**\r\n * Store definition for createStore.\r\n */\r\nexport type StoreDefinition<\r\n S extends Record<string, unknown> = Record<string, unknown>,\r\n G extends Record<string, unknown> = Record<string, unknown>,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n A extends Record<string, (...args: any[]) => any> = Record<string, never>,\r\n> = {\r\n /** Unique store identifier for devtools */\r\n id: string;\r\n /** State factory function */\r\n state: StateFactory<S>;\r\n /** Computed getters */\r\n getters?: Getters<S, G>;\r\n /** Action methods */\r\n actions?: A;\r\n};\r\n\r\n/**\r\n * The returned store instance with state, getters, and actions merged.\r\n */\r\nexport type Store<\r\n S extends Record<string, unknown>,\r\n G extends Record<string, unknown>,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n A extends Record<string, (...args: any[]) => any>,\r\n> = S &\r\n G &\r\n A & {\r\n /** Store identifier */\r\n $id: string;\r\n /** Reset state to initial values */\r\n $reset: () => void;\r\n /** Subscribe to state changes */\r\n $subscribe: (callback: (state: S) => void) => () => void;\r\n /** Patch multiple state properties at once (shallow) */\r\n $patch: (partial: Partial<S> | ((state: S) => void)) => void;\r\n /**\r\n * Patch with deep reactivity support.\r\n * Unlike $patch, this method deep-clones nested objects before mutation,\r\n * ensuring that all changes trigger reactive updates.\r\n */\r\n $patchDeep: (partial: Partial<S> | ((state: S) => void)) => void;\r\n /** Get raw state object (non-reactive snapshot) */\r\n $state: S;\r\n };\r\n\r\n/**\r\n * Plugin that can extend store functionality.\r\n */\r\nexport type StorePlugin<S = unknown> = (context: {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n store: Store<any, any, any>;\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n options: StoreDefinition<any, any, any>;\r\n}) => Partial<S> | void;\r\n\r\n// ============================================================================\r\n// Internal State\r\n// ============================================================================\r\n\r\n/** @internal Registry of all stores for devtools */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nconst storeRegistry = new Map<string, Store<any, any, any>>();\r\n\r\n// ============================================================================\r\n// Internal Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Check if a value is a plain object (not array, null, Date, etc.).\r\n * @internal\r\n */\r\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\r\n return (\r\n value !== null && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype\r\n );\r\n};\r\n\r\n/**\r\n * Deep clones an object. Used for deep reactivity support.\r\n * @internal\r\n */\r\nconst deepClone = <T>(obj: T): T => {\r\n if (obj === null || typeof obj !== 'object') {\r\n return obj;\r\n }\r\n\r\n if (Array.isArray(obj)) {\r\n return obj.map(deepClone) as T;\r\n }\r\n\r\n if (obj instanceof Date) {\r\n return new Date(obj.getTime()) as T;\r\n }\r\n\r\n if (obj instanceof Map) {\r\n return new Map(Array.from(obj.entries()).map(([k, v]) => [k, deepClone(v)])) as T;\r\n }\r\n\r\n if (obj instanceof Set) {\r\n return new Set(Array.from(obj).map(deepClone)) as T;\r\n }\r\n\r\n const cloned = {} as T;\r\n for (const key of Object.keys(obj)) {\r\n (cloned as Record<string, unknown>)[key] = deepClone((obj as Record<string, unknown>)[key]);\r\n }\r\n return cloned;\r\n};\r\n\r\n/**\r\n * Compares two values for deep equality.\r\n * @internal\r\n */\r\nconst deepEqual = (a: unknown, b: unknown): boolean => {\r\n if (a === b) return true;\r\n if (a === null || b === null) return false;\r\n if (typeof a !== 'object' || typeof b !== 'object') return false;\r\n\r\n if (Array.isArray(a) && Array.isArray(b)) {\r\n if (a.length !== b.length) return false;\r\n return a.every((item, i) => deepEqual(item, b[i]));\r\n }\r\n\r\n if (Array.isArray(a) !== Array.isArray(b)) return false;\r\n\r\n const keysA = Object.keys(a as object);\r\n const keysB = Object.keys(b as object);\r\n\r\n if (keysA.length !== keysB.length) return false;\r\n\r\n return keysA.every((key) =>\r\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])\r\n );\r\n};\r\n\r\n/**\r\n * Detects if nested objects were mutated but the reference stayed the same.\r\n * Returns the keys where nested mutations were detected.\r\n * @internal\r\n */\r\nconst detectNestedMutations = <S extends Record<string, unknown>>(\r\n before: S,\r\n after: S,\r\n signalValues: Map<keyof S, unknown>\r\n): Array<keyof S> => {\r\n const mutatedKeys: Array<keyof S> = [];\r\n\r\n for (const key of Object.keys(after) as Array<keyof S>) {\r\n const beforeValue = before[key];\r\n const afterValue = after[key];\r\n const signalValue = signalValues.get(key);\r\n\r\n // Check if it's the same reference but content changed\r\n if (\r\n signalValue === afterValue && // Same reference as signal\r\n isPlainObject(beforeValue) &&\r\n isPlainObject(afterValue) &&\r\n !deepEqual(beforeValue, afterValue)\r\n ) {\r\n mutatedKeys.push(key);\r\n }\r\n }\r\n\r\n return mutatedKeys;\r\n};\r\n\r\n/** @internal Flag to enable/disable development warnings */\r\nconst __DEV__ = (() => {\r\n try {\r\n // Check for Node.js environment\r\n const globalProcess = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process;\r\n return typeof globalProcess !== 'undefined' && globalProcess.env?.NODE_ENV !== 'production';\r\n } catch {\r\n return true; // Default to dev mode if detection fails\r\n }\r\n})();\r\n\r\n/** @internal Registered plugins */\r\nconst plugins: StorePlugin[] = [];\r\n\r\n/** @internal Devtools hook */\r\ndeclare global {\r\n interface Window {\r\n __BQUERY_DEVTOOLS__?: {\r\n stores: Map<string, unknown>;\r\n onStoreCreated?: (id: string, store: unknown) => void;\r\n onStateChange?: (id: string, state: unknown) => void;\r\n };\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Store Creation\r\n// ============================================================================\r\n\r\n/**\r\n * Creates a reactive store with state, getters, and actions.\r\n *\r\n * @template S - State type\r\n * @template G - Getters type\r\n * @template A - Actions type\r\n * @param definition - Store definition\r\n * @returns The reactive store instance\r\n *\r\n * @example\r\n * ```ts\r\n * import { createStore } from 'bquery/store';\r\n *\r\n * // Simple counter store\r\n * const useCounter = createStore({\r\n * id: 'counter',\r\n * state: () => ({ count: 0, step: 1 }),\r\n * getters: {\r\n * doubled: (state) => state.count * 2,\r\n * next: (state) => state.count + state.step,\r\n * },\r\n * actions: {\r\n * increment() {\r\n * this.count += this.step;\r\n * },\r\n * decrement() {\r\n * this.count -= this.step;\r\n * },\r\n * setStep(newStep: number) {\r\n * this.step = newStep;\r\n * },\r\n * async loadFromServer() {\r\n * const res = await fetch('/api/counter');\r\n * const data = await res.json();\r\n * this.count = data.count;\r\n * },\r\n * },\r\n * });\r\n *\r\n * // Use the store\r\n * useCounter.increment();\r\n * console.log(useCounter.count); // 1\r\n * console.log(useCounter.doubled); // 2\r\n * ```\r\n */\r\nexport const createStore = <\r\n S extends Record<string, unknown>,\r\n G extends Record<string, unknown> = Record<string, never>,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n A extends Record<string, (...args: any[]) => any> = Record<string, never>,\r\n>(\r\n definition: StoreDefinition<S, G, A>\r\n): Store<S, G, A> => {\r\n const { id, state: stateFactory, getters = {} as Getters<S, G>, actions = {} as A } = definition;\r\n\r\n // Check for duplicate store IDs\r\n if (storeRegistry.has(id)) {\r\n console.warn(`bQuery store: Store \"${id}\" already exists. Returning existing instance.`);\r\n return storeRegistry.get(id) as Store<S, G, A>;\r\n }\r\n\r\n // Create initial state\r\n const initialState = stateFactory();\r\n\r\n // Create signals for each state property\r\n const stateSignals = new Map<keyof S, Signal<unknown>>();\r\n for (const key of Object.keys(initialState) as Array<keyof S>) {\r\n stateSignals.set(key, signal(initialState[key]));\r\n }\r\n\r\n // Subscribers for $subscribe\r\n const subscribers: Array<(state: S) => void> = [];\r\n\r\n /**\r\n * Notifies subscribers of state changes.\r\n * @internal\r\n */\r\n const notifySubscribers = (): void => {\r\n const currentState = getCurrentState();\r\n for (const callback of subscribers) {\r\n callback(currentState);\r\n }\r\n\r\n // Notify devtools\r\n if (typeof window !== 'undefined' && window.__BQUERY_DEVTOOLS__?.onStateChange) {\r\n window.__BQUERY_DEVTOOLS__.onStateChange(id, currentState);\r\n }\r\n };\r\n\r\n /**\r\n * Cached state proxy that lazily reads signal values.\r\n * Uses a Proxy to avoid creating new objects on each access.\r\n *\r\n * **Note:** This returns a shallow snapshot of the state. Nested object\r\n * mutations will NOT trigger reactive updates. For nested reactivity,\r\n * replace the entire object or use signals for nested properties.\r\n *\r\n * @internal\r\n */\r\n const stateProxy = new Proxy({} as S, {\r\n get: (_, prop: string | symbol) => {\r\n const key = prop as keyof S;\r\n if (stateSignals.has(key)) {\r\n return stateSignals.get(key)!.value;\r\n }\r\n return undefined;\r\n },\r\n ownKeys: () => Array.from(stateSignals.keys()) as string[],\r\n getOwnPropertyDescriptor: (_, prop) => {\r\n if (stateSignals.has(prop as keyof S)) {\r\n return { enumerable: true, configurable: true };\r\n }\r\n return undefined;\r\n },\r\n has: (_, prop) => stateSignals.has(prop as keyof S),\r\n });\r\n\r\n /**\r\n * Gets the current state.\r\n *\r\n * For subscriber notifications (where a plain object snapshot is needed),\r\n * this creates a shallow copy. For internal reads, use stateProxy directly.\r\n *\r\n * **Note:** Returns a shallow snapshot. Nested object mutations will NOT\r\n * trigger reactive updates. This differs from frameworks like Pinia that\r\n * use deep reactivity. To update nested state, replace the entire object:\r\n *\r\n * @example\r\n * ```ts\r\n * // ❌ Won't trigger updates\r\n * store.user.name = 'New Name';\r\n *\r\n * // ✅ Will trigger updates\r\n * store.user = { ...store.user, name: 'New Name' };\r\n * ```\r\n *\r\n * @internal\r\n */\r\n const getCurrentState = (): S => ({ ...stateProxy });\r\n\r\n // Create computed getters\r\n const getterComputed = new Map<keyof G, ReadonlySignal<unknown>>();\r\n\r\n // Build the store proxy\r\n const store = {} as Store<S, G, A>;\r\n\r\n // Define state properties with getters/setters\r\n for (const key of Object.keys(initialState) as Array<keyof S>) {\r\n Object.defineProperty(store, key, {\r\n get: () => stateSignals.get(key)!.value,\r\n set: (value: unknown) => {\r\n stateSignals.get(key)!.value = value;\r\n notifySubscribers();\r\n },\r\n enumerable: true,\r\n configurable: false,\r\n });\r\n }\r\n\r\n // Define getters as computed properties\r\n for (const key of Object.keys(getters) as Array<keyof G>) {\r\n const getterFn = getters[key];\r\n\r\n // Create computed that reads from state signals via proxy (more efficient)\r\n const computedGetter = computed(() => {\r\n const state = stateProxy;\r\n // For getter dependencies, pass a proxy that reads from computed getters\r\n const getterProxy = new Proxy({} as G, {\r\n get: (_, prop: string | symbol) => {\r\n const propKey = prop as keyof G;\r\n if (getterComputed.has(propKey)) {\r\n return getterComputed.get(propKey)!.value;\r\n }\r\n return undefined;\r\n },\r\n });\r\n return getterFn(state, getterProxy);\r\n });\r\n\r\n getterComputed.set(key, computedGetter as unknown as ReadonlySignal<unknown>);\r\n\r\n Object.defineProperty(store, key, {\r\n get: () => computedGetter.value,\r\n enumerable: true,\r\n configurable: false,\r\n });\r\n }\r\n\r\n // Bind actions to the store context\r\n for (const key of Object.keys(actions) as Array<keyof A>) {\r\n const actionFn = actions[key];\r\n\r\n // Wrap action to enable 'this' binding\r\n (store as Record<string, unknown>)[key as string] = function (...args: unknown[]) {\r\n // Create a context that allows 'this.property' access\r\n const context = new Proxy(store, {\r\n get: (target, prop) => {\r\n if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {\r\n return stateSignals.get(prop as keyof S)!.value;\r\n }\r\n return (target as Record<string, unknown>)[prop as string];\r\n },\r\n set: (_target, prop, value) => {\r\n if (typeof prop === 'string' && stateSignals.has(prop as keyof S)) {\r\n stateSignals.get(prop as keyof S)!.value = value;\r\n notifySubscribers();\r\n return true;\r\n }\r\n return false;\r\n },\r\n });\r\n\r\n return actionFn.apply(context, args);\r\n };\r\n }\r\n\r\n // Add store utility methods\r\n Object.defineProperties(store, {\r\n $id: {\r\n value: id,\r\n writable: false,\r\n enumerable: false,\r\n },\r\n $reset: {\r\n value: () => {\r\n const fresh = stateFactory();\r\n batch(() => {\r\n for (const [key, sig] of stateSignals) {\r\n sig.value = fresh[key];\r\n }\r\n });\r\n notifySubscribers();\r\n },\r\n writable: false,\r\n enumerable: false,\r\n },\r\n $subscribe: {\r\n value: (callback: (state: S) => void) => {\r\n subscribers.push(callback);\r\n return () => {\r\n const index = subscribers.indexOf(callback);\r\n if (index > -1) subscribers.splice(index, 1);\r\n };\r\n },\r\n writable: false,\r\n enumerable: false,\r\n },\r\n $patch: {\r\n value: (partial: Partial<S> | ((state: S) => void)) => {\r\n batch(() => {\r\n if (typeof partial === 'function') {\r\n // Capture state before mutation for nested mutation detection\r\n const stateBefore = __DEV__ ? deepClone(getCurrentState()) : null;\r\n const signalValuesBefore = __DEV__\r\n ? new Map(Array.from(stateSignals.entries()).map(([k, s]) => [k, s.value]))\r\n : null;\r\n\r\n // Mutation function\r\n const state = getCurrentState();\r\n partial(state);\r\n\r\n // Detect nested mutations in development mode\r\n if (__DEV__ && stateBefore && signalValuesBefore) {\r\n const mutatedKeys = detectNestedMutations(stateBefore, state, signalValuesBefore);\r\n if (mutatedKeys.length > 0) {\r\n console.warn(\r\n `[bQuery store \"${id}\"] Nested mutation detected in $patch() for keys: ${mutatedKeys.map(String).join(', ')}.\\n` +\r\n 'Nested object mutations do not trigger reactive updates because the store uses shallow reactivity.\\n' +\r\n 'To fix this, either:\\n' +\r\n ' 1. Replace the entire object: state.user = { ...state.user, name: \"New\" }\\n' +\r\n ' 2. Use $patchDeep() for automatic deep cloning\\n' +\r\n 'See: https://bquery.dev/guide/store#deep-reactivity'\r\n );\r\n }\r\n }\r\n\r\n for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {\r\n if (stateSignals.has(key)) {\r\n stateSignals.get(key)!.value = value;\r\n }\r\n }\r\n } else {\r\n // Partial object\r\n for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {\r\n if (stateSignals.has(key)) {\r\n stateSignals.get(key)!.value = value;\r\n }\r\n }\r\n }\r\n });\r\n notifySubscribers();\r\n },\r\n writable: false,\r\n enumerable: false,\r\n },\r\n $patchDeep: {\r\n value: (partial: Partial<S> | ((state: S) => void)) => {\r\n batch(() => {\r\n if (typeof partial === 'function') {\r\n // Deep clone state before mutation to ensure new references\r\n const state = deepClone(getCurrentState());\r\n partial(state);\r\n\r\n for (const [key, value] of Object.entries(state) as Array<[keyof S, unknown]>) {\r\n if (stateSignals.has(key)) {\r\n stateSignals.get(key)!.value = value;\r\n }\r\n }\r\n } else {\r\n // Deep clone each value in partial to ensure new references\r\n for (const [key, value] of Object.entries(partial) as Array<[keyof S, unknown]>) {\r\n if (stateSignals.has(key)) {\r\n stateSignals.get(key)!.value = deepClone(value);\r\n }\r\n }\r\n }\r\n });\r\n notifySubscribers();\r\n },\r\n writable: false,\r\n enumerable: false,\r\n },\r\n $state: {\r\n get: () => getCurrentState(),\r\n enumerable: false,\r\n },\r\n });\r\n\r\n // Register store\r\n storeRegistry.set(id, store);\r\n\r\n // Apply plugins\r\n for (const plugin of plugins) {\r\n const extension = plugin({ store, options: definition });\r\n if (extension) {\r\n Object.assign(store, extension);\r\n }\r\n }\r\n\r\n // Notify devtools\r\n if (typeof window !== 'undefined') {\r\n if (!window.__BQUERY_DEVTOOLS__) {\r\n window.__BQUERY_DEVTOOLS__ = { stores: new Map() };\r\n }\r\n window.__BQUERY_DEVTOOLS__.stores.set(id, store);\r\n window.__BQUERY_DEVTOOLS__.onStoreCreated?.(id, store);\r\n }\r\n\r\n return store;\r\n};\r\n\r\n// ============================================================================\r\n// Store Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Retrieves an existing store by its ID.\r\n *\r\n * @param id - The store identifier\r\n * @returns The store instance or undefined if not found\r\n *\r\n * @example\r\n * ```ts\r\n * import { getStore } from 'bquery/store';\r\n *\r\n * const counter = getStore('counter');\r\n * if (counter) {\r\n * counter.increment();\r\n * }\r\n * ```\r\n */\r\nexport const getStore = <T = unknown>(id: string): T | undefined => {\r\n return storeRegistry.get(id) as T | undefined;\r\n};\r\n\r\n/**\r\n * Lists all registered store IDs.\r\n *\r\n * @returns Array of store IDs\r\n *\r\n * @example\r\n * ```ts\r\n * import { listStores } from 'bquery/store';\r\n *\r\n * console.log('Active stores:', listStores());\r\n * ```\r\n */\r\nexport const listStores = (): string[] => {\r\n return Array.from(storeRegistry.keys());\r\n};\r\n\r\n/**\r\n * Removes a store from the registry.\r\n *\r\n * @param id - The store identifier\r\n *\r\n * @example\r\n * ```ts\r\n * import { destroyStore } from 'bquery/store';\r\n *\r\n * destroyStore('counter');\r\n * ```\r\n */\r\nexport const destroyStore = (id: string): void => {\r\n storeRegistry.delete(id);\r\n if (typeof window !== 'undefined' && window.__BQUERY_DEVTOOLS__) {\r\n window.__BQUERY_DEVTOOLS__.stores.delete(id);\r\n }\r\n};\r\n\r\n/**\r\n * Registers a plugin that extends all stores.\r\n *\r\n * @param plugin - The plugin function\r\n *\r\n * @example\r\n * ```ts\r\n * import { registerPlugin } from 'bquery/store';\r\n *\r\n * // Add localStorage persistence\r\n * registerPlugin(({ store, options }) => {\r\n * const key = `bquery-store-${options.id}`;\r\n *\r\n * // Load saved state\r\n * const saved = localStorage.getItem(key);\r\n * if (saved) {\r\n * store.$patch(JSON.parse(saved));\r\n * }\r\n *\r\n * // Save on changes\r\n * store.$subscribe((state) => {\r\n * localStorage.setItem(key, JSON.stringify(state));\r\n * });\r\n * });\r\n * ```\r\n */\r\nexport const registerPlugin = (plugin: StorePlugin): void => {\r\n plugins.push(plugin);\r\n};\r\n\r\n// ============================================================================\r\n// Composition Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Creates a store with automatic persistence to localStorage.\r\n *\r\n * @param definition - Store definition\r\n * @param storageKey - Optional custom storage key\r\n * @returns The reactive store instance\r\n *\r\n * @example\r\n * ```ts\r\n * import { createPersistedStore } from 'bquery/store';\r\n *\r\n * const settings = createPersistedStore({\r\n * id: 'settings',\r\n * state: () => ({\r\n * theme: 'dark',\r\n * language: 'en',\r\n * }),\r\n * });\r\n *\r\n * // State is automatically saved/loaded from localStorage\r\n * settings.theme = 'light';\r\n * ```\r\n */\r\nexport const createPersistedStore = <\r\n S extends Record<string, unknown>,\r\n G extends Record<string, unknown> = Record<string, never>,\r\n A extends Record<string, (...args: unknown[]) => unknown> = Record<string, never>,\r\n>(\r\n definition: StoreDefinition<S, G, A>,\r\n storageKey?: string\r\n): Store<S, G, A> => {\r\n const key = storageKey ?? `bquery-store-${definition.id}`;\r\n\r\n // Wrap state factory to load from storage\r\n const originalStateFactory = definition.state;\r\n definition.state = () => {\r\n const defaultState = originalStateFactory();\r\n\r\n if (typeof window !== 'undefined') {\r\n try {\r\n const saved = localStorage.getItem(key);\r\n if (saved) {\r\n return { ...defaultState, ...JSON.parse(saved) };\r\n }\r\n } catch {\r\n // Ignore parse errors\r\n }\r\n }\r\n\r\n return defaultState;\r\n };\r\n\r\n const store = createStore(definition);\r\n\r\n // Subscribe to save changes\r\n store.$subscribe((state) => {\r\n if (typeof window !== 'undefined') {\r\n try {\r\n localStorage.setItem(key, JSON.stringify(state));\r\n } catch {\r\n // Ignore quota errors\r\n }\r\n }\r\n });\r\n\r\n return store;\r\n};\r\n\r\n/**\r\n * Maps store state properties to a reactive object for use in components.\r\n *\r\n * @param store - The store instance\r\n * @param keys - State keys to map\r\n * @returns Object with mapped properties\r\n *\r\n * @example\r\n * ```ts\r\n * import { mapState } from 'bquery/store';\r\n *\r\n * const counter = useCounter();\r\n * const { count, step } = mapState(counter, ['count', 'step']);\r\n * ```\r\n */\r\nexport const mapState = <S extends Record<string, unknown>, K extends keyof S>(\r\n store: S,\r\n keys: K[]\r\n): Pick<S, K> => {\r\n const mapped = {} as Pick<S, K>;\r\n\r\n for (const key of keys) {\r\n Object.defineProperty(mapped, key, {\r\n get: () => store[key],\r\n enumerable: true,\r\n });\r\n }\r\n\r\n return mapped;\r\n};\r\n\r\n/**\r\n * Maps store actions to an object for easier destructuring.\r\n *\r\n * @param store - The store instance\r\n * @param keys - Action keys to map\r\n * @returns Object with mapped actions\r\n *\r\n * @example\r\n * ```ts\r\n * import { mapActions } from 'bquery/store';\r\n *\r\n * const counter = useCounter();\r\n * const { increment, decrement } = mapActions(counter, ['increment', 'decrement']);\r\n *\r\n * // Use directly\r\n * increment();\r\n * ```\r\n */\r\nexport const mapActions = <\r\n A extends Record<string, (...args: unknown[]) => unknown>,\r\n K extends keyof A,\r\n>(\r\n store: A,\r\n keys: K[]\r\n): Pick<A, K> => {\r\n const mapped = {} as Pick<A, K>;\r\n\r\n for (const key of keys) {\r\n (mapped as Record<string, unknown>)[key as string] = (...args: unknown[]) =>\r\n (store[key] as (...args: unknown[]) => unknown)(...args);\r\n }\r\n\r\n return mapped;\r\n};\r\n"],"names":["storeRegistry","isPlainObject","value","deepClone","obj","k","v","cloned","key","deepEqual","a","b","item","i","keysA","keysB","detectNestedMutations","before","after","signalValues","mutatedKeys","beforeValue","afterValue","__DEV__","globalProcess","plugins","createStore","definition","id","stateFactory","getters","actions","initialState","stateSignals","signal","subscribers","notifySubscribers","currentState","getCurrentState","callback","stateProxy","_","prop","getterComputed","store","getterFn","computedGetter","computed","state","getterProxy","propKey","actionFn","args","context","target","_target","fresh","batch","sig","index","partial","stateBefore","signalValuesBefore","s","plugin","extension","getStore","listStores","destroyStore","registerPlugin","createPersistedStore","storageKey","originalStateFactory","defaultState","saved","mapState","keys","mapped","mapActions"],"mappings":"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"}
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Declarative DOM bindings via data attributes.
3
+ *
4
+ * This module provides Vue/Svelte-style template directives without
5
+ * requiring a compiler. Bindings are evaluated at runtime using
6
+ * bQuery's reactive system. Features include:
7
+ * - Conditional rendering (bq-if)
8
+ * - List rendering (bq-for)
9
+ * - Two-way binding (bq-model)
10
+ * - Class binding (bq-class)
11
+ * - Text/HTML binding (bq-text, bq-html)
12
+ * - Attribute binding (bq-bind)
13
+ * - Event binding (bq-on)
14
+ *
15
+ * ## Security Considerations
16
+ *
17
+ * **WARNING:** This module uses `new Function()` to evaluate expressions at runtime.
18
+ * This is similar to Vue/Alpine's approach but carries inherent security risks:
19
+ *
20
+ * - **NEVER** use expressions derived from user input or untrusted sources
21
+ * - Expressions should only come from developer-controlled templates
22
+ * - The context object should not contain sensitive data that could be exfiltrated
23
+ * - For user-generated content, use static bindings with sanitized values instead
24
+ *
25
+ * Since bQuery is runtime-only (no build-time compilation), expressions are evaluated
26
+ * dynamically. If your application loads templates from external sources (APIs, databases),
27
+ * ensure they are trusted and validated before mounting.
28
+ *
29
+ * ## Content Security Policy (CSP) Compatibility
30
+ *
31
+ * **IMPORTANT:** This module requires `'unsafe-eval'` in your CSP `script-src` directive.
32
+ * The `new Function()` constructor used for expression evaluation will be blocked by
33
+ * strict CSP policies that omit `'unsafe-eval'`.
34
+ *
35
+ * ### Required CSP Header
36
+ * ```
37
+ * Content-Security-Policy: script-src 'self' 'unsafe-eval';
38
+ * ```
39
+ *
40
+ * ### CSP-Strict Alternatives
41
+ *
42
+ * If your application requires a strict CSP without `'unsafe-eval'`, consider these alternatives:
43
+ *
44
+ * 1. **Use bQuery's core reactive system directly** - Bind signals to DOM elements manually
45
+ * using `effect()` without the view module's template directives:
46
+ * ```ts
47
+ * import { signal, effect } from 'bquery/reactive';
48
+ * import { $ } from 'bquery';
49
+ *
50
+ * const count = signal(0);
51
+ * effect(() => {
52
+ * $('#counter').text(String(count.value));
53
+ * });
54
+ * ```
55
+ *
56
+ * 2. **Use bQuery's component module** - Web Components with typed props don't require
57
+ * dynamic expression evaluation:
58
+ * ```ts
59
+ * import { component } from 'bquery/component';
60
+ * component('my-counter', {
61
+ * props: { count: { type: Number } },
62
+ * render: ({ props }) => `<span>${props.count}</span>`,
63
+ * });
64
+ * ```
65
+ *
66
+ * 3. **Pre-compile templates at build time** - Use a build step to transform bq-* attributes
67
+ * into static JavaScript (similar to Svelte/Vue SFC compilation). This is outside bQuery's
68
+ * scope but can be achieved with custom Vite/Rollup plugins.
69
+ *
70
+ * The view module is designed for rapid prototyping and applications where CSP flexibility
71
+ * is acceptable. For security-critical applications requiring strict CSP, use the alternatives above.
72
+ *
73
+ * @module bquery/view
74
+ *
75
+ * @example
76
+ * ```html
77
+ * <div id="app">
78
+ * <input bq-model="name" />
79
+ * <p bq-text="greeting"></p>
80
+ * <ul>
81
+ * <li bq-for="item in items" bq-text="item.name"></li>
82
+ * </ul>
83
+ * <button bq-on:click="handleClick">Click me</button>
84
+ * <div bq-if="showDetails" bq-class="{ active: isActive }">
85
+ * Details here
86
+ * </div>
87
+ * </div>
88
+ * ```
89
+ *
90
+ * ```ts
91
+ * import { mount } from 'bquery/view';
92
+ * import { signal } from 'bquery/reactive';
93
+ *
94
+ * mount('#app', {
95
+ * name: signal('World'),
96
+ * greeting: computed(() => `Hello, ${name.value}!`),
97
+ * items: signal([{ name: 'Item 1' }, { name: 'Item 2' }]),
98
+ * showDetails: signal(true),
99
+ * isActive: signal(false),
100
+ * handleClick: () => console.log('Clicked!'),
101
+ * });
102
+ * ```
103
+ */
104
+ /**
105
+ * Context object passed to binding expressions.
106
+ */
107
+ export type BindingContext = Record<string, unknown>;
108
+ /**
109
+ * Configuration options for mount.
110
+ */
111
+ export type MountOptions = {
112
+ /** Prefix for directive attributes (default: 'bq') */
113
+ prefix?: string;
114
+ /** Whether to sanitize bq-html content (default: true) */
115
+ sanitize?: boolean;
116
+ };
117
+ /**
118
+ * Mounted view instance.
119
+ */
120
+ export type View = {
121
+ /** The root element */
122
+ el: Element;
123
+ /** The binding context */
124
+ context: BindingContext;
125
+ /** Update the context and re-render */
126
+ update: (newContext: Partial<BindingContext>) => void;
127
+ /** Destroy the view and cleanup effects */
128
+ destroy: () => void;
129
+ };
130
+ /**
131
+ * Mounts a reactive view to an element.
132
+ *
133
+ * @param selector - CSS selector or Element
134
+ * @param context - Binding context with signals, computed, and functions
135
+ * @param options - Mount options
136
+ * @returns The mounted View instance
137
+ *
138
+ * @security **WARNING:** Directive expressions (bq-text, bq-if, bq-on, etc.) are evaluated
139
+ * using `new Function()` at runtime. This means:
140
+ * - Template attributes must come from trusted sources only
141
+ * - NEVER load templates containing bq-* attributes from user input or untrusted APIs
142
+ * - If you must use external templates, validate/sanitize attribute values first
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * import { mount } from 'bquery/view';
147
+ * import { signal, computed } from 'bquery/reactive';
148
+ *
149
+ * const name = signal('World');
150
+ * const greeting = computed(() => `Hello, ${name.value}!`);
151
+ * const items = signal([
152
+ * { id: 1, text: 'Item 1' },
153
+ * { id: 2, text: 'Item 2' },
154
+ * ]);
155
+ *
156
+ * const view = mount('#app', {
157
+ * name,
158
+ * greeting,
159
+ * items,
160
+ * addItem: () => {
161
+ * items.value = [...items.value, { id: Date.now(), text: 'New Item' }];
162
+ * },
163
+ * });
164
+ *
165
+ * // Later, cleanup
166
+ * view.destroy();
167
+ * ```
168
+ */
169
+ export declare const mount: (selector: string | Element, context: BindingContext, options?: MountOptions) => View;
170
+ /**
171
+ * Creates a reactive template function.
172
+ *
173
+ * @param template - HTML template string
174
+ * @returns A function that creates a mounted element with the given context
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * import { createTemplate } from 'bquery/view';
179
+ * import { signal } from 'bquery/reactive';
180
+ *
181
+ * const TodoItem = createTemplate(`
182
+ * <li bq-class="{ completed: done }">
183
+ * <input type="checkbox" bq-model="done" />
184
+ * <span bq-text="text"></span>
185
+ * </li>
186
+ * `);
187
+ *
188
+ * const item = TodoItem({
189
+ * done: signal(false),
190
+ * text: 'Buy groceries',
191
+ * });
192
+ *
193
+ * document.querySelector('#list').append(item.el);
194
+ * ```
195
+ */
196
+ export declare const createTemplate: (template: string, options?: MountOptions) => ((context: BindingContext) => View);
197
+ /**
198
+ * Re-export reactive primitives for convenience.
199
+ */
200
+ export { batch, computed, effect, signal } from '../reactive/index';
201
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/view/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsGG;AASH;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,IAAI,GAAG;IACjB,uBAAuB;IACvB,EAAE,EAAE,OAAO,CAAC;IACZ,0BAA0B;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,uCAAuC;IACvC,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;IACtD,2CAA2C;IAC3C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAywBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,KAAK,GAChB,UAAU,MAAM,GAAG,OAAO,EAC1B,SAAS,cAAc,EACvB,UAAS,YAAiB,KACzB,IA8BF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,UAAS,YAAiB,KACzB,CAAC,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAYpC,CAAC;AAMF;;GAEG;AACH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC"}