@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.
- package/README.md +186 -0
- package/dist/src/client/index.d.ts +33 -0
- package/dist/src/client/index.js +586 -0
- package/dist/src/client/index.js.map +13 -0
- package/dist/src/client/wrapper.d.ts +54 -0
- package/dist/src/core/clock.d.ts +63 -0
- package/dist/src/elysia/index.d.ts +43 -0
- package/dist/src/elysia/index.js +241 -0
- package/dist/src/elysia/index.js.map +12 -0
- package/dist/src/elysia/plugin.d.ts +5 -0
- package/dist/src/elysia/tla-generator.d.ts +16 -0
- package/dist/src/elysia/types.d.ts +137 -0
- package/dist/src/utils/function-serialization.d.ts +14 -0
- package/dist/tools/analysis/src/extract/adr.d.ts +37 -0
- package/dist/tools/analysis/src/extract/architecture.d.ts +42 -0
- package/dist/tools/analysis/src/extract/contexts.d.ts +74 -0
- package/dist/tools/analysis/src/extract/flows.d.ts +68 -0
- package/dist/tools/analysis/src/extract/handlers.d.ts +330 -0
- package/dist/tools/analysis/src/extract/index.d.ts +9 -0
- package/dist/tools/analysis/src/extract/integrations.d.ts +77 -0
- package/dist/tools/analysis/src/extract/manifest.d.ts +64 -0
- package/dist/tools/analysis/src/extract/project-detector.d.ts +103 -0
- package/dist/tools/analysis/src/extract/relationships.d.ts +119 -0
- package/dist/tools/analysis/src/extract/types.d.ts +139 -0
- package/dist/tools/analysis/src/index.d.ts +2 -0
- package/dist/tools/analysis/src/types/adr.d.ts +39 -0
- package/dist/tools/analysis/src/types/architecture.d.ts +198 -0
- package/dist/tools/analysis/src/types/core.d.ts +178 -0
- package/dist/tools/analysis/src/types/index.d.ts +4 -0
- package/dist/tools/teach/src/cli.js +376 -81
- package/dist/tools/teach/src/cli.js.map +13 -13
- package/dist/tools/teach/src/index.d.ts +28 -0
- package/dist/tools/teach/src/index.js +233 -84
- package/dist/tools/teach/src/index.js.map +13 -13
- package/dist/tools/verify/src/cli.js +127 -26
- package/dist/tools/verify/src/cli.js.map +7 -7
- package/dist/tools/visualize/src/cli.js +213 -78
- package/dist/tools/visualize/src/cli.js.map +11 -11
- package/dist/tools/visualize/src/codegen/structurizr.d.ts +343 -0
- package/dist/tools/visualize/src/types/structurizr.d.ts +235 -0
- 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";
|