@bquery/bquery 1.0.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.
Files changed (80) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +266 -0
  3. package/dist/component/index.d.ts +155 -0
  4. package/dist/component/index.d.ts.map +1 -0
  5. package/dist/component.es.mjs +128 -0
  6. package/dist/component.es.mjs.map +1 -0
  7. package/dist/core/collection.d.ts +198 -0
  8. package/dist/core/collection.d.ts.map +1 -0
  9. package/dist/core/element.d.ts +301 -0
  10. package/dist/core/element.d.ts.map +1 -0
  11. package/dist/core/index.d.ts +5 -0
  12. package/dist/core/index.d.ts.map +1 -0
  13. package/dist/core/selector.d.ts +11 -0
  14. package/dist/core/selector.d.ts.map +1 -0
  15. package/dist/core/shared.d.ts +7 -0
  16. package/dist/core/shared.d.ts.map +1 -0
  17. package/dist/core/utils.d.ts +300 -0
  18. package/dist/core/utils.d.ts.map +1 -0
  19. package/dist/core.es.mjs +1015 -0
  20. package/dist/core.es.mjs.map +1 -0
  21. package/dist/full.d.ts +48 -0
  22. package/dist/full.d.ts.map +1 -0
  23. package/dist/full.es.mjs +43 -0
  24. package/dist/full.es.mjs.map +1 -0
  25. package/dist/full.iife.js +2 -0
  26. package/dist/full.iife.js.map +1 -0
  27. package/dist/full.umd.js +2 -0
  28. package/dist/full.umd.js.map +1 -0
  29. package/dist/index.d.ts +16 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.es.mjs +43 -0
  32. package/dist/index.es.mjs.map +1 -0
  33. package/dist/motion/index.d.ts +145 -0
  34. package/dist/motion/index.d.ts.map +1 -0
  35. package/dist/motion.es.mjs +104 -0
  36. package/dist/motion.es.mjs.map +1 -0
  37. package/dist/platform/buckets.d.ts +44 -0
  38. package/dist/platform/buckets.d.ts.map +1 -0
  39. package/dist/platform/cache.d.ts +71 -0
  40. package/dist/platform/cache.d.ts.map +1 -0
  41. package/dist/platform/index.d.ts +15 -0
  42. package/dist/platform/index.d.ts.map +1 -0
  43. package/dist/platform/notifications.d.ts +52 -0
  44. package/dist/platform/notifications.d.ts.map +1 -0
  45. package/dist/platform/storage.d.ts +69 -0
  46. package/dist/platform/storage.d.ts.map +1 -0
  47. package/dist/platform.es.mjs +245 -0
  48. package/dist/platform.es.mjs.map +1 -0
  49. package/dist/reactive/index.d.ts +8 -0
  50. package/dist/reactive/index.d.ts.map +1 -0
  51. package/dist/reactive/signal.d.ts +204 -0
  52. package/dist/reactive/signal.d.ts.map +1 -0
  53. package/dist/reactive.es.mjs +123 -0
  54. package/dist/reactive.es.mjs.map +1 -0
  55. package/dist/security/index.d.ts +8 -0
  56. package/dist/security/index.d.ts.map +1 -0
  57. package/dist/security/sanitize.d.ts +99 -0
  58. package/dist/security/sanitize.d.ts.map +1 -0
  59. package/dist/security.es.mjs +194 -0
  60. package/dist/security.es.mjs.map +1 -0
  61. package/package.json +120 -0
  62. package/src/component/index.ts +360 -0
  63. package/src/core/collection.ts +339 -0
  64. package/src/core/element.ts +493 -0
  65. package/src/core/index.ts +4 -0
  66. package/src/core/selector.ts +29 -0
  67. package/src/core/shared.ts +13 -0
  68. package/src/core/utils.ts +425 -0
  69. package/src/full.ts +101 -0
  70. package/src/index.ts +27 -0
  71. package/src/motion/index.ts +365 -0
  72. package/src/platform/buckets.ts +115 -0
  73. package/src/platform/cache.ts +130 -0
  74. package/src/platform/index.ts +18 -0
  75. package/src/platform/notifications.ts +87 -0
  76. package/src/platform/storage.ts +208 -0
  77. package/src/reactive/index.ts +9 -0
  78. package/src/reactive/signal.ts +347 -0
  79. package/src/security/index.ts +18 -0
  80. package/src/security/sanitize.ts +446 -0
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Unified storage adapters for web platform storage APIs.
3
+ * Provides a consistent, promise-based interface with predictable errors.
4
+ */
5
+
6
+ /**
7
+ * Common interface for all storage adapters.
8
+ * All methods return promises for a unified async API.
9
+ */
10
+ export interface StorageAdapter {
11
+ /**
12
+ * Retrieve a value by key.
13
+ * @param key - The storage key
14
+ * @returns The stored value or null if not found
15
+ */
16
+ get<T>(key: string): Promise<T | null>;
17
+
18
+ /**
19
+ * Store a value by key.
20
+ * @param key - The storage key
21
+ * @param value - The value to store
22
+ */
23
+ set<T>(key: string, value: T): Promise<void>;
24
+
25
+ /**
26
+ * Remove a value by key.
27
+ * @param key - The storage key
28
+ */
29
+ remove(key: string): Promise<void>;
30
+
31
+ /**
32
+ * Clear all stored values.
33
+ */
34
+ clear(): Promise<void>;
35
+
36
+ /**
37
+ * Get all storage keys.
38
+ * @returns Array of all keys
39
+ */
40
+ keys(): Promise<string[]>;
41
+ }
42
+
43
+ /**
44
+ * Abstract base class for web storage adapters (localStorage/sessionStorage).
45
+ * Implements DRY principle by sharing common logic.
46
+ */
47
+ abstract class WebStorageAdapter implements StorageAdapter {
48
+ constructor(protected readonly storage: Storage) {}
49
+
50
+ async get<T>(key: string): Promise<T | null> {
51
+ const raw = this.storage.getItem(key);
52
+ if (raw === null) return null;
53
+ try {
54
+ return JSON.parse(raw) as T;
55
+ } catch {
56
+ return raw as unknown as T;
57
+ }
58
+ }
59
+
60
+ async set<T>(key: string, value: T): Promise<void> {
61
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
62
+ this.storage.setItem(key, serialized);
63
+ }
64
+
65
+ async remove(key: string): Promise<void> {
66
+ this.storage.removeItem(key);
67
+ }
68
+
69
+ async clear(): Promise<void> {
70
+ this.storage.clear();
71
+ }
72
+
73
+ async keys(): Promise<string[]> {
74
+ return Object.keys(this.storage);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * localStorage adapter with async interface.
80
+ */
81
+ class LocalStorageAdapter extends WebStorageAdapter {
82
+ constructor() {
83
+ super(localStorage);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * sessionStorage adapter with async interface.
89
+ */
90
+ class SessionStorageAdapter extends WebStorageAdapter {
91
+ constructor() {
92
+ super(sessionStorage);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * IndexedDB configuration options.
98
+ */
99
+ export interface IndexedDBOptions {
100
+ /** Database name */
101
+ name: string;
102
+ /** Object store name */
103
+ store: string;
104
+ /** Database version (optional) */
105
+ version?: number;
106
+ }
107
+
108
+ /**
109
+ * IndexedDB key-value adapter.
110
+ * Wraps IndexedDB with a simple key-value interface.
111
+ */
112
+ class IndexedDBAdapter implements StorageAdapter {
113
+ private dbPromise: Promise<IDBDatabase> | null = null;
114
+
115
+ constructor(private readonly options: IndexedDBOptions) {}
116
+
117
+ /**
118
+ * Opens or creates the IndexedDB database.
119
+ */
120
+ private openDB(): Promise<IDBDatabase> {
121
+ if (this.dbPromise) return this.dbPromise;
122
+
123
+ this.dbPromise = new Promise((resolve, reject) => {
124
+ const request = indexedDB.open(this.options.name, this.options.version ?? 1);
125
+
126
+ request.onupgradeneeded = () => {
127
+ const db = request.result;
128
+ if (!db.objectStoreNames.contains(this.options.store)) {
129
+ db.createObjectStore(this.options.store);
130
+ }
131
+ };
132
+
133
+ request.onsuccess = () => resolve(request.result);
134
+ request.onerror = () => reject(request.error);
135
+ });
136
+
137
+ return this.dbPromise;
138
+ }
139
+
140
+ /**
141
+ * Executes a transaction on the object store.
142
+ */
143
+ private async withStore<T>(
144
+ mode: IDBTransactionMode,
145
+ operation: (store: IDBObjectStore) => IDBRequest<T>
146
+ ): Promise<T> {
147
+ const db = await this.openDB();
148
+ return new Promise((resolve, reject) => {
149
+ const tx = db.transaction(this.options.store, mode);
150
+ const store = tx.objectStore(this.options.store);
151
+ const request = operation(store);
152
+ request.onsuccess = () => resolve(request.result);
153
+ request.onerror = () => reject(request.error);
154
+ });
155
+ }
156
+
157
+ async get<T>(key: string): Promise<T | null> {
158
+ const result = await this.withStore<T | undefined>('readonly', (store) => store.get(key));
159
+ return result ?? null;
160
+ }
161
+
162
+ async set<T>(key: string, value: T): Promise<void> {
163
+ await this.withStore('readwrite', (store) => store.put(value, key));
164
+ }
165
+
166
+ async remove(key: string): Promise<void> {
167
+ await this.withStore('readwrite', (store) => store.delete(key));
168
+ }
169
+
170
+ async clear(): Promise<void> {
171
+ await this.withStore('readwrite', (store) => store.clear());
172
+ }
173
+
174
+ async keys(): Promise<string[]> {
175
+ const result = await this.withStore<IDBValidKey[]>('readonly', (store) => store.getAllKeys());
176
+ return result.map((key) => String(key));
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Storage factory providing access to different storage adapters.
182
+ */
183
+ export const storage = {
184
+ /**
185
+ * Create a localStorage adapter.
186
+ * @returns StorageAdapter wrapping localStorage
187
+ */
188
+ local(): StorageAdapter {
189
+ return new LocalStorageAdapter();
190
+ },
191
+
192
+ /**
193
+ * Create a sessionStorage adapter.
194
+ * @returns StorageAdapter wrapping sessionStorage
195
+ */
196
+ session(): StorageAdapter {
197
+ return new SessionStorageAdapter();
198
+ },
199
+
200
+ /**
201
+ * Create an IndexedDB adapter with key-value interface.
202
+ * @param options - Database and store configuration
203
+ * @returns StorageAdapter wrapping IndexedDB
204
+ */
205
+ indexedDB(options: IndexedDBOptions): StorageAdapter {
206
+ return new IndexedDBAdapter(options);
207
+ },
208
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Reactive module providing fine-grained reactivity primitives.
3
+ *
4
+ * @module bquery/reactive
5
+ */
6
+
7
+ export { batch, Computed, computed, effect, persistedSignal, Signal, signal } from './signal';
8
+
9
+ export type { CleanupFn, Observer } from './signal';
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Reactive primitives inspired by fine-grained reactivity.
3
+ *
4
+ * This module provides a minimal but powerful reactive system:
5
+ * - Signal: A reactive value that notifies subscribers when changed
6
+ * - Computed: A derived value that automatically updates when dependencies change
7
+ * - Effect: A side effect that re-runs when its dependencies change
8
+ * - Batch: Group multiple updates to prevent intermediate re-renders
9
+ *
10
+ * @module bquery/reactive
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const count = signal(0);
15
+ * const doubled = computed(() => count.value * 2);
16
+ *
17
+ * effect(() => {
18
+ * console.log(`Count: ${count.value}, Doubled: ${doubled.value}`);
19
+ * });
20
+ *
21
+ * batch(() => {
22
+ * count.value = 1;
23
+ * count.value = 2;
24
+ * });
25
+ * // Logs: "Count: 2, Doubled: 4" (only once due to batching)
26
+ * ```
27
+ */
28
+
29
+ /**
30
+ * Observer function type used internally for tracking reactivity.
31
+ */
32
+ export type Observer = () => void;
33
+
34
+ /**
35
+ * Cleanup function returned by effects for disposal.
36
+ */
37
+ export type CleanupFn = () => void;
38
+
39
+ // Internal state for tracking the current observer context
40
+ let observerStack: Observer[] = [];
41
+ let batchDepth = 0;
42
+ const pendingObservers = new Set<Observer>();
43
+
44
+ /**
45
+ * Tracks dependencies during a function execution.
46
+ * @internal
47
+ */
48
+ const track = <T>(observer: Observer, fn: () => T): T => {
49
+ observerStack = [...observerStack, observer];
50
+ try {
51
+ return fn();
52
+ } finally {
53
+ observerStack = observerStack.slice(0, -1);
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Schedules an observer to run, respecting batch mode.
59
+ * @internal
60
+ */
61
+ const scheduleObserver = (observer: Observer) => {
62
+ if (batchDepth > 0) {
63
+ pendingObservers.add(observer);
64
+ return;
65
+ }
66
+ observer();
67
+ };
68
+
69
+ /**
70
+ * Flushes all pending observers after a batch completes.
71
+ * @internal
72
+ */
73
+ const flushObservers = () => {
74
+ for (const observer of Array.from(pendingObservers)) {
75
+ pendingObservers.delete(observer);
76
+ observer();
77
+ }
78
+ };
79
+
80
+ /**
81
+ * A reactive value container that notifies subscribers on change.
82
+ *
83
+ * Signals are the foundational primitive of the reactive system.
84
+ * Reading a signal's value inside an effect or computed automatically
85
+ * establishes a reactive dependency.
86
+ *
87
+ * @template T - The type of the stored value
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * const name = signal('World');
92
+ * console.log(name.value); // 'World'
93
+ *
94
+ * name.value = 'bQuery';
95
+ * console.log(name.value); // 'bQuery'
96
+ * ```
97
+ */
98
+ export class Signal<T> {
99
+ private subscribers = new Set<Observer>();
100
+
101
+ /**
102
+ * Creates a new signal with an initial value.
103
+ * @param _value - The initial value
104
+ */
105
+ constructor(private _value: T) {}
106
+
107
+ /**
108
+ * Gets the current value and tracks the read if inside an observer.
109
+ */
110
+ get value(): T {
111
+ const current = observerStack[observerStack.length - 1];
112
+ if (current) {
113
+ this.subscribers.add(current);
114
+ }
115
+ return this._value;
116
+ }
117
+
118
+ /**
119
+ * Sets a new value and notifies all subscribers if the value changed.
120
+ * Uses Object.is for equality comparison.
121
+ */
122
+ set value(next: T) {
123
+ if (Object.is(this._value, next)) return;
124
+ this._value = next;
125
+ for (const subscriber of this.subscribers) {
126
+ scheduleObserver(subscriber);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Reads the current value without tracking.
132
+ * Useful when you need the value but don't want to create a dependency.
133
+ *
134
+ * @returns The current value
135
+ */
136
+ peek(): T {
137
+ return this._value;
138
+ }
139
+
140
+ /**
141
+ * Updates the value using a function.
142
+ * Useful for updates based on the current value.
143
+ *
144
+ * @param updater - Function that receives current value and returns new value
145
+ */
146
+ update(updater: (current: T) => T): void {
147
+ this.value = updater(this._value);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * A computed value that derives from other reactive sources.
153
+ *
154
+ * Computed values are lazily evaluated and cached. They only
155
+ * recompute when their dependencies change.
156
+ *
157
+ * @template T - The type of the computed value
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const price = signal(100);
162
+ * const quantity = signal(2);
163
+ * const total = computed(() => price.value * quantity.value);
164
+ *
165
+ * console.log(total.value); // 200
166
+ * price.value = 150;
167
+ * console.log(total.value); // 300
168
+ * ```
169
+ */
170
+ export class Computed<T> {
171
+ private cachedValue!: T;
172
+ private dirty = true;
173
+ private subscribers = new Set<Observer>();
174
+ private readonly markDirty = () => {
175
+ this.dirty = true;
176
+ for (const subscriber of this.subscribers) {
177
+ scheduleObserver(subscriber);
178
+ }
179
+ };
180
+
181
+ /**
182
+ * Creates a new computed value.
183
+ * @param compute - Function that computes the value
184
+ */
185
+ constructor(private readonly compute: () => T) {}
186
+
187
+ /**
188
+ * Gets the computed value, recomputing if dependencies changed.
189
+ */
190
+ get value(): T {
191
+ const current = observerStack[observerStack.length - 1];
192
+ if (current) {
193
+ this.subscribers.add(current);
194
+ }
195
+ if (this.dirty) {
196
+ this.dirty = false;
197
+ this.cachedValue = track(this.markDirty, this.compute);
198
+ }
199
+ return this.cachedValue;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Creates a new reactive signal.
205
+ *
206
+ * @template T - The type of the signal value
207
+ * @param value - The initial value
208
+ * @returns A new Signal instance
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * const count = signal(0);
213
+ * count.value++; // Triggers subscribers
214
+ * ```
215
+ */
216
+ export const signal = <T>(value: T): Signal<T> => new Signal(value);
217
+
218
+ /**
219
+ * Creates a new computed value.
220
+ *
221
+ * @template T - The type of the computed value
222
+ * @param fn - Function that computes the value from reactive sources
223
+ * @returns A new Computed instance
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * const doubled = computed(() => count.value * 2);
228
+ * ```
229
+ */
230
+ export const computed = <T>(fn: () => T): Computed<T> => new Computed(fn);
231
+
232
+ /**
233
+ * Creates a side effect that automatically re-runs when dependencies change.
234
+ *
235
+ * The effect runs immediately upon creation and then re-runs whenever
236
+ * any signal or computed value read inside it changes.
237
+ *
238
+ * @param fn - The effect function to run
239
+ * @returns A cleanup function to stop the effect
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * const count = signal(0);
244
+ *
245
+ * const cleanup = effect(() => {
246
+ * document.title = `Count: ${count.value}`;
247
+ * });
248
+ *
249
+ * // Later, to stop the effect:
250
+ * cleanup();
251
+ * ```
252
+ */
253
+ export const effect = (fn: () => void | CleanupFn): CleanupFn => {
254
+ let cleanupFn: CleanupFn | void;
255
+ let isDisposed = false;
256
+
257
+ const observer: Observer = () => {
258
+ if (isDisposed) return;
259
+
260
+ // Run previous cleanup if exists
261
+ if (cleanupFn) {
262
+ cleanupFn();
263
+ }
264
+
265
+ // Run effect and capture cleanup
266
+ cleanupFn = track(observer, fn);
267
+ };
268
+
269
+ observer();
270
+
271
+ return () => {
272
+ isDisposed = true;
273
+ if (cleanupFn) {
274
+ cleanupFn();
275
+ }
276
+ };
277
+ };
278
+
279
+ /**
280
+ * Batches multiple signal updates into a single notification cycle.
281
+ *
282
+ * Updates made inside the batch function are deferred until the batch
283
+ * completes, preventing intermediate re-renders and improving performance.
284
+ *
285
+ * @param fn - Function containing multiple signal updates
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * batch(() => {
290
+ * firstName.value = 'John';
291
+ * lastName.value = 'Doe';
292
+ * age.value = 30;
293
+ * });
294
+ * // Effects only run once with all three updates
295
+ * ```
296
+ */
297
+ export const batch = (fn: () => void): void => {
298
+ batchDepth += 1;
299
+ try {
300
+ fn();
301
+ } finally {
302
+ batchDepth -= 1;
303
+ if (batchDepth === 0) {
304
+ flushObservers();
305
+ }
306
+ }
307
+ };
308
+
309
+ /**
310
+ * Creates a signal that persists to localStorage.
311
+ *
312
+ * @template T - The type of the signal value
313
+ * @param key - The localStorage key
314
+ * @param initialValue - The initial value if not found in storage
315
+ * @returns A Signal that syncs with localStorage
316
+ *
317
+ * @example
318
+ * ```ts
319
+ * const theme = persistedSignal('theme', 'light');
320
+ * theme.value = 'dark'; // Automatically saved to localStorage
321
+ * ```
322
+ */
323
+ export const persistedSignal = <T>(key: string, initialValue: T): Signal<T> => {
324
+ let stored: T = initialValue;
325
+
326
+ try {
327
+ const raw = localStorage.getItem(key);
328
+ if (raw !== null) {
329
+ stored = JSON.parse(raw) as T;
330
+ }
331
+ } catch {
332
+ // Use initial value on parse error
333
+ }
334
+
335
+ const sig = signal(stored);
336
+
337
+ // Create an effect to persist changes
338
+ effect(() => {
339
+ try {
340
+ localStorage.setItem(key, JSON.stringify(sig.value));
341
+ } catch {
342
+ // Ignore storage errors
343
+ }
344
+ });
345
+
346
+ return sig;
347
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Security module providing sanitization, CSP compatibility, and Trusted Types.
3
+ *
4
+ * @module bquery/security
5
+ */
6
+
7
+ export {
8
+ createTrustedHtml,
9
+ escapeHtml,
10
+ generateNonce,
11
+ getTrustedTypesPolicy,
12
+ hasCSPDirective,
13
+ isTrustedTypesSupported,
14
+ sanitizeHtml as sanitize,
15
+ sanitizeHtml,
16
+ stripTags,
17
+ } from './sanitize';
18
+ export type { SanitizeOptions } from './sanitize';