@bquery/bquery 1.4.0 → 1.6.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 (164) hide show
  1. package/README.md +586 -527
  2. package/dist/component/component.d.ts +13 -5
  3. package/dist/component/component.d.ts.map +1 -1
  4. package/dist/component/html.d.ts +40 -3
  5. package/dist/component/html.d.ts.map +1 -1
  6. package/dist/component/index.d.ts +4 -2
  7. package/dist/component/index.d.ts.map +1 -1
  8. package/dist/component/library.d.ts +34 -0
  9. package/dist/component/library.d.ts.map +1 -0
  10. package/dist/component/types.d.ts +132 -13
  11. package/dist/component/types.d.ts.map +1 -1
  12. package/dist/component-BEQgt5hl.js +600 -0
  13. package/dist/component-BEQgt5hl.js.map +1 -0
  14. package/dist/component.es.mjs +7 -184
  15. package/dist/config-DRmZZno3.js +40 -0
  16. package/dist/config-DRmZZno3.js.map +1 -0
  17. package/dist/core-BGQJVw0-.js +35 -0
  18. package/dist/core-BGQJVw0-.js.map +1 -0
  19. package/dist/core-CCEabVHl.js +648 -0
  20. package/dist/core-CCEabVHl.js.map +1 -0
  21. package/dist/core.es.mjs +45 -1261
  22. package/dist/effect-AFRW_Plg.js +84 -0
  23. package/dist/effect-AFRW_Plg.js.map +1 -0
  24. package/dist/full.d.ts +8 -8
  25. package/dist/full.d.ts.map +1 -1
  26. package/dist/full.es.mjs +101 -91
  27. package/dist/full.iife.js +173 -3
  28. package/dist/full.iife.js.map +1 -1
  29. package/dist/full.umd.js +173 -3
  30. package/dist/full.umd.js.map +1 -1
  31. package/dist/index.es.mjs +147 -139
  32. package/dist/motion/transition.d.ts +1 -1
  33. package/dist/motion/transition.d.ts.map +1 -1
  34. package/dist/motion/types.d.ts +11 -1
  35. package/dist/motion/types.d.ts.map +1 -1
  36. package/dist/motion-D9TcHxOF.js +415 -0
  37. package/dist/motion-D9TcHxOF.js.map +1 -0
  38. package/dist/motion.es.mjs +25 -361
  39. package/dist/object-qGpWr6-J.js +38 -0
  40. package/dist/object-qGpWr6-J.js.map +1 -0
  41. package/dist/platform/announcer.d.ts +59 -0
  42. package/dist/platform/announcer.d.ts.map +1 -0
  43. package/dist/platform/config.d.ts +92 -0
  44. package/dist/platform/config.d.ts.map +1 -0
  45. package/dist/platform/cookies.d.ts +45 -0
  46. package/dist/platform/cookies.d.ts.map +1 -0
  47. package/dist/platform/index.d.ts +8 -0
  48. package/dist/platform/index.d.ts.map +1 -1
  49. package/dist/platform/meta.d.ts +62 -0
  50. package/dist/platform/meta.d.ts.map +1 -0
  51. package/dist/platform-Dr9b6fsq.js +362 -0
  52. package/dist/platform-Dr9b6fsq.js.map +1 -0
  53. package/dist/platform.es.mjs +11 -248
  54. package/dist/reactive/async-data.d.ts +114 -0
  55. package/dist/reactive/async-data.d.ts.map +1 -0
  56. package/dist/reactive/index.d.ts +2 -2
  57. package/dist/reactive/index.d.ts.map +1 -1
  58. package/dist/reactive/signal.d.ts +2 -0
  59. package/dist/reactive/signal.d.ts.map +1 -1
  60. package/dist/reactive-DSkct0dO.js +254 -0
  61. package/dist/reactive-DSkct0dO.js.map +1 -0
  62. package/dist/reactive.es.mjs +18 -32
  63. package/dist/router-CbDhl8rS.js +188 -0
  64. package/dist/router-CbDhl8rS.js.map +1 -0
  65. package/dist/router.es.mjs +11 -200
  66. package/dist/sanitize-Bs2dkMby.js +313 -0
  67. package/dist/sanitize-Bs2dkMby.js.map +1 -0
  68. package/dist/security/constants.d.ts.map +1 -1
  69. package/dist/security/index.d.ts +4 -2
  70. package/dist/security/index.d.ts.map +1 -1
  71. package/dist/security/sanitize.d.ts +4 -1
  72. package/dist/security/sanitize.d.ts.map +1 -1
  73. package/dist/security/trusted-html.d.ts +53 -0
  74. package/dist/security/trusted-html.d.ts.map +1 -0
  75. package/dist/security.es.mjs +11 -56
  76. package/dist/store/define-store.d.ts +1 -1
  77. package/dist/store/define-store.d.ts.map +1 -1
  78. package/dist/store/mapping.d.ts +1 -1
  79. package/dist/store/mapping.d.ts.map +1 -1
  80. package/dist/store/persisted.d.ts +1 -1
  81. package/dist/store/persisted.d.ts.map +1 -1
  82. package/dist/store/types.d.ts +2 -2
  83. package/dist/store/types.d.ts.map +1 -1
  84. package/dist/store/watch.d.ts +1 -1
  85. package/dist/store/watch.d.ts.map +1 -1
  86. package/dist/store-BwDvI45q.js +263 -0
  87. package/dist/store-BwDvI45q.js.map +1 -0
  88. package/dist/store.es.mjs +12 -25
  89. package/dist/storybook/index.d.ts +37 -0
  90. package/dist/storybook/index.d.ts.map +1 -0
  91. package/dist/storybook.es.mjs +151 -0
  92. package/dist/storybook.es.mjs.map +1 -0
  93. package/dist/untrack-B0rVscTc.js +7 -0
  94. package/dist/untrack-B0rVscTc.js.map +1 -0
  95. package/dist/view-C70lA3vf.js +397 -0
  96. package/dist/view-C70lA3vf.js.map +1 -0
  97. package/dist/view.es.mjs +11 -430
  98. package/package.json +141 -132
  99. package/src/component/component.ts +524 -289
  100. package/src/component/html.ts +153 -53
  101. package/src/component/index.ts +50 -40
  102. package/src/component/library.ts +518 -0
  103. package/src/component/types.ts +256 -85
  104. package/src/core/collection.ts +628 -628
  105. package/src/core/element.ts +774 -774
  106. package/src/core/index.ts +48 -48
  107. package/src/core/utils/function.ts +151 -151
  108. package/src/full.ts +229 -187
  109. package/src/motion/animate.ts +113 -113
  110. package/src/motion/flip.ts +176 -176
  111. package/src/motion/scroll.ts +57 -57
  112. package/src/motion/spring.ts +150 -150
  113. package/src/motion/timeline.ts +246 -246
  114. package/src/motion/transition.ts +97 -51
  115. package/src/motion/types.ts +11 -1
  116. package/src/platform/announcer.ts +208 -0
  117. package/src/platform/config.ts +163 -0
  118. package/src/platform/cookies.ts +165 -0
  119. package/src/platform/index.ts +21 -0
  120. package/src/platform/meta.ts +168 -0
  121. package/src/platform/storage.ts +215 -215
  122. package/src/reactive/async-data.ts +486 -0
  123. package/src/reactive/core.ts +114 -114
  124. package/src/reactive/effect.ts +54 -54
  125. package/src/reactive/index.ts +15 -1
  126. package/src/reactive/internals.ts +122 -122
  127. package/src/reactive/signal.ts +9 -0
  128. package/src/security/constants.ts +3 -1
  129. package/src/security/index.ts +17 -10
  130. package/src/security/sanitize-core.ts +364 -364
  131. package/src/security/sanitize.ts +70 -66
  132. package/src/security/trusted-html.ts +71 -0
  133. package/src/store/define-store.ts +49 -48
  134. package/src/store/mapping.ts +74 -73
  135. package/src/store/persisted.ts +62 -61
  136. package/src/store/types.ts +92 -94
  137. package/src/store/watch.ts +53 -52
  138. package/src/storybook/index.ts +479 -0
  139. package/src/view/evaluate.ts +290 -290
  140. package/dist/batch-x7b2eZST.js +0 -13
  141. package/dist/batch-x7b2eZST.js.map +0 -1
  142. package/dist/component.es.mjs.map +0 -1
  143. package/dist/core-BhpuvPhy.js +0 -170
  144. package/dist/core-BhpuvPhy.js.map +0 -1
  145. package/dist/core.es.mjs.map +0 -1
  146. package/dist/full.es.mjs.map +0 -1
  147. package/dist/index.es.mjs.map +0 -1
  148. package/dist/motion.es.mjs.map +0 -1
  149. package/dist/persisted-DHoi3uEs.js +0 -278
  150. package/dist/persisted-DHoi3uEs.js.map +0 -1
  151. package/dist/platform.es.mjs.map +0 -1
  152. package/dist/reactive.es.mjs.map +0 -1
  153. package/dist/router.es.mjs.map +0 -1
  154. package/dist/sanitize-Cxvxa-DX.js +0 -283
  155. package/dist/sanitize-Cxvxa-DX.js.map +0 -1
  156. package/dist/security.es.mjs.map +0 -1
  157. package/dist/store.es.mjs.map +0 -1
  158. package/dist/type-guards-BdKlYYlS.js +0 -32
  159. package/dist/type-guards-BdKlYYlS.js.map +0 -1
  160. package/dist/untrack-DNnnqdlR.js +0 -6
  161. package/dist/untrack-DNnnqdlR.js.map +0 -1
  162. package/dist/view.es.mjs.map +0 -1
  163. package/dist/watch-DXXv3iAI.js +0 -58
  164. package/dist/watch-DXXv3iAI.js.map +0 -1
