fauxbase 0.4.0 → 0.5.1

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.
package/README.md CHANGED
@@ -5,7 +5,17 @@
5
5
  Fauxbase is a frontend data layer that simulates your backend during development, then connects to your real API without changing your components.
6
6
 
7
7
  ```
8
- npm install fauxbase
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
9
19
  ```
10
20
 
11
21
  ---
@@ -59,7 +69,7 @@ That's it.
59
69
  Fauxbase Fauxbase
60
70
  ↓ ↓
61
71
  Local Driver HTTP Driver
62
- (memory / localStorage)
72
+ (memory/localStorage/IndexedDB)
63
73
  Your Backend API
64
74
  ```
65
75
 
@@ -108,22 +118,52 @@ During development, it runs locally. When your backend is ready, it forwards the
108
118
  - Entity system with decorators (`@field`, `@relation`, `@computed`)
109
119
  - Service layer with lifecycle hooks (`@beforeCreate`, `@afterUpdate`, ...)
110
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`)
111
128
  - Seed data with deterministic IDs
112
- - Local driver (memory / localStorage)
113
- - HTTP driver for real backends
129
+ - Local driver (memory / localStorage / IndexedDB)
130
+ - HTTP driver for real backends (with retry, timeout, error mapping)
114
131
  - Hybrid mode for gradual migration
115
- - Backend presets (Spring Boot, NestJS, Laravel, Django, Rails, ...)
132
+ - Backend presets (Spring Boot, NestJS, Laravel, Django, Express)
133
+ - DevTools panel for inspecting data, auth, and requests
116
134
  - Zero runtime dependencies (~8KB gzipped)
117
135
 
118
136
  ---
119
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
+
120
159
  ## Hybrid Mode
121
160
 
122
- This is the killer feature. Migrate one service at a time:
161
+ Migrate one service at a time:
123
162
 
124
163
  ```ts
125
164
  const fb = createClient({
126
165
  driver: { type: 'local' },
166
+ services: { product: ProductService, order: OrderService, cart: CartService },
127
167
 
128
168
  overrides: {
129
169
  product: { driver: { type: 'http', baseUrl: '/api', preset: 'spring-boot' } },
@@ -131,7 +171,87 @@ const fb = createClient({
131
171
  });
132
172
  ```
133
173
 
134
- Products use the real API. Everything else stays local. Migrate at your own pace.
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` |
135
255
 
136
256
  ---
137
257
 
@@ -224,6 +344,460 @@ Every service gets: `list`, `get`, `create`, `update`, `delete`, `count`, `bulk.
224
344
 
225
345
  ---
226
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
+
227
801
  ## Seeding
228
802
 
229
803
  Seed data has deterministic IDs. Runtime data has UUIDs. They never collide.
@@ -242,26 +816,6 @@ const productSeed = seed(Product, [
242
816
 
243
817
  ---
244
818
 
245
- ## Backend Presets
246
-
247
- Works with any REST backend. Presets tell the HTTP driver how to serialize queries and parse responses.
248
-
249
- | Preset | Framework | Filter Style |
250
- |--------|-----------|-------------|
251
- | `lightwind` | Lightwind (Quarkus) | `?price__gte=100` |
252
- | `spring-boot` | Spring Boot | `?price.gte=100` |
253
- | `nestjs` | NestJS | `?filter.price.$gte=100` |
254
- | `laravel` | Laravel | `?filter[price_gte]=100` |
255
- | `django` | Django REST Framework | `?price__gte=100` |
256
- | `express` | Express.js | `?price__gte=100` |
257
- | `fastapi` | FastAPI | `?price__gte=100` |
258
- | `rails` | Ruby on Rails | `?q[price_gteq]=100` |
259
- | `go-gin` | Go (Gin) | `?price__gte=100` |
260
-
261
- Custom presets supported via `definePreset()`.
262
-
263
- ---
264
-
265
819
  ## Migration Timeline
266
820
 
267
821
  ```
@@ -293,9 +847,10 @@ Week 6 "All APIs ready"
293
847
  ## Roadmap
294
848
 
295
849
  - [x] **v0.1** — Core: Entity, Service, QueryEngine, LocalDriver, Seeds
296
- - [ ] **v0.2** — React hooks (`useList`, `useGet`, `useMutation`) + Auth simulation
297
- - [ ] **v0.3** — HTTP Driver + Backend Presets + DevTools
298
- - [ ] **v0.4** — IndexedDB, CLI (`npx fauxbase init`), Vue/Svelte adapters
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
299
854
 
300
855
  ---
301
856