@fairfox/polly 0.10.1 → 0.12.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 (41) hide show
  1. package/README.md +186 -0
  2. package/dist/src/client/index.d.ts +33 -0
  3. package/dist/src/client/index.js +586 -0
  4. package/dist/src/client/index.js.map +13 -0
  5. package/dist/src/client/wrapper.d.ts +54 -0
  6. package/dist/src/core/clock.d.ts +63 -0
  7. package/dist/src/elysia/index.d.ts +43 -0
  8. package/dist/src/elysia/index.js +241 -0
  9. package/dist/src/elysia/index.js.map +12 -0
  10. package/dist/src/elysia/plugin.d.ts +5 -0
  11. package/dist/src/elysia/tla-generator.d.ts +16 -0
  12. package/dist/src/elysia/types.d.ts +137 -0
  13. package/dist/src/utils/function-serialization.d.ts +14 -0
  14. package/dist/tools/analysis/src/extract/adr.d.ts +37 -0
  15. package/dist/tools/analysis/src/extract/architecture.d.ts +42 -0
  16. package/dist/tools/analysis/src/extract/contexts.d.ts +74 -0
  17. package/dist/tools/analysis/src/extract/flows.d.ts +68 -0
  18. package/dist/tools/analysis/src/extract/handlers.d.ts +330 -0
  19. package/dist/tools/analysis/src/extract/index.d.ts +9 -0
  20. package/dist/tools/analysis/src/extract/integrations.d.ts +77 -0
  21. package/dist/tools/analysis/src/extract/manifest.d.ts +64 -0
  22. package/dist/tools/analysis/src/extract/project-detector.d.ts +103 -0
  23. package/dist/tools/analysis/src/extract/relationships.d.ts +119 -0
  24. package/dist/tools/analysis/src/extract/types.d.ts +139 -0
  25. package/dist/tools/analysis/src/index.d.ts +2 -0
  26. package/dist/tools/analysis/src/types/adr.d.ts +39 -0
  27. package/dist/tools/analysis/src/types/architecture.d.ts +198 -0
  28. package/dist/tools/analysis/src/types/core.d.ts +178 -0
  29. package/dist/tools/analysis/src/types/index.d.ts +4 -0
  30. package/dist/tools/teach/src/cli.js +376 -81
  31. package/dist/tools/teach/src/cli.js.map +13 -13
  32. package/dist/tools/teach/src/index.d.ts +28 -0
  33. package/dist/tools/teach/src/index.js +233 -84
  34. package/dist/tools/teach/src/index.js.map +13 -13
  35. package/dist/tools/verify/src/cli.js +127 -26
  36. package/dist/tools/verify/src/cli.js.map +7 -7
  37. package/dist/tools/visualize/src/cli.js +213 -78
  38. package/dist/tools/visualize/src/cli.js.map +11 -11
  39. package/dist/tools/visualize/src/codegen/structurizr.d.ts +343 -0
  40. package/dist/tools/visualize/src/types/structurizr.d.ts +235 -0
  41. package/package.json +22 -2
package/README.md CHANGED
@@ -343,6 +343,192 @@ test('user profile updates', async ({ page, extensionId }) => {
343
343
  })