package/README.md CHANGED
@@ -1,527 +1,586 @@
1
- <p align="center">
2
- <img src="assets/bquerry-logo.svg" alt="bQuery.js Logo" width="120" />
3
- </p>
4
-
5
- <h1 align="center">bQuery.js</h1>
6
-
7
- <p align="center">
8
-
9
- [![Repo](https://img.shields.io/badge/github-bquery%2Fbquery-24292f?logo=github)](https://github.com/bQuery/bQuery)
10
- [![Stars](https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/stargazers)
11
- [![Issues](https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/issues)
12
- [![License](https://img.shields.io/github/license/bquery/bquery?style=flat)](https://github.com/bQuery/bQuery/blob/main/LICENSE.md)
13
- [![npm](https://img.shields.io/npm/v/@bquery/bquery)](https://www.npmjs.com/package/@bquery/bquery)
14
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@bquery/bquery)](https://bundlephobia.com/package/@bquery/bquery)
15
- [![unpkg](https://img.shields.io/badge/unpkg-browse-blue?logo=unpkg)](https://unpkg.com/@bquery/bquery)
16
- [![CodeFactor](https://www.codefactor.io/repository/github/bquery/bquery/badge)](https://www.codefactor.io/repository/github/bquery/bquery)
17
- [![JsDelivr](https://data.jsdelivr.com/v1/package/npm/@bquery/bquery/badge)](https://www.jsdelivr.com/package/npm/@bquery/bquery)
18
-
19
- **The jQuery for the modern Web Platform.**
20
-
21
- bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, Web Components, and motion utilities — without a mandatory build step.
22
-
23
- ## Highlights
24
-
25
- - **Zero‑build capable**: runs directly in the browser; build tools are optional.
26
- - **Security‑focused**: DOM writes are sanitized by default; Trusted Types supported.
27
- - **Modular**: the core stays small; extra modules are opt‑in.
28
- - **TypeScript‑first**: clear types and strong IDE support.
29
- - **Tree-shakeable**: import only what you need.
30
-
31
- ## Installation
32
-
33
- ### Via npm/bun/pnpm
34
-
35
- ```bash
36
- # npm
37
- npm install @bquery/bquery
38
-
39
- # bun
40
- bun add @bquery/bquery
41
-
42
- # pnpm
43
- pnpm add @bquery/bquery
44
- ```
45
-
46
- ### Via CDN (Zero-build)
47
-
48
- #### ES Modules (recommended)
49
-
50
- ```html
51
- <script type="module">
52
- import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
53
-
54
- const count = signal(0);
55
- $('#counter').text(`Count: ${count.value}`);
56
- </script>
57
- ```
58
-
59
- #### UMD (global variable)
60
-
61
- ```html
62
- <script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
63
- <script>
64
- const { $, signal } = bQuery;
65
- const count = signal(0);
66
- </script>
67
- ```
68
-
69
- #### IIFE (self-executing)
70
-
71
- ```html
72
- <script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
73
- <script>
74
- const { $, $$ } = bQuery;
75
- $$('.items').addClass('loaded');
76
- </script>
77
- ```
78
-
79
- ### Import Strategies
80
-
81
- ```ts
82
- // Full bundle (all modules)
83
- import { $, signal, component } from '@bquery/bquery';
84
-
85
- // Core only
86
- import { $, $$ } from '@bquery/bquery/core';
87
-
88
- // Core utilities (named exports, tree-shakeable)
89
- import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
90
-
91
- // À la carte (individual modules)
92
- import { signal, computed, effect, linkedSignal, persistedSignal } from '@bquery/bquery/reactive';
93
- import { component, defineComponent, html } from '@bquery/bquery/component';
94
- import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
95
- import { sanitize } from '@bquery/bquery/security';
96
- import { storage, cache } from '@bquery/bquery/platform';
97
- import { createRouter, navigate } from '@bquery/bquery/router';
98
- import { createStore, defineStore } from '@bquery/bquery/store';
99
- import { mount, createTemplate } from '@bquery/bquery/view';
100
- ```
101
-
102
- ## Modules at a glance
103
-
104
- | Module | Description | Size (gzip) |
105
- | ------------- | -------------------------------------------------- | ----------- |
106
- | **Core** | Selectors, DOM manipulation, events, utilities | ~11.3 KB |
107
- | **Reactive** | `signal`, `computed`, `effect`, `batch` | ~0.3 KB |
108
- | **Component** | Lightweight Web Components with props | ~1.9 KB |
109
- | **Motion** | View transitions, FLIP, timelines, scroll, springs | ~4.0 KB |
110
- | **Security** | HTML sanitizing, Trusted Types, CSP | ~0.7 KB |
111
- | **Platform** | Storage, cache, notifications, buckets | ~2.2 KB |
112
- | **Router** | SPA routing, navigation guards, history API | ~2.2 KB |
113
- | **Store** | Signal-based state management, persistence | ~0.3 KB |
114
- | **View** | Declarative DOM bindings, directives | ~4.3 KB |
115
-
116
- ## Quick examples
117
-
118
- ### Core – DOM & events
119
-
120
- ```ts
121
- import { $, $$ } from '@bquery/bquery/core';
122
-
123
- // jQuery-style selectors
124
- $('#save').on('click', (event) => {
125
- console.log('Saved', event.type);
126
- });
127
-
128
- // Event delegation for dynamic content
129
- $('#list').delegate('click', '.item', (event, target) => {
130
- console.log('Item clicked', target.textContent);
131
- });
132
-
133
- // Method chaining
134
- $('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
135
-
136
- // CSS getter (computed style)
137
- const color = $('#box').css('color'); // Returns computed style value
138
-
139
- // Selector matching
140
- if ($('#el').is('.active')) {
141
- console.log('Element is active');
142
- }
143
-
144
- // DOM manipulation
145
- $('#content').wrap('div');
146
- $('#content').unwrap(); // Remove parent wrapper
147
-
148
- // Attribute helpers
149
- $('#dialog').toggleAttr('open');
150
-
151
- // Smooth scrolling
152
- $('#section').scrollTo({ behavior: 'smooth' });
153
-
154
- // Form serialization
155
- const formData = $('form').serialize(); // Returns object
156
- const queryString = $('form').serializeString(); // Returns URL-encoded string
157
-
158
- // Collections
159
- $$('.items').addClass('highlight');
160
- $$('.items').append('<li class="item">New</li>');
161
- $$('.container').find('.item').addClass('found'); // Find descendants across collection
162
- ```
163
-
164
- ### Reactive – signals
165
-
166
- ```ts
167
- import {
168
- signal,
169
- computed,
170
- effect,
171
- batch,
172
- watch,
173
- readonly,
174
- linkedSignal,
175
- } from '@bquery/bquery/reactive';
176
-
177
- const count = signal(0);
178
- const doubled = computed(() => count.value * 2);
179
-
180
- effect(() => {
181
- console.log('Count changed', count.value);
182
- });
183
-
184
- // Watch with cleanup support
185
- const stop = watch(count, (newVal, oldVal) => {
186
- console.log(`Changed from ${oldVal} to ${newVal}`);
187
- });
188
-
189
- // Read-only signal wrapper
190
- const readOnlyCount = readonly(count);
191
-
192
- // Batch updates for performance
193
- batch(() => {
194
- count.value++;
195
- count.value++;
196
- });
197
-
198
- // Dispose signal (remove all subscribers)
199
- count.dispose();
200
-
201
- // Writable computed (linked signal)
202
- const first = signal('Ada');
203
- const last = signal('Lovelace');
204
- const fullName = linkedSignal(
205
- () => `${first.value} ${last.value}`,
206
- (next) => {
207
- const [nextFirst, nextLast] = next.split(' ');
208
- first.value = nextFirst ?? '';
209
- last.value = nextLast ?? '';
210
- }
211
- );
212
-
213
- fullName.value = 'Grace Hopper';
214
- ```
215
-
216
- ### Components Web Components
217
-
218
- ```ts
219
- import { component, defineComponent, html } from '@bquery/bquery/component';
220
-
221
- component('user-card', {
222
- props: {
223
- username: { type: String, required: true },
224
- age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
225
- },
226
- // Extended lifecycle hooks
227
- beforeMount() {
228
- console.log('About to mount');
229
- },
230
- connected() {
231
- console.log('Mounted');
232
- },
233
- beforeUpdate(props) {
234
- // Return false to prevent update
235
- return props.username !== '';
236
- },
237
- onError(error) {
238
- console.error('Component error:', error);
239
- },
240
- render({ props }) {
241
- return html`<div>Hello ${props.username}</div>`;
242
- },
243
- });
244
-
245
- // Optional: create the class without auto-registration
246
- const UserCard = defineComponent('user-card', {
247
- props: { username: { type: String, required: true } },
248
- render: ({ props }) => html`<div>Hello ${props.username}</div>`,
249
- });
250
- customElements.define('user-card', UserCard);
251
- ```
252
-
253
- ### Motion – animations
254
-
255
- ```ts
256
- import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
257
-
258
- // View transitions (with fallback)
259
- await transition(() => {
260
- $('#content').text('Updated');
261
- });
262
-
263
- // Web Animations helper
264
- await animate(card, {
265
- keyframes: keyframePresets.pop(),
266
- options: { duration: 240, easing: 'ease-out' },
267
- });
268
-
269
- // Spring physics
270
- const x = spring(0, { stiffness: 120, damping: 14 });
271
- x.onChange((value) => {
272
- element.style.transform = `translateX(${value}px)`;
273
- });
274
- await x.to(100);
275
- ```
276
-
277
- ### Security – sanitizing
278
-
279
- Internally modularized (sanitize core, Trusted Types, CSP helpers) — the public API remains unchanged. For legacy deep imports, `@bquery/bquery/security/sanitize` also re-exports `generateNonce()` and `isTrustedTypesSupported()`.
280
-
281
- ```ts
282
- import { sanitize, escapeHtml } from '@bquery/bquery/security';
283
-
284
- // Sanitize HTML (removes dangerous elements like script, iframe, svg)
285
- const safeHtml = sanitize(userInput);
286
-
287
- // DOM clobbering protection (reserved IDs are blocked)
288
- const safe = sanitize('<form id="cookie">...</form>'); // id stripped
289
-
290
- // Unicode bypass protection in URLs
291
- const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
292
-
293
- // Automatic link security (adds rel="noopener noreferrer" to external/target="_blank" links)
294
- const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
295
-
296
- // srcset validation (per-URL; entire attribute removed if any entry is unsafe)
297
- const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">'); // <img>
298
-
299
- // Form action validation (blocks javascript: protocol)
300
- const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
301
-
302
- // Escape for text display
303
- const escaped = escapeHtml('<script>alert(1)</script>');
304
- ```
305
-
306
- ### Platform – storage & APIs
307
-
308
- ```ts
309
- import { storage, notifications } from '@bquery/bquery/platform';
310
-
311
- // Unified storage API
312
- const local = storage.local();
313
- await local.set('theme', 'dark');
314
- const theme = await local.get<string>('theme');
315
-
316
- // Notifications
317
- const permission = await notifications.requestPermission();
318
- if (permission === 'granted') {
319
- notifications.send('Build complete', {
320
- body: 'Your docs are ready.',
321
- });
322
- }
323
- ```
324
-
325
- ### Router – SPA navigation
326
-
327
- Internally, the router has been split into focused submodules (matching, navigation, state, links, utilities) with no public API changes.
328
-
329
- ```ts
330
- import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
331
-
332
- // Create router with routes
333
- const router = createRouter({
334
- routes: [
335
- { path: '/', name: 'home', component: HomePage },
336
- { path: '/user/:id', name: 'user', component: UserPage },
337
- { path: '*', component: NotFound },
338
- ],
339
- });
340
-
341
- // Navigation guards
342
- router.beforeEach(async (to, from) => {
343
- if (to.path === '/admin' && !isAuthenticated()) {
344
- await navigate('/login'); // Redirect
345
- return false; // Cancel original navigation
346
- }
347
- });
348
-
349
- // Navigate programmatically
350
- await navigate('/user/42');
351
- await navigate('/search?q=bquery'); // Query params in path
352
- await navigate('/login', { replace: true }); // Replace history entry
353
-
354
- // Reactive current route
355
- effect(() => {
356
- console.log('Current path:', currentRoute.value.path);
357
- });
358
- ```
359
-
360
- ### Store state management
361
-
362
- ```ts
363
- import { createStore, createPersistedStore } from '@bquery/bquery/store';
364
-
365
- // Create a store (returns the store instance directly)
366
- const counterStore = createStore({
367
- id: 'counter',
368
- state: () => ({ count: 0, name: 'Counter' }),
369
- getters: {
370
- doubled: (state) => state.count * 2,
371
- },
372
- actions: {
373
- increment() {
374
- this.count++;
375
- },
376
- async fetchCount() {
377
- this.count = await api.getCount();
378
- },
379
- },
380
- });
381
-
382
- // Use the store
383
- counterStore.increment();
384
- console.log(counterStore.doubled); // Reactive getter
385
-
386
- // Persisted store (localStorage)
387
- const settingsStore = createPersistedStore({
388
- id: 'settings',
389
- state: () => ({ theme: 'dark', language: 'en' }),
390
- });
391
-
392
- // Factory-style store definition (Pinia-style)
393
- import { defineStore, mapGetters, watchStore } from '@bquery/bquery/store';
394
-
395
- const useCounter = defineStore('counter', {
396
- state: () => ({ count: 0 }),
397
- getters: {
398
- doubled: (state) => state.count * 2,
399
- },
400
- actions: {
401
- increment() {
402
- this.count++;
403
- },
404
- },
405
- });
406
-
407
- const counter = useCounter();
408
- const getters = mapGetters(counter, ['doubled']);
409
-
410
- watchStore(
411
- counter,
412
- (state) => state.count,
413
- (value) => {
414
- console.log('Count changed:', value, getters.doubled);
415
- }
416
- );
417
- ```
418
-
419
- ### View – declarative bindings
420
-
421
- Internally modularized into focused submodules; the public API remains unchanged.
422
-
423
- ```ts
424
- import { mount, createTemplate } from '@bquery/bquery/view';
425
- import { signal } from '@bquery/bquery/reactive';
426
-
427
- // Mount reactive bindings to DOM
428
- const count = signal(0);
429
- const items = signal(['Apple', 'Banana', 'Cherry']);
430
-
431
- const app = mount('#app', {
432
- count,
433
- items,
434
- increment: () => count.value++,
435
- });
436
-
437
- // In HTML:
438
- // <p bq-text="count"></p>
439
- // <button bq-on:click="increment">+1</button>
440
- // <ul><li bq-for="item in items" bq-text="item"></li></ul>
441
- // <input bq-model="count" type="number" />
442
- // <div bq-if="count > 5">Count is high!</div>
443
- // <div bq-class="{ active: count > 0 }"></div>
444
- ```
445
-
446
- ## Browser Support
447
-
448
- | Browser | Version | Support |
449
- | ------- | ------- | ------- |
450
- | Chrome | 90+ | ✅ Full |
451
- | Firefox | 90+ | ✅ Full |
452
- | Safari | 15+ | ✅ Full |
453
- | Edge | 90+ | ✅ Full |
454
-
455
- > **No IE support** by design.
456
-
457
- ## Documentation
458
-
459
- - **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
460
- - **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
461
- - **Agents**: [docs/guide/agents.md](docs/guide/agents.md)
462
- - **Components**: [docs/guide/components.md](docs/guide/components.md)
463
- - **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
464
- - **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
465
- - **Security**: [docs/guide/security.md](docs/guide/security.md)
466
- - **Platform**: [docs/guide/platform.md](docs/guide/platform.md)
467
- - **Router**: [docs/guide/router.md](docs/guide/router.md)
468
- - **Store**: [docs/guide/store.md](docs/guide/store.md)
469
- - **View**: [docs/guide/view.md](docs/guide/view.md)
470
-
471
- ## Local Development
472
-
473
- ```bash
474
- # Install dependencies
475
- bun install
476
-
477
- # Start VitePress docs
478
- bun run dev
479
-
480
- # Run Vite playground
481
- bun run playground
482
-
483
- # Run tests
484
- bun test
485
-
486
- # Build library
487
- bun run build
488
-
489
- # Generate API documentation
490
- bun run docs:api
491
- ```
492
-
493
- ## Project Structure
494
-
495
- ```text
496
- bQuery.js
497
- ├── src/
498
- │ ├── core/ # Selectors, DOM ops, events, utils
499
- │ ├── reactive/ # Signals, computed, effects
500
- │ ├── component/ # Web Components helper
501
- │ ├── motion/ # View transitions, FLIP, springs
502
- │ ├── security/ # Sanitizer, CSP, Trusted Types
503
- │ ├── platform/ # Storage, cache, notifications
504
- │ ├── router/ # SPA routing, navigation guards
505
- │ ├── store/ # State management, persistence
506
- │ └── view/ # Declarative DOM bindings
507
- ├── docs/ # VitePress documentation
508
- ├── playground/ # Vite demo app
509
- ├── tests/ # bun:test suites
510
- └── dist/ # Built files (ESM, UMD, IIFE)
511
- ```
512
-
513
- ## Contributing
514
-
515
- See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
516
-
517
- ## AI Agent Support
518
-
519
- This project provides dedicated context files for AI coding agents:
520
-
521
- - **[AGENT.md](AGENT.md)** — Architecture, module API reference, coding conventions, common tasks
522
- - **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
523
- - **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
524
-
525
- ## License
526
-
527
- MIT – See [LICENSE.md](LICENSE.md) for details.
1
+ <p align="center">
2
+ <img src="assets/bquerry-logo.svg" alt="bQuery.js Logo" width="120" />
3
+ </p>
4
+
5
+ <h1 align="center">bQuery.js</h1>
6
+
7
+ <p align="center">
8
+
9
+ [![Repo](https://img.shields.io/badge/github-bquery%2Fbquery-24292f?logo=github)](https://github.com/bQuery/bQuery)
10
+ [![Stars](https://img.shields.io/github/stars/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/stargazers)
11
+ [![Issues](https://img.shields.io/github/issues/bquery/bquery?style=flat&logo=github)](https://github.com/bQuery/bQuery/issues)
12
+ [![License](https://img.shields.io/github/license/bquery/bquery?style=flat)](https://github.com/bQuery/bQuery/blob/main/LICENSE.md)
13
+ [![npm](https://img.shields.io/npm/v/@bquery/bquery)](https://www.npmjs.com/package/@bquery/bquery)
14
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@bquery/bquery)](https://bundlephobia.com/package/@bquery/bquery)
15
+ [![unpkg](https://img.shields.io/badge/unpkg-browse-blue?logo=unpkg)](https://unpkg.com/@bquery/bquery)
16
+ [![CodeFactor](https://www.codefactor.io/repository/github/bquery/bquery/badge)](https://www.codefactor.io/repository/github/bquery/bquery)
17
+ [![JsDelivr](https://data.jsdelivr.com/v1/package/npm/@bquery/bquery/badge)](https://www.jsdelivr.com/package/npm/@bquery/bquery)
18
+
19
+ </p>
20
+
21
+ **The jQuery for the modern Web Platform.**
22
+
23
+ bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, async data composables, Web Components, motion utilities, routing, stores, and declarative views — without a mandatory build step.
24
+
25
+ ## Highlights
26
+
27
+ - **Zero-build capable**: runs directly in the browser; build tools are optional.
28
+ - **Async data built-in**: fetch and async state composables integrate directly with signals.
29
+ - **Security-focused**: DOM writes are sanitized by default; Trusted Types supported.
30
+ - **Modular**: the core stays small; extra modules are opt-in.
31
+ - **TypeScript-first**: clear types and strong IDE support.
32
+ - **Tree-shakeable**: import only what you need.
33
+ - **Storybook-ready**: default components can be previewed and developed in Storybook with dedicated story template helpers.
34
+
35
+ ## Installation
36
+
37
+ ### Via npm/bun/pnpm
38
+
39
+ ```bash
40
+ # npm
41
+ npm install @bquery/bquery
42
+
43
+ # bun
44
+ bun add @bquery/bquery
45
+
46
+ # pnpm
47
+ pnpm add @bquery/bquery
48
+ ```
49
+
50
+ ### Via CDN (Zero-build)
51
+
52
+ #### ES Modules (recommended)
53
+
54
+ ```html
55
+ <script type="module">
56
+ import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
57
+
58
+ const count = signal(0);
59
+ $('#counter').text(`Count: ${count.value}`);
60
+ </script>
61
+ ```
62
+
63
+ #### UMD (global variable)
64
+
65
+ ```html
66
+ <script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
67
+ <script>
68
+ const { $, signal } = bQuery;
69
+ const count = signal(0);
70
+ </script>
71
+ ```
72
+
73
+ #### IIFE (self-executing)
74
+
75
+ ```html
76
+ <script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
77
+ <script>
78
+ const { $, $$ } = bQuery;
79
+ $$('.items').addClass('loaded');
80
+ </script>
81
+ ```
82
+
83
+ ## Import Strategies
84
+
85
+ ```ts
86
+ // Full bundle (all modules)
87
+ import {
88
+ $,
89
+ signal,
90
+ component,
91
+ registerDefaultComponents,
92
+ defineBqueryConfig,
93
+ } from '@bquery/bquery';
94
+
95
+ // Core only
96
+ import { $, $$ } from '@bquery/bquery/core';
97
+
98
+ // Core utilities (named exports, tree-shakeable)
99
+ import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
100
+
101
+ // Reactive only
102
+ import {
103
+ signal,
104
+ computed,
105
+ effect,
106
+ linkedSignal,
107
+ persistedSignal,
108
+ useAsyncData,
109
+ useFetch,
110
+ createUseFetch,
111
+ } from '@bquery/bquery/reactive';
112
+
113
+ // Components only
114
+ import {
115
+ bool,
116
+ component,
117
+ defineComponent,
118
+ html,
119
+ registerDefaultComponents,
120
+ } from '@bquery/bquery/component';
121
+
122
+ // Motion only
123
+ import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
124
+
125
+ // Security only
126
+ import { sanitize, sanitizeHtml, trusted } from '@bquery/bquery/security';
127
+
128
+ // Platform only
129
+ import { storage, cache, useCookie, definePageMeta, useAnnouncer } from '@bquery/bquery/platform';
130
+
131
+ // Router, Store, View
132
+ import { createRouter, navigate } from '@bquery/bquery/router';
133
+ import { createStore, defineStore } from '@bquery/bquery/store';
134
+ import { mount, createTemplate } from '@bquery/bquery/view';
135
+
136
+ // Storybook helpers
137
+ import { storyHtml, when } from '@bquery/bquery/storybook';
138
+ ```
139
+
140
+ ## Modules at a glance
141
+
142
+ | Module | Description | Size (gzip) |
143
+ | ------------- | -------------------------------------------------- | ----------- |
144
+ | **Core** | Selectors, DOM manipulation, events, utilities | ~11.3 KB |
145
+ | **Reactive** | `signal`, `computed`, `effect`, async data/fetch | ~0.3 KB |
146
+ | **Component** | Lightweight Web Components with props + defaults | ~1.9 KB |
147
+ | **Motion** | View transitions, FLIP, timelines, scroll, springs | ~4.0 KB |
148
+ | **Security** | HTML sanitizing, Trusted Types, CSP | ~0.7 KB |
149
+ | **Platform** | Storage, cache, cookies, meta, announcers, config | ~2.2 KB |
150
+ | **Router** | SPA routing, navigation guards, history API | ~2.2 KB |
151
+ | **Store** | Signal-based state management, persistence | ~0.3 KB |
152
+ | **View** | Declarative DOM bindings, directives | ~4.3 KB |
153
+
154
+ Storybook authoring helpers are also available as a dedicated entry point via `@bquery/bquery/storybook`.
155
+
156
+ ## Quick examples
157
+
158
+ ### Core – DOM & events
159
+
160
+ ```ts
161
+ import { $, $$ } from '@bquery/bquery/core';
162
+
163
+ $('#save').on('click', (event) => {
164
+ console.log('Saved', event.type);
165
+ });
166
+
167
+ $('#list').delegate('click', '.item', (event, target) => {
168
+ console.log('Item clicked', target.textContent);
169
+ });
170
+
171
+ $('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
172
+
173
+ const color = $('#box').css('color');
174
+
175
+ if ($('#el').is('.active')) {
176
+ console.log('Element is active');
177
+ }
178
+
179
+ $$('.container').find('.item').addClass('found');
180
+ ```
181
+
182
+ ### Reactive – signals
183
+
184
+ ```ts
185
+ import {
186
+ signal,
187
+ computed,
188
+ effect,
189
+ batch,
190
+ watch,
191
+ readonly,
192
+ linkedSignal,
193
+ } from '@bquery/bquery/reactive';
194
+
195
+ const count = signal(0);
196
+ const doubled = computed(() => count.value * 2);
197
+
198
+ effect(() => {
199
+ console.log('Count changed', count.value);
200
+ });
201
+
202
+ watch(count, (newVal, oldVal) => {
203
+ console.log(`Changed from ${oldVal} to ${newVal}`);
204
+ });
205
+
206
+ const readOnlyCount = readonly(count);
207
+
208
+ batch(() => {
209
+ count.value++;
210
+ count.value++;
211
+ });
212
+
213
+ count.dispose();
214
+
215
+ const first = signal('Ada');
216
+ const last = signal('Lovelace');
217
+ const fullName = linkedSignal(
218
+ () => `${first.value} ${last.value}`,
219
+ (next) => {
220
+ const [nextFirst, nextLast] = next.split(' ');
221
+ first.value = nextFirst ?? '';
222
+ last.value = nextLast ?? '';
223
+ }
224
+ );
225
+
226
+ fullName.value = 'Grace Hopper';
227
+ ```
228
+
229
+ ### Reactive – async data & fetch
230
+
231
+ ```ts
232
+ import { signal, useFetch, createUseFetch } from '@bquery/bquery/reactive';
233
+
234
+ const userId = signal(1);
235
+
236
+ const user = useFetch<{ id: number; name: string }>(() => `/users/${userId.value}`, {
237
+ baseUrl: 'https://api.example.com',
238
+ watch: [userId],
239
+ query: { include: 'profile' },
240
+ });
241
+
242
+ const useApiFetch = createUseFetch({
243
+ baseUrl: 'https://api.example.com',
244
+ headers: { 'x-client': 'bquery-readme' },
245
+ });
246
+
247
+ const settings = useApiFetch<{ theme: string }>('/settings');
248
+
249
+ console.log(user.pending.value, user.data.value, settings.error.value);
250
+ ```
251
+
252
+ ### Components – Web Components
253
+
254
+ ```ts
255
+ import {
256
+ bool,
257
+ component,
258
+ defineComponent,
259
+ html,
260
+ registerDefaultComponents,
261
+ safeHtml,
262
+ } from '@bquery/bquery/component';
263
+ import { sanitizeHtml, trusted } from '@bquery/bquery/security';
264
+
265
+ const badge = trusted(sanitizeHtml('<span class="badge">Active</span>'));
266
+
267
+ component('user-card', {
268
+ props: {
269
+ username: { type: String, required: true },
270
+ age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
271
+ },
272
+ state: { count: 0 },
273
+ beforeMount() {
274
+ console.log('About to mount');
275
+ },
276
+ connected() {
277
+ console.log('Mounted');
278
+ },
279
+ beforeUpdate(newProps, oldProps) {
280
+ return newProps.username !== oldProps.username;
281
+ },
282
+ updated(change) {
283
+ console.log('Updated because of', change?.name ?? 'state/signal change');
284
+ },
285
+ onError(error) {
286
+ console.error('Component error:', error);
287
+ },
288
+ render({ props, state }) {
289
+ return safeHtml`
290
+ <button class="user-card" ${bool('disabled', state.count > 3)}>
291
+ ${badge}
292
+ <span>Hello ${props.username}</span>
293
+ </button>
294
+ `;
295
+ },
296
+ });
297
+
298
+ const UserCard = defineComponent('user-card-manual', {
299
+ props: { username: { type: String, required: true } },
300
+ render: ({ props }) => html`<div>Hello ${props.username}</div>`,
301
+ });
302
+
303
+ customElements.define('user-card-manual', UserCard);
304
+
305
+ const tags = registerDefaultComponents({ prefix: 'ui' });
306
+ console.log(tags.button); // ui-button
307
+ ```
308
+
309
+ ### Storybook authoring helpers
310
+
311
+ ```ts
312
+ import { storyHtml, when } from '@bquery/bquery/storybook';
313
+
314
+ export const Primary = {
315
+ args: { disabled: false, label: 'Save' },
316
+ render: ({ disabled, label }) =>
317
+ storyHtml`
318
+ <ui-card>
319
+ <ui-button ?disabled=${disabled}>${label}</ui-button>
320
+ ${when(!disabled, '<small>Ready to submit</small>')}
321
+ </ui-card>
322
+ `,
323
+ };
324
+ ```
325
+
326
+ ### Motion – animations
327
+
328
+ ```ts
329
+ import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
330
+
331
+ await transition({
332
+ update: () => {
333
+ $('#content').text('Updated');
334
+ },
335
+ classes: ['page-transition'],
336
+ types: ['navigation'],
337
+ skipOnReducedMotion: true,
338
+ });
339
+
340
+ await animate(card, {
341
+ keyframes: keyframePresets.pop(),
342
+ options: { duration: 240, easing: 'ease-out' },
343
+ });
344
+
345
+ const x = spring(0, { stiffness: 120, damping: 14 });
346
+ x.onChange((value) => {
347
+ element.style.transform = `translateX(${value}px)`;
348
+ });
349
+ await x.to(100);
350
+ ```
351
+
352
+ ### Security sanitizing
353
+
354
+ ```ts
355
+ import { sanitize, escapeHtml, sanitizeHtml, trusted } from '@bquery/bquery/security';
356
+ import { safeHtml } from '@bquery/bquery/component';
357
+
358
+ const safeMarkup = sanitize(userInput);
359
+ const safe = sanitize('<form id="cookie">...</form>');
360
+ const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
361
+ const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
362
+ const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">');
363
+ const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
364
+ const escaped = escapeHtml('<script>alert(1)</script>');
365
+ const icon = trusted(sanitizeHtml('<span class="icon">♥</span>'));
366
+ const button = safeHtml`<button>${icon}<span>Save</span></button>`;
367
+ ```
368
+
369
+ ### Platform – config, cookies & accessibility
370
+
371
+ ```ts
372
+ import {
373
+ defineBqueryConfig,
374
+ useCookie,
375
+ definePageMeta,
376
+ useAnnouncer,
377
+ storage,
378
+ notifications,
379
+ } from '@bquery/bquery/platform';
380
+
381
+ defineBqueryConfig({
382
+ fetch: { baseUrl: 'https://api.example.com' },
383
+ transitions: { skipOnReducedMotion: true, classes: ['page-transition'] },
384
+ components: { prefix: 'ui' },
385
+ });
386
+
387
+ const theme = useCookie<'light' | 'dark'>('theme', { defaultValue: 'light' });
388
+ const cleanupMeta = definePageMeta({ title: 'Dashboard' });
389
+ const announcer = useAnnouncer();
390
+
391
+ theme.value = 'dark';
392
+ announcer.announce('Preferences saved');
393
+ cleanupMeta();
394
+
395
+ const local = storage.local();
396
+ await local.set('theme', theme.value);
397
+
398
+ const permission = await notifications.requestPermission();
399
+ if (permission === 'granted') {
400
+ notifications.send('Build complete', {
401
+ body: 'Your docs are ready.',
402
+ });
403
+ }
404
+ ```
405
+
406
+ ### Router – SPA navigation
407
+
408
+ ```ts
409
+ import { effect } from '@bquery/bquery/reactive';
410
+ import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
411
+
412
+ const router = createRouter({
413
+ routes: [
414
+ { path: '/', name: 'home', component: HomePage },
415
+ { path: '/user/:id', name: 'user', component: UserPage },
416
+ { path: '*', component: NotFound },
417
+ ],
418
+ });
419
+
420
+ router.beforeEach(async (to) => {
421
+ if (to.path === '/admin' && !isAuthenticated()) {
422
+ await navigate('/login');
423
+ return false;
424
+ }
425
+ });
426
+
427
+ effect(() => {
428
+ console.log('Current path:', currentRoute.value.path);
429
+ });
430
+ ```
431
+
432
+ ### Store – state management
433
+
434
+ ```ts
435
+ import {
436
+ createStore,
437
+ createPersistedStore,
438
+ defineStore,
439
+ mapGetters,
440
+ watchStore,
441
+ } from '@bquery/bquery/store';
442
+
443
+ const counterStore = createStore({
444
+ id: 'counter',
445
+ state: () => ({ count: 0, name: 'Counter' }),
446
+ getters: {
447
+ doubled: (state) => state.count * 2,
448
+ },
449
+ actions: {
450
+ increment() {
451
+ this.count++;
452
+ },
453
+ },
454
+ });
455
+
456
+ const settingsStore = createPersistedStore({
457
+ id: 'settings',
458
+ state: () => ({ theme: 'dark', language: 'en' }),
459
+ });
460
+
461
+ const useCounter = defineStore('counter', {
462
+ state: () => ({ count: 0 }),
463
+ getters: {
464
+ doubled: (state) => state.count * 2,
465
+ },
466
+ actions: {
467
+ increment() {
468
+ this.count++;
469
+ },
470
+ },
471
+ });
472
+
473
+ const counter = useCounter();
474
+ const getters = mapGetters(counter, ['doubled']);
475
+
476
+ watchStore(
477
+ counter,
478
+ (state) => state.count,
479
+ (value) => {
480
+ console.log('Count changed:', value, getters.doubled);
481
+ }
482
+ );
483
+ ```
484
+
485
+ ### View – declarative bindings
486
+
487
+ ```ts
488
+ import { mount, createTemplate } from '@bquery/bquery/view';
489
+ import { signal } from '@bquery/bquery/reactive';
490
+
491
+ const count = signal(0);
492
+ const items = signal(['Apple', 'Banana', 'Cherry']);
493
+
494
+ mount('#app', {
495
+ count,
496
+ items,
497
+ increment: () => count.value++,
498
+ });
499
+ ```
500
+
501
+ ## Browser Support
502
+
503
+ | Browser | Version | Support |
504
+ | ------- | ------- | ------- |
505
+ | Chrome | 90+ | Full |
506
+ | Firefox | 90+ | ✅ Full |
507
+ | Safari | 15+ | ✅ Full |
508
+ | Edge | 90+ | Full |
509
+
510
+ > **No IE support** by design.
511
+
512
+ ## Documentation
513
+
514
+ - **Getting Started**: [docs/guide/getting-started.md](docs/guide/getting-started.md)
515
+ - **Core API**: [docs/guide/api-core.md](docs/guide/api-core.md)
516
+ - **Agents**: [docs/guide/agents.md](docs/guide/agents.md)
517
+ - **Components**: [docs/guide/components.md](docs/guide/components.md)
518
+ - **Reactivity**: [docs/guide/reactive.md](docs/guide/reactive.md)
519
+ - **Motion**: [docs/guide/motion.md](docs/guide/motion.md)
520
+ - **Security**: [docs/guide/security.md](docs/guide/security.md)
521
+ - **Platform**: [docs/guide/platform.md](docs/guide/platform.md)
522
+ - **Router**: [docs/guide/router.md](docs/guide/router.md)
523
+ - **Store**: [docs/guide/store.md](docs/guide/store.md)
524
+ - **View**: [docs/guide/view.md](docs/guide/view.md)
525
+
526
+ ## Local Development
527
+
528
+ ```bash
529
+ # Install dependencies
530
+ bun install
531
+
532
+ # Start VitePress docs
533
+ bun run dev
534
+
535
+ # Run Storybook
536
+ bun run storybook
537
+
538
+ # Run tests
539
+ bun test
540
+
541
+ # Build library
542
+ bun run build
543
+
544
+ # Build docs
545
+ bun run build:docs
546
+
547
+ # Generate API documentation
548
+ bun run docs:api
549
+ ```
550
+
551
+ ## Project Structure
552
+
553
+ ```text
554
+ bQuery.js
555
+ ├── src/
556
+ │ ├── core/ # Selectors, DOM ops, events, utils
557
+ │ ├── reactive/ # Signals, computed, effects, async data
558
+ │ ├── component/ # Web Components helper + default library
559
+ │ ├── motion/ # View transitions, FLIP, springs
560
+ │ ├── security/ # Sanitizer, CSP, Trusted Types
561
+ │ ├── platform/ # Storage, cache, cookies, meta, config
562
+ │ ├── router/ # SPA routing, navigation guards
563
+ │ ├── store/ # State management, persistence
564
+ │ └── view/ # Declarative DOM bindings
565
+ ├── docs/ # VitePress documentation
566
+ ├── .storybook/ # Storybook config
567
+ ├── stories/ # Component stories
568
+ ├── tests/ # bun:test suites
569
+ └── dist/ # Built files (ESM, UMD, IIFE)
570
+ ```
571
+
572
+ ## Contributing
573
+
574
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
575
+
576
+ ## AI Agent Support
577
+
578
+ This project provides dedicated context files for AI coding agents:
579
+
580
+ - **[AGENT.md](AGENT.md)** — Architecture, module API reference, coding conventions, common tasks
581
+ - **[llms.txt](llms.txt)** — Compact LLM-optimized project summary
582
+ - **[.github/copilot-instructions.md](.github/copilot-instructions.md)** — GitHub Copilot context
583
+
584
+ ## License
585
+
586
+ MIT – See [LICENSE.md](LICENSE.md) for details.