fauxbase-react 0.5.0 → 0.5.2

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 (2) hide show
  1. package/README.md +859 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,859 @@
1
+ # Fauxbase
2
+
3
+ **Start with fake. Ship with real. Change nothing.**
4
+
5
+ Fauxbase is a frontend data layer that simulates your backend during development, then connects to your real API without changing your components.
6
+
7
+ ```
8
+ npm install fauxbase # core
9
+ npm install fauxbase-react # react hooks (optional)
10
+ npm install fauxbase-vue # vue composables (optional)
11
+ npm install fauxbase-svelte # svelte stores (optional)
12
+ npm install fauxbase-devtools # devtools panel (optional)
13
+ ```
14
+
15
+ Or scaffold a new project instantly:
16
+
17
+ ```
18
+ npx fauxbase-cli init
19
+ ```
20
+
21
+ ---
22
+
23
+ ## 15-Second Example
24
+
25
+ Define your data:
26
+
27
+ ```ts
28
+ class Product extends Entity {
29
+ @field({ required: true }) name!: string;
30
+ @field({ min: 0 }) price!: number;
31
+ @field({ default: 0 }) stock!: number;
32
+ }
33
+ ```
34
+
35
+ Use it:
36
+
37
+ ```ts
38
+ const fb = createClient({
39
+ services: { product: ProductService },
40
+ seeds: [seed(Product, [
41
+ { name: 'Hair Clay', price: 185000, stock: 50 },
42
+ { name: 'Beard Oil', price: 125000, stock: 30 },
43
+ ])],
44
+ });
45
+
46
+ // Filter, sort, paginate — all work locally
47
+ const result = await fb.product.list({
48
+ filter: { price__gte: 100000 },
49
+ sort: { field: 'price', direction: 'desc' },
50
+ page: 1, size: 20,
51
+ });
52
+ ```
53
+
54
+ That's it.
55
+
56
+ - Works locally with fake data
57
+ - Switch to real backend later
58
+ - No component changes
59
+
60
+ ---
61
+
62
+ ## How It Works
63
+
64
+ ```
65
+ Development Production
66
+
67
+ Your App Your App
68
+ ↓ ↓
69
+ Fauxbase Fauxbase
70
+ ↓ ↓
71
+ Local Driver HTTP Driver
72
+ (memory/localStorage/IndexedDB) ↓
73
+ Your Backend API
74
+ ```
75
+
76
+ Components always talk to Fauxbase. Fauxbase talks to a driver. The driver is swappable. Your components never know whether they're hitting localStorage or a REST API.
77
+
78
+ ---
79
+
80
+ ## The Problem
81
+
82
+ Every frontend project does this:
83
+
84
+ ```
85
+ Step 1: Backend not ready → hardcode mock data
86
+ Step 2: Mock data grows → copy-paste everywhere
87
+ Step 3: Need filtering → hack together Array.filter()
88
+ Step 4: Need pagination → hack together Array.slice()
89
+ Step 5: Need auth → fake login with useState
90
+ Step 6: Backend arrives → rewrite everything
91
+ ```
92
+
93
+ Existing tools don't solve this:
94
+
95
+ | Tool | What it does | The gap |
96
+ |------|-------------|---------|
97
+ | **MSW** | Intercepts HTTP | No query engine, no auth, mocks transport not data |
98
+ | **json-server** | Fake REST from JSON | No query operators, no hooks, separate process |
99
+ | **MirageJS** | In-browser server | Limited operators, no typed entities, largely abandoned |
100
+ | **Zustand/Redux** | State management | No CRUD contract, no query engine, no migration path |
101
+
102
+ **Fauxbase removes the rewrite.**
103
+
104
+ ---
105
+
106
+ ## The Key Idea
107
+
108
+ Fauxbase runs your backend contract in the browser.
109
+
110
+ Entities define your data model. Services define business logic. A query engine handles filtering, sorting, and pagination.
111
+
112
+ During development, it runs locally. When your backend is ready, it forwards the same calls to your API.
113
+
114
+ ---
115
+
116
+ ## Core Features
117
+
118
+ - Entity system with decorators (`@field`, `@relation`, `@computed`)
119
+ - Service layer with lifecycle hooks (`@beforeCreate`, `@afterUpdate`, ...)
120
+ - 13 query operators (`eq`, `gte`, `contains`, `between`, `in`, ...)
121
+ - Auth simulation (`AuthService`, login/register/logout, role checks)
122
+ - Auto-injection of `createdById`/`updatedById` when authenticated
123
+ - React hooks (`useList`, `useGet`, `useMutation`, `useAuth`, `useEvent`)
124
+ - Vue 3 composables (same API as React hooks)
125
+ - Svelte stores (same API, uses `writable`/`readable`)
126
+ - Real-time events (EventBus, SSE, STOMP/WebSocket)
127
+ - CLI scaffolder (`npx fauxbase-cli init`)
128
+ - Seed data with deterministic IDs
129
+ - Local driver (memory / localStorage / IndexedDB)
130
+ - HTTP driver for real backends (with retry, timeout, error mapping)
131
+ - Hybrid mode for gradual migration
132
+ - Backend presets (Spring Boot, NestJS, Laravel, Django, Express)
133
+ - DevTools panel for inspecting data, auth, and requests
134
+ - Zero runtime dependencies (~8KB gzipped)
135
+
136
+ ---
137
+
138
+ ## Switching to Real Backend
139
+
140
+ When your API is ready, change one line:
141
+
142
+ ```ts
143
+ // Before: local driver (default)
144
+ const fb = createClient({
145
+ services: { product: ProductService },
146
+ });
147
+
148
+ // After: HTTP driver
149
+ const fb = createClient({
150
+ driver: { type: 'http', baseUrl: '/api', preset: 'spring-boot' },
151
+ services: { product: ProductService },
152
+ });
153
+ ```
154
+
155
+ Every component stays the same. Every query operator maps to your backend's filter syntax.
156
+
157
+ ---
158
+
159
+ ## Hybrid Mode
160
+
161
+ Migrate one service at a time:
162
+
163
+ ```ts
164
+ const fb = createClient({
165
+ driver: { type: 'local' },
166
+ services: { product: ProductService, order: OrderService, cart: CartService },
167
+
168
+ overrides: {
169
+ product: { driver: { type: 'http', baseUrl: '/api', preset: 'spring-boot' } },
170
+ },
171
+ });
172
+ ```
173
+
174
+ Products use the real API. Orders and cart stay local. Migrate at your own pace.
175
+
176
+ ---
177
+
178
+ ## Backend Presets
179
+
180
+ Presets tell the HTTP driver how to serialize queries and parse responses for your backend framework.
181
+
182
+ | Preset | Framework | Filter Style | Page Indexing |
183
+ |--------|-----------|-------------|---------------|
184
+ | `default` | Generic REST | `?price__gte=100` | 1-indexed |
185
+ | `spring-boot` | Spring Boot | `?price.gte=100` | 0-indexed |
186
+ | `nestjs` | NestJS | `?filter.price.$gte=100` | 1-indexed |
187
+ | `laravel` | Laravel | `?filter[price_gte]=100` | 1-indexed |
188
+ | `django` | Django REST | `?price__gte=100` | 1-indexed |
189
+ | `express` | Express.js | `?price__gte=100` | 1-indexed |
190
+
191
+ ### Custom Presets
192
+
193
+ ```ts
194
+ import { definePreset } from 'fauxbase';
195
+
196
+ const myPreset = definePreset({
197
+ name: 'my-backend',
198
+ response: {
199
+ single: (raw) => ({ data: raw.result }),
200
+ list: (raw) => ({ items: raw.results, meta: raw.pagination }),
201
+ error: (raw) => ({ error: raw.message, code: raw.code }),
202
+ },
203
+ meta: { page: 'page', size: 'limit', totalItems: 'total', totalPages: 'pages' },
204
+ query: {
205
+ filterStyle: 'django',
206
+ pageParam: 'page',
207
+ sizeParam: 'limit',
208
+ sortParam: 'order_by',
209
+ sortFormat: 'field,direction',
210
+ },
211
+ auth: {
212
+ loginUrl: '/auth/login',
213
+ registerUrl: '/auth/register',
214
+ tokenField: 'access_token',
215
+ userField: 'user',
216
+ headerFormat: 'Bearer {token}',
217
+ },
218
+ });
219
+
220
+ const fb = createClient({
221
+ driver: { type: 'http', baseUrl: '/api', preset: myPreset },
222
+ services: { product: ProductService },
223
+ });
224
+ ```
225
+
226
+ ---
227
+
228
+ ## HTTP Driver Options
229
+
230
+ ```ts
231
+ const fb = createClient({
232
+ driver: {
233
+ type: 'http',
234
+ baseUrl: 'https://api.example.com',
235
+ preset: 'spring-boot',
236
+ timeout: 10000, // 10s (default: 30s)
237
+ retry: { maxRetries: 3, baseDelay: 300 }, // exponential backoff for 5xx
238
+ headers: { 'X-API-Key': 'my-key' }, // custom headers on every request
239
+ },
240
+ services: { product: ProductService },
241
+ });
242
+ ```
243
+
244
+ Error mapping:
245
+
246
+ | HTTP Status | Fauxbase Error |
247
+ |-------------|---------------|
248
+ | 400, 422 | `ValidationError` |
249
+ | 401, 403 | `ForbiddenError` |
250
+ | 404 | `NotFoundError` |
251
+ | 409 | `ConflictError` |
252
+ | 5xx | `HttpError` (with retry) |
253
+ | Network failure | `NetworkError` |
254
+ | Timeout | `TimeoutError` |
255
+
256
+ ---
257
+
258
+ ## AI Prototypes → Production
259
+
260
+ Many AI-generated prototypes hardcode arrays:
261
+
262
+ ```ts
263
+ const products = [
264
+ { name: 'Hair Clay', price: 185000 },
265
+ { name: 'Beard Oil', price: 125000 },
266
+ ];
267
+ ```
268
+
269
+ When the prototype becomes real, engineers must rewrite the data layer.
270
+
271
+ Fauxbase lets prototypes start with a real data contract:
272
+
273
+ ```
274
+ Claude / Cursor prototype
275
+
276
+ Fauxbase local driver (works immediately)
277
+
278
+ Real backend later (no rewrite)
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Query Engine — 13 Operators
284
+
285
+ Every operator works identically on local and HTTP drivers.
286
+
287
+ ```ts
288
+ const result = await fb.product.list({
289
+ filter: {
290
+ price__gte: 100000,
291
+ name__contains: 'pomade',
292
+ categoryId__in: ['cat-1', 'cat-2'],
293
+ stock__between: [10, 100],
294
+ isActive: true,
295
+ },
296
+ sort: { field: 'price', direction: 'desc' },
297
+ page: 1,
298
+ size: 20,
299
+ });
300
+ ```
301
+
302
+ | Operator | Syntax | Example |
303
+ |----------|--------|---------|
304
+ | `eq` | `field` or `field__eq` | `{ isActive: true }` |
305
+ | `ne` | `field__ne` | `{ status__ne: 'deleted' }` |
306
+ | `gt` | `field__gt` | `{ price__gt: 100 }` |
307
+ | `gte` | `field__gte` | `{ price__gte: 100 }` |
308
+ | `lt` | `field__lt` | `{ stock__lt: 10 }` |
309
+ | `lte` | `field__lte` | `{ stock__lte: 100 }` |
310
+ | `like` | `field__like` | `{ name__like: 'hair' }` |
311
+ | `contains` | `field__contains` | `{ name__contains: 'hair' }` |
312
+ | `startswith` | `field__startswith` | `{ name__startswith: 'ha' }` |
313
+ | `endswith` | `field__endswith` | `{ email__endswith: '@gmail.com' }` |
314
+ | `between` | `field__between` | `{ price__between: [100, 500] }` |
315
+ | `in` | `field__in` | `{ status__in: ['active', 'pending'] }` |
316
+ | `isnull` | `field__isnull` | `{ deletedAt__isnull: true }` |
317
+
318
+ ---
319
+
320
+ ## Services & Hooks
321
+
322
+ ```ts
323
+ class ProductService extends Service<Product> {
324
+ entity = Product;
325
+ endpoint = '/products';
326
+
327
+ @beforeCreate()
328
+ ensureUniqueName(data: Partial<Product>, existing: Product[]) {
329
+ if (existing.some(p => p.name === data.name)) {
330
+ throw new ConflictError(`Product "${data.name}" already exists`);
331
+ }
332
+ }
333
+
334
+ async getByCategory(categoryId: string) {
335
+ return this.list({
336
+ filter: { categoryId, isActive: true },
337
+ sort: { field: 'name', direction: 'asc' },
338
+ });
339
+ }
340
+ }
341
+ ```
342
+
343
+ Every service gets: `list`, `get`, `create`, `update`, `delete`, `count`, `bulk.create`, `bulk.update`, `bulk.delete`.
344
+
345
+ ---
346
+
347
+ ## Auth
348
+
349
+ Simulate login, registration, and role-based access during development. When your real auth backend is ready, the same API works over HTTP.
350
+
351
+ ```ts
352
+ class User extends Entity {
353
+ @field({ required: true }) name!: string;
354
+ @field({ required: true }) email!: string;
355
+ @field({ required: true }) password!: string;
356
+ @field({ default: 'user' }) role!: string;
357
+ }
358
+
359
+ class UserAuth extends AuthService<User> {
360
+ entity = User;
361
+ endpoint = '/users';
362
+ }
363
+
364
+ const fb = createClient({
365
+ services: { product: ProductService },
366
+ auth: UserAuth,
367
+ });
368
+
369
+ // Register & login
370
+ await fb.auth.register({ name: 'Alice', email: 'alice@test.com', password: 'secret' });
371
+ await fb.auth.login({ email: 'alice@test.com', password: 'secret' });
372
+
373
+ // Check state
374
+ fb.auth.isLoggedIn; // true
375
+ fb.auth.currentUser; // { id, email }
376
+ fb.auth.hasRole('admin'); // false
377
+ fb.auth.token; // mock JWT (base64)
378
+
379
+ // Auto-injection — createdById/updatedById set automatically
380
+ const { data } = await fb.product.create({ name: 'Pomade', price: 150000 });
381
+ data.createdById; // → user's ID
382
+ data.createdByName; // → 'Alice'
383
+
384
+ fb.auth.logout();
385
+ ```
386
+
387
+ With HTTP driver, `login()` and `register()` POST to the preset's auth endpoints. The token from the server response is injected into all subsequent requests as `Authorization: Bearer <token>`.
388
+
389
+ ---
390
+
391
+ ## React Hooks
392
+
393
+ `fauxbase-react` provides hooks that connect your React components to Fauxbase services.
394
+
395
+ ```
396
+ npm install fauxbase-react
397
+ ```
398
+
399
+ ### Setup
400
+
401
+ ```tsx
402
+ import { FauxbaseProvider } from 'fauxbase-react';
403
+
404
+ function App() {
405
+ return (
406
+ <FauxbaseProvider client={fb}>
407
+ <ProductList />
408
+ </FauxbaseProvider>
409
+ );
410
+ }
411
+ ```
412
+
413
+ ### useList — fetch collections
414
+
415
+ ```tsx
416
+ import { useList } from 'fauxbase-react';
417
+
418
+ function ProductList() {
419
+ const { items, loading, error, meta, refetch } = useList(fb.product, {
420
+ filter: { isActive: true },
421
+ sort: { field: 'price', direction: 'desc' },
422
+ page: 1,
423
+ size: 20,
424
+ });
425
+
426
+ if (loading) return <p>Loading...</p>;
427
+ return items.map(p => <div key={p.id}>{p.name}</div>);
428
+ }
429
+ ```
430
+
431
+ Options: `enabled` (skip fetch), `refetchInterval` (polling in ms).
432
+
433
+ ### useGet — fetch single record
434
+
435
+ ```tsx
436
+ import { useGet } from 'fauxbase-react';
437
+
438
+ function ProductDetail({ id }: { id: string }) {
439
+ const { data, loading, error } = useGet(fb.product, id);
440
+ if (loading) return <p>Loading...</p>;
441
+ return <h1>{data?.name}</h1>;
442
+ }
443
+ ```
444
+
445
+ Pass `null` as id to skip fetching.
446
+
447
+ ### useMutation — create, update, delete
448
+
449
+ ```tsx
450
+ import { useMutation } from 'fauxbase-react';
451
+
452
+ function CreateProduct() {
453
+ const { create, loading, error } = useMutation(fb.product);
454
+
455
+ const handleSubmit = async () => {
456
+ await create({ name: 'New Product', price: 100000 });
457
+ // useList hooks on the same service auto-refetch
458
+ };
459
+ }
460
+ ```
461
+
462
+ Returns `{ create, update, remove, loading, error }`. Mutations automatically invalidate all `useList` subscribers on the same service.
463
+
464
+ ### useAuth — auth state in React
465
+
466
+ ```tsx
467
+ import { useAuth } from 'fauxbase-react';
468
+
469
+ function LoginPage() {
470
+ const { user, isLoggedIn, login, logout, register, hasRole, loading } = useAuth();
471
+
472
+ const handleLogin = async () => {
473
+ await login({ email: 'alice@test.com', password: 'secret' });
474
+ };
475
+
476
+ if (isLoggedIn) return <p>Welcome, {user.email}</p>;
477
+ return <button onClick={handleLogin}>Login</button>;
478
+ }
479
+ ```
480
+
481
+ ### useFauxbase — raw client access
482
+
483
+ ```tsx
484
+ import { useFauxbase } from 'fauxbase-react';
485
+
486
+ function Dashboard() {
487
+ const fb = useFauxbase();
488
+ // fb.product, fb.auth, etc.
489
+ }
490
+ ```
491
+
492
+ ---
493
+
494
+ ## DevTools
495
+
496
+ `fauxbase-devtools` provides a floating panel for inspecting your Fauxbase instance during development.
497
+
498
+ ```
499
+ npm install fauxbase-devtools
500
+ ```
501
+
502
+ ```tsx
503
+ import { FauxbaseDevtools } from 'fauxbase-devtools';
504
+
505
+ function App() {
506
+ return (
507
+ <FauxbaseProvider client={fb}>
508
+ <ProductList />
509
+ {process.env.NODE_ENV === 'development' && (
510
+ <FauxbaseDevtools client={fb} />
511
+ )}
512
+ </FauxbaseProvider>
513
+ );
514
+ }
515
+ ```
516
+
517
+ ### Panels
518
+
519
+ | Panel | What it shows |
520
+ |-------|--------------|
521
+ | **Data** | Browse records per service |
522
+ | **Auth** | Current auth state + logout button |
523
+ | **Requests** | Chronological log of all service method calls with timing |
524
+ | **Seeds** | Reset seed data per resource (LocalDriver only) |
525
+
526
+ ### Configuration
527
+
528
+ ```tsx
529
+ <FauxbaseDevtools
530
+ client={fb}
531
+ config={{
532
+ position: 'bottom-left', // default: 'bottom-right'
533
+ defaultOpen: true, // default: false
534
+ maxLogEntries: 200, // default: 100
535
+ }}
536
+ />
537
+ ```
538
+
539
+ The request logger uses `Proxy` to wrap service methods — zero changes to the Service class, zero overhead when devtools is not rendered.
540
+
541
+ ---
542
+
543
+ ## IndexedDB Storage
544
+
545
+ For persistent local data that survives page refreshes without needing `localStorage` size limits:
546
+
547
+ ```ts
548
+ const fb = createClient({
549
+ driver: { type: 'local', persist: 'indexeddb' },
550
+ services: { product: ProductService },
551
+ });
552
+ await fb.ready; // Wait for IndexedDB to load
553
+ ```
554
+
555
+ Options:
556
+
557
+ | Option | Default | Description |
558
+ |--------|---------|-------------|
559
+ | `persist` | `'memory'` | `'memory'`, `'localStorage'`, or `'indexeddb'` |
560
+ | `dbName` | `'fauxbase'` | Custom IndexedDB database name |
561
+
562
+ The IndexedDB backend uses a memory-cached, write-through strategy: all data is loaded into memory on init (behind `fb.ready`), then writes are synchronously reflected in memory and asynchronously persisted to IndexedDB. This means reads are always fast and the synchronous `StorageBackend` interface is preserved.
563
+
564
+ For memory and localStorage drivers, `fb.ready` resolves immediately.
565
+
566
+ ---
567
+
568
+ ## Vue Composables
569
+
570
+ `fauxbase-vue` provides Vue 3 Composition API equivalents of the React hooks.
571
+
572
+ ```
573
+ npm install fauxbase-vue
574
+ ```
575
+
576
+ ### Setup
577
+
578
+ ```ts
579
+ // main.ts
580
+ import { FauxbasePlugin } from 'fauxbase-vue';
581
+ import { fb } from './fauxbase';
582
+
583
+ const app = createApp(App);
584
+ app.use(FauxbasePlugin, { client: fb });
585
+ ```
586
+
587
+ ### Usage
588
+
589
+ ```vue
590
+ <script setup>
591
+ import { useList, useMutation, useAuth } from 'fauxbase-vue';
592
+
593
+ const { items, loading, error, meta, refetch } = useList(fb.product, {
594
+ filter: { isActive: true },
595
+ sort: { field: 'price', direction: 'desc' },
596
+ });
597
+
598
+ const { create, update, remove } = useMutation(fb.product);
599
+
600
+ // items, loading, error, meta are Vue Ref<> values
601
+ </script>
602
+ ```
603
+
604
+ All hooks: `useList`, `useGet`, `useMutation`, `useAuth`, `useFauxbase`.
605
+
606
+ Returns are `Ref<>` values — use `.value` in script, automatic unwrapping in templates. Mutations auto-invalidate `useList` subscribers.
607
+
608
+ ---
609
+
610
+ ## Svelte Stores
611
+
612
+ `fauxbase-svelte` provides Svelte store-based equivalents. Compatible with Svelte 4 and 5.
613
+
614
+ ```
615
+ npm install fauxbase-svelte
616
+ ```
617
+
618
+ ### Setup
619
+
620
+ ```svelte
621
+ <!-- +layout.svelte -->
622
+ <script>
623
+ import { setFauxbaseContext } from 'fauxbase-svelte';
624
+ import { fb } from './fauxbase';
625
+
626
+ setFauxbaseContext(fb);
627
+ </script>
628
+
629
+ <slot />
630
+ ```
631
+
632
+ ### Usage
633
+
634
+ ```svelte
635
+ <script>
636
+ import { useList, useMutation } from 'fauxbase-svelte';
637
+
638
+ const { items, loading, error } = useList(fb.product, {
639
+ filter: { isActive: true },
640
+ });
641
+
642
+ const { create } = useMutation(fb.product);
643
+ </script>
644
+
645
+ {#if $loading}
646
+ <p>Loading...</p>
647
+ {:else}
648
+ {#each $items as product}
649
+ <div>{product.name}</div>
650
+ {/each}
651
+ {/if}
652
+ ```
653
+
654
+ All hooks: `useList`, `useGet`, `useMutation`, `useAuth`, `useFauxbase`.
655
+
656
+ Returns are Svelte `Readable<>` stores — use `$store` syntax in templates. Mutations auto-invalidate `useList` subscribers.
657
+
658
+ ---
659
+
660
+ ## Real-Time Events
661
+
662
+ Fauxbase includes an opt-in event system. Local mutations auto-emit events. For server-pushed events, connect via SSE or STOMP (WebSocket).
663
+
664
+ ### Enable local events
665
+
666
+ ```ts
667
+ const fb = createClient({
668
+ services: { todo: TodoService },
669
+ events: true,
670
+ });
671
+
672
+ // Listen to events
673
+ fb._eventBus.on('todo', (event) => {
674
+ console.log(event.action, event.data); // 'created', { id, ... }
675
+ });
676
+
677
+ // Or listen to all events
678
+ fb._eventBus.onAny((event) => {
679
+ console.log(event.resource, event.action);
680
+ });
681
+ ```
682
+
683
+ ### SSE (Server-Sent Events)
684
+
685
+ ```ts
686
+ const fb = createClient({
687
+ driver: { type: 'http', baseUrl: '/api' },
688
+ services: { todo: TodoService },
689
+ events: {
690
+ source: {
691
+ type: 'sse',
692
+ url: '/api/events',
693
+ eventMap: { 'todo-changed': 'todo' },
694
+ },
695
+ },
696
+ });
697
+ ```
698
+
699
+ ### STOMP (WebSocket)
700
+
701
+ Requires `@stomp/stompjs` as a peer dependency.
702
+
703
+ ```ts
704
+ const fb = createClient({
705
+ driver: { type: 'http', baseUrl: '/api' },
706
+ services: { todo: TodoService },
707
+ events: {
708
+ source: {
709
+ type: 'stomp',
710
+ brokerUrl: 'wss://api.example.com/ws',
711
+ subscriptions: { '/topic/todos': 'todo' },
712
+ },
713
+ },
714
+ });
715
+ ```
716
+
717
+ ### Custom handlers
718
+
719
+ ```ts
720
+ const fb = createClient({
721
+ services: { todo: TodoService },
722
+ events: {
723
+ handlers: {
724
+ todo: (event) => console.log('Todo changed:', event),
725
+ },
726
+ },
727
+ });
728
+ ```
729
+
730
+ ### useEvent hook (React / Vue / Svelte)
731
+
732
+ ```tsx
733
+ import { useEvent } from 'fauxbase-react'; // or fauxbase-vue, fauxbase-svelte
734
+
735
+ function TodoList() {
736
+ useEvent('todo', (event) => {
737
+ toast(`Todo ${event.action}!`);
738
+ });
739
+
740
+ // Also accepts a service instance
741
+ useEvent(fb.todo, (event) => { ... });
742
+ }
743
+ ```
744
+
745
+ ### Auto-invalidation
746
+
747
+ Remote events (from SSE/STOMP) automatically trigger refetches in `useList`/`useGet` hooks. No manual wiring needed.
748
+
749
+ ### Event type
750
+
751
+ ```ts
752
+ interface FauxbaseEvent<T = any> {
753
+ action: 'created' | 'updated' | 'deleted' | 'bulkCreated' | 'bulkUpdated' | 'bulkDeleted';
754
+ resource: string;
755
+ data?: T;
756
+ id?: string;
757
+ ids?: string[];
758
+ timestamp: number;
759
+ source: 'local' | 'remote';
760
+ }
761
+ ```
762
+
763
+ ### Cleanup
764
+
765
+ ```ts
766
+ fb.disconnect(); // Closes SSE/STOMP connection and clears all listeners
767
+ ```
768
+
769
+ ---
770
+
771
+ ## CLI
772
+
773
+ `fauxbase-cli` scaffolds a new Fauxbase project with entities, services, seeds, and framework-specific setup.
774
+
775
+ ```
776
+ npx fauxbase-cli init
777
+ ```
778
+
779
+ ### Interactive prompts
780
+
781
+ 1. **Framework?** — React / Vue / Svelte / None
782
+ 2. **Output directory?** — default `src/fauxbase/`
783
+ 3. **Sample entity (Todo)?** — Y/N
784
+ 4. **Authentication?** — Y/N
785
+ 5. **Storage?** — memory / localStorage / IndexedDB
786
+
787
+ ### Generated structure
788
+
789
+ ```
790
+ src/fauxbase/
791
+ entities/todo.ts, user.ts
792
+ services/todo.ts, user.ts
793
+ seeds/todo.ts, user.ts
794
+ index.ts # createClient call
795
+ ```
796
+
797
+ After scaffolding, the CLI prints the `npm install` command and framework-specific setup instructions.
798
+
799
+ ---
800
+
801
+ ## Seeding
802
+
803
+ Seed data has deterministic IDs. Runtime data has UUIDs. They never collide.
804
+
805
+ ```ts
806
+ const productSeed = seed(Product, [
807
+ { name: 'Hair Clay', price: 185000, stock: 50 }, // → seed:product:0
808
+ { name: 'Beard Oil', price: 125000, stock: 30 }, // → seed:product:1
809
+ ]);
810
+ ```
811
+
812
+ - Seeds auto-apply on first load
813
+ - Fauxbase tracks a version hash — if seeds change, only seed records are re-applied
814
+ - Runtime records are never touched during re-seeding
815
+ - On HTTP driver, seeding is disabled — the backend owns the data
816
+
817
+ ---
818
+
819
+ ## Migration Timeline
820
+
821
+ ```
822
+ Week 1 Install Fauxbase, define entities/services/seeds.
823
+ Build UI with local driver. Everything works.
824
+
825
+ Week 2-4 Build features at full speed.
826
+ No blocking on backend. No mock data spaghetti.
827
+
828
+ Week 5 "Products API is ready"
829
+ → Switch products to HTTP driver (hybrid mode)
830
+ → Zero component changes
831
+
832
+ Week 6 "All APIs ready"
833
+ → Set VITE_API_URL → done
834
+ ```
835
+
836
+ ---
837
+
838
+ ## Who This Is For
839
+
840
+ - Frontend teams waiting for backend APIs
841
+ - Solo devs building full-stack apps
842
+ - Prototypers using AI coding tools
843
+ - Teams building UI before backend is ready
844
+
845
+ ---
846
+
847
+ ## Roadmap
848
+
849
+ - [x] **v0.1** — Core: Entity, Service, QueryEngine, LocalDriver, Seeds
850
+ - [x] **v0.2** — React hooks (`useList`, `useGet`, `useMutation`, `useAuth`) + Auth simulation
851
+ - [x] **v0.3** — HTTP Driver + Backend Presets + Hybrid Mode + DevTools
852
+ - [x] **v0.4** — IndexedDB + CLI (`npx fauxbase-cli init`) + Vue/Svelte adapters
853
+ - [x] **v0.5** — Real-Time Events (EventBus, SSE, STOMP) + `useEvent` hook + auto-invalidation
854
+
855
+ ---
856
+
857
+ ## License
858
+
859
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fauxbase-react",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -12,7 +12,7 @@
12
12
  "require": "./dist/index.cjs"
13
13
  }
14
14
  },
15
- "files": ["dist"],
15
+ "files": ["dist", "README.md"],
16
16
  "scripts": {
17
17
  "build": "tsup",
18
18
  "test": "vitest run",