344
344
  ```
345
345
 
346
+ ### Full-Stack SPAs with Elysia (Bun)
347
+
348
+ Polly provides first-class support for building full-stack web applications with Elysia and Bun, treating your SPA as a distributed system.
349
+
350
+ **Why?** Modern SPAs are distributed systems facing classic distributed computing problems: network unreliability, eventual consistency, offline behavior, cache invalidation, and the CAP theorem. The Elysia integration makes these concerns explicit and verifiable.
351
+
352
+ #### Server: Add Polly Middleware
353
+
354
+ ```typescript
355
+ // server/index.ts
356
+ import { Elysia, t } from 'elysia'
357
+ import { polly } from '@fairfox/polly/elysia'
358
+ import { $syncedState, $serverState } from '@fairfox/polly'
359
+
360
+ const app = new Elysia()
361
+ .use(polly({
362
+ // Define shared state
363
+ state: {
364
+ client: {
365
+ todos: $syncedState('todos', []),
366
+ user: $syncedState('user', null),
367
+ },
368
+ server: {
369
+ db: $serverState('db', database),
370
+ },
371
+ },
372
+
373
+ // Define client-side effects (what happens after server operations)
374
+ effects: {
375
+ 'POST /todos': {
376
+ client: ({ result, state }) => {
377
+ // Update client state with new todo
378
+ state.client.todos.value = [...state.client.todos.value, result]
379
+ },
380
+ broadcast: true, // Notify all connected clients
381
+ },
382
+ 'PATCH /todos/:id': {
383
+ client: ({ result, state }) => {
384
+ // Update specific todo in client state
385
+ state.client.todos.value = state.client.todos.value.map(t =>
386
+ t.id === result.id ? result : t
387
+ )
388
+ },
389
+ broadcast: true,
390
+ },
391
+ 'DELETE /todos/:id': {
392
+ client: ({ params, state }) => {
393
+ // Remove todo from client state
394
+ state.client.todos.value = state.client.todos.value.filter(
395
+ t => t.id !== Number(params.id)
396
+ )
397
+ },
398
+ broadcast: true,
399
+ },
400
+ },
401
+
402
+ // Define authorization rules
403
+ authorization: {
404
+ 'POST /todos': ({ state }) => state.client.user.value !== null,
405
+ 'PATCH /todos/:id': ({ state }) => state.client.user.value !== null,
406
+ 'DELETE /todos/:id': ({ state }) => state.client.user.value !== null,
407
+ },
408
+
409
+ // Configure offline behavior
410
+ offline: {
411
+ 'POST /todos': {
412
+ queue: true, // Queue when offline
413
+ optimistic: (body) => ({
414
+ id: -Date.now(), // Temporary ID
415
+ text: body.text,
416
+ completed: false,
417
+ }),
418
+ },
419
+ },
420
+
421
+ // Enable TLA+ generation for verification
422
+ tlaGeneration: true,
423
+ }))
424
+
425
+ // Write normal Elysia routes (no Polly annotations!)
426
+ .post('/todos', async ({ body, pollyState }) => {
427
+ const todo = await pollyState.server.db.value.todos.create(body)
428
+ return todo
429
+ }, {
430
+ body: t.Object({ text: t.String() })
431
+ })
432
+
433
+ .listen(3000)
434
+ ```
435
+
436
+ #### Client: Use Eden with Polly Wrapper
437
+
438
+ ```typescript
439
+ // client/api.ts
440
+ import { createPollyClient } from '@fairfox/polly/client'
441
+ import { $syncedState } from '@fairfox/polly'
442
+ import type { app } from '../server' // Import server type!
443
+
444
+ // Define client state
445
+ export const clientState = {
446
+ todos: $syncedState('todos', []),
447
+ user: $syncedState('user', null),
448
+ }
449
+
450
+ // Create type-safe API client (types inferred from server!)
451
+ export const api = createPollyClient<typeof app>('http://localhost:3000', {
452
+ state: clientState,
453
+ websocket: true, // Enable real-time updates
454
+ })
455
+ ```
456
+
457
+ ```typescript
458
+ // client/components/TodoList.tsx
459
+ import { useSignal } from '@preact/signals'
460
+ import { api, clientState } from '../api'
461
+
462
+ export function TodoList() {
463
+ const newTodo = useSignal('')
464
+
465
+ async function handleAdd() {
466
+ // Automatically handles:
467
+ // - Optimistic update if offline
468
+ // - Queue for retry
469
+ // - Execute client effect on success
470
+ // - Broadcast to other clients
471
+ await api.todos.post({ text: newTodo.value })
472
+ newTodo.value = ''
473
+ }
474
+
475
+ return (
476
+ <div>
477
+ {/* Connection status */}
478
+ <div>Status: {api.$polly.state.isOnline.value ? '🟢 Online' : '🔴 Offline'}</div>
479
+
480
+ {/* Queued requests indicator */}
481
+ {api.$polly.state.queuedRequests.value.length > 0 && (
482
+ <div>{api.$polly.state.queuedRequests.value.length} requests queued</div>
483
+ )}
484
+
485
+ {/* Todo list (automatically updates from state) */}
486
+ <ul>
487
+ {clientState.todos.value.map(todo => (
488
+ <li key={todo.id}>
489
+ <input
490
+ type="checkbox"
491
+ checked={todo.completed}
492
+ onChange={() => api.todos[todo.id].patch({ completed: !todo.completed })}
493
+ />
494
+ <span>{todo.text}</span>
495
+ <button onClick={() => api.todos[todo.id].delete()}>Delete</button>
496
+ </li>
497
+ ))}
498
+ </ul>
499
+
500
+ {/* Add new todo */}
501
+ <input
502
+ value={newTodo.value}
503
+ onInput={(e) => newTodo.value = e.currentTarget.value}
504
+ placeholder="What needs to be done?"
505
+ />
506
+ <button onClick={handleAdd}>Add</button>
507
+ </div>
508
+ )
509
+ }
510
+ ```
511
+
512
+ #### Key Benefits
513
+
514
+ 1. **Zero Type Duplication** - Eden infers client types from Elysia routes automatically
515
+ 2. **Distributed Systems Semantics** - Explicit offline, authorization, and effects configuration
516
+ 3. **Production-Ready** - Middleware is pass-through in production (minimal overhead)
517
+ 4. **Real-Time Updates** - WebSocket broadcast keeps all clients in sync
518
+ 5. **Formal Verification** - Generate TLA+ specs from middleware config to verify distributed properties
519
+
520
+ #### Production vs Development
521
+
522
+ **Development Mode:**
523
+ - Middleware adds metadata to responses for hot-reload and debugging
524
+ - Client effects serialized from server for live updates
525
+ - TLA+ generation enabled for verification
526
+
527
+ **Production Mode:**
528
+ - Middleware is minimal (authorization + broadcast only)
529
+ - Client effects are bundled at build time
530
+ - Zero serialization overhead
531
+
346
532
  ## Core Concepts
347
533
 
348
534
  ### State Primitives
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Polly Client Wrapper for Eden
3
+ *
4
+ * Enhances Eden treaty client with Polly features:
5
+ * - Offline queueing
6
+ * - WebSocket real-time updates
7
+ * - Client effect execution (dev mode)
8
+ * - Lamport clock synchronization
9
+ *
10
+ * Example:
11
+ * ```typescript
12
+ * import { createPollyClient } from '@fairfox/polly/client';
13
+ * import { $syncedState } from '@fairfox/polly';
14
+ * import type { app } from './server';
15
+ *
16
+ * const clientState = {
17
+ * todos: $syncedState('todos', []),
18
+ * };
19
+ *
20
+ * export const api = createPollyClient<typeof app>('http://localhost:3000', {
21
+ * state: clientState,
22
+ * });
23
+ *
24
+ * // Use it
25
+ * await api.todos.post({ text: 'Buy milk' });
26
+ *
27
+ * // Access Polly features
28
+ * console.log(api.$polly.state.isOnline.value); // true/false
29
+ * console.log(api.$polly.state.queuedRequests.value); // Array of queued requests
30
+ * ```
31
+ */
32
+ export type { PollyClientOptions } from "./wrapper";
33
+ export { createPollyClient } from "./wrapper";