@flightdev/ui 2.0.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 (118) hide show
  1. package/.turbo/turbo-build.log +81 -0
  2. package/.turbo/turbo-lint.log +40 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/LICENSE +21 -0
  5. package/README.md +92 -0
  6. package/TESTING.md +124 -0
  7. package/dist/adapter-MMD-iHNx.d.ts +424 -0
  8. package/dist/adapters/tier-1/angular.d.ts +60 -0
  9. package/dist/adapters/tier-1/angular.js +2 -0
  10. package/dist/adapters/tier-1/index.d.ts +7 -0
  11. package/dist/adapters/tier-1/index.js +7 -0
  12. package/dist/adapters/tier-1/qwik.d.ts +55 -0
  13. package/dist/adapters/tier-1/qwik.js +2 -0
  14. package/dist/adapters/tier-1/react.d.ts +67 -0
  15. package/dist/adapters/tier-1/react.js +2 -0
  16. package/dist/adapters/tier-1/solid.d.ts +45 -0
  17. package/dist/adapters/tier-1/solid.js +2 -0
  18. package/dist/adapters/tier-1/svelte.d.ts +48 -0
  19. package/dist/adapters/tier-1/svelte.js +2 -0
  20. package/dist/adapters/tier-1/vue.d.ts +47 -0
  21. package/dist/adapters/tier-1/vue.js +2 -0
  22. package/dist/adapters/tier-2/index.d.ts +7 -0
  23. package/dist/adapters/tier-2/index.js +7 -0
  24. package/dist/adapters/tier-2/inferno.d.ts +31 -0
  25. package/dist/adapters/tier-2/inferno.js +2 -0
  26. package/dist/adapters/tier-2/lit.d.ts +34 -0
  27. package/dist/adapters/tier-2/lit.js +2 -0
  28. package/dist/adapters/tier-2/marko.d.ts +59 -0
  29. package/dist/adapters/tier-2/marko.js +2 -0
  30. package/dist/adapters/tier-2/mithril.d.ts +31 -0
  31. package/dist/adapters/tier-2/mithril.js +2 -0
  32. package/dist/adapters/tier-2/preact.d.ts +33 -0
  33. package/dist/adapters/tier-2/preact.js +2 -0
  34. package/dist/adapters/tier-2/stencil.d.ts +52 -0
  35. package/dist/adapters/tier-2/stencil.js +2 -0
  36. package/dist/adapters/tier-3/alpine.d.ts +73 -0
  37. package/dist/adapters/tier-3/alpine.js +2 -0
  38. package/dist/adapters/tier-3/hotwire.d.ts +71 -0
  39. package/dist/adapters/tier-3/hotwire.js +2 -0
  40. package/dist/adapters/tier-3/htmx.d.ts +88 -0
  41. package/dist/adapters/tier-3/htmx.js +2 -0
  42. package/dist/adapters/tier-3/index.d.ts +7 -0
  43. package/dist/adapters/tier-3/index.js +7 -0
  44. package/dist/adapters/tier-3/petite-vue.d.ts +56 -0
  45. package/dist/adapters/tier-3/petite-vue.js +2 -0
  46. package/dist/adapters/tier-3/stimulus.d.ts +63 -0
  47. package/dist/adapters/tier-3/stimulus.js +2 -0
  48. package/dist/adapters/tier-3/vanilla.d.ts +63 -0
  49. package/dist/adapters/tier-3/vanilla.js +2 -0
  50. package/dist/chunk-2SNQ6PTM.js +217 -0
  51. package/dist/chunk-3D4XMIZI.js +136 -0
  52. package/dist/chunk-3HU6GSQ4.js +125 -0
  53. package/dist/chunk-4PZDNFL7.js +148 -0
  54. package/dist/chunk-5IBLFTYL.js +114 -0
  55. package/dist/chunk-64JZJ7OK.js +142 -0
  56. package/dist/chunk-7ZJI3QU2.js +132 -0
  57. package/dist/chunk-CE4FJHQJ.js +133 -0
  58. package/dist/chunk-DTCAUBH5.js +87 -0
  59. package/dist/chunk-NTASPOHG.js +106 -0
  60. package/dist/chunk-OI2AMQLG.js +152 -0
  61. package/dist/chunk-Q7HUE44H.js +106 -0
  62. package/dist/chunk-QH3LOWXU.js +155 -0
  63. package/dist/chunk-QIVAK6BH.js +103 -0
  64. package/dist/chunk-V34XPVGK.js +103 -0
  65. package/dist/chunk-VK7ZPMO7.js +221 -0
  66. package/dist/chunk-X6CNUW6T.js +136 -0
  67. package/dist/chunk-XTDK7ME5.js +382 -0
  68. package/dist/chunk-YFGSHW5S.js +121 -0
  69. package/dist/chunk-ZAJVSE7J.js +90 -0
  70. package/dist/core/index.d.ts +161 -0
  71. package/dist/core/index.js +2 -0
  72. package/dist/index.d.ts +103 -0
  73. package/dist/index.js +71 -0
  74. package/docs/ADAPTERS.md +946 -0
  75. package/docs/PATTERNS.md +836 -0
  76. package/package.json +229 -0
  77. package/src/adapters/tier-1/angular.ts +223 -0
  78. package/src/adapters/tier-1/index.ts +12 -0
  79. package/src/adapters/tier-1/qwik.ts +177 -0
  80. package/src/adapters/tier-1/react.ts +330 -0
  81. package/src/adapters/tier-1/solid.ts +222 -0
  82. package/src/adapters/tier-1/svelte.ts +211 -0
  83. package/src/adapters/tier-1/vue.ts +234 -0
  84. package/src/adapters/tier-2/index.ts +12 -0
  85. package/src/adapters/tier-2/inferno.ts +149 -0
  86. package/src/adapters/tier-2/lit.ts +191 -0
  87. package/src/adapters/tier-2/marko.ts +199 -0
  88. package/src/adapters/tier-2/mithril.ts +152 -0
  89. package/src/adapters/tier-2/preact.ts +133 -0
  90. package/src/adapters/tier-2/stencil.ts +214 -0
  91. package/src/adapters/tier-3/alpine.ts +218 -0
  92. package/src/adapters/tier-3/hotwire.ts +254 -0
  93. package/src/adapters/tier-3/htmx.ts +263 -0
  94. package/src/adapters/tier-3/index.ts +12 -0
  95. package/src/adapters/tier-3/petite-vue.ts +163 -0
  96. package/src/adapters/tier-3/stimulus.ts +233 -0
  97. package/src/adapters/tier-3/vanilla.ts +252 -0
  98. package/src/ambient.d.ts +310 -0
  99. package/src/core/adapter.ts +366 -0
  100. package/src/core/index.ts +56 -0
  101. package/src/core/registry.ts +518 -0
  102. package/src/core/types.ts +461 -0
  103. package/src/htmx.ts +134 -0
  104. package/src/index.ts +263 -0
  105. package/test/__mocks__/stencil-core.ts +19 -0
  106. package/test/__mocks__/stencil-hydrate.ts +15 -0
  107. package/test/adapters/tier-1.test.ts +206 -0
  108. package/test/adapters/tier-2.test.ts +175 -0
  109. package/test/adapters/tier-3.test.ts +284 -0
  110. package/test/contracts/adapter.contract.ts +293 -0
  111. package/test/core/core.test.ts +310 -0
  112. package/test/errors/error-handling.test.ts +454 -0
  113. package/test/integration/htmx.integration.test.ts +246 -0
  114. package/test/integration/react.integration.test.ts +271 -0
  115. package/test/integration/registry.integration.test.ts +308 -0
  116. package/tsconfig.json +22 -0
  117. package/tsup.config.ts +93 -0
  118. package/vitest.config.ts +101 -0
@@ -0,0 +1,946 @@
1
+ # UI Adapter System
2
+
3
+ This document provides comprehensive documentation for the `@flight-framework/ui` adapter system, including tier classifications, implementation patterns, and enterprise usage examples.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Overview](#overview)
8
+ 2. [Tier Classification](#tier-classification)
9
+ 3. [Tier 1: Full Support Adapters](#tier-1-full-support-adapters)
10
+ 4. [Tier 2: Standard Support Adapters](#tier-2-standard-support-adapters)
11
+ 5. [Tier 3: HTML-First Adapters](#tier-3-html-first-adapters)
12
+ 6. [Advanced Patterns](#advanced-patterns)
13
+ 7. [Creating Custom Adapters](#creating-custom-adapters)
14
+ 8. [Performance Considerations](#performance-considerations)
15
+ 9. [Migration Guide](#migration-guide)
16
+
17
+ ---
18
+
19
+ ## Overview
20
+
21
+ The Flight UI adapter system provides a unified interface for rendering components across 18+ frameworks. All adapters implement the `UIAdapterV2` interface, ensuring consistent behavior regardless of the underlying framework.
22
+
23
+ ### Core Concepts
24
+
25
+ **Adapter**: A class that translates Flight's rendering API to a specific framework's SSR implementation.
26
+
27
+ **Capabilities**: Feature flags indicating what an adapter supports (streaming, islands, resumability, etc.).
28
+
29
+ **Tiers**: Classification levels indicating the depth of framework integration.
30
+
31
+ ### Quick Start
32
+
33
+ ```typescript
34
+ import { defineUI } from '@flight-framework/ui';
35
+ import { react } from '@flight-framework/ui/react';
36
+
37
+ export default defineUI(react({
38
+ streaming: true,
39
+ }));
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Tier Classification
45
+
46
+ Adapters are organized into three tiers based on their feature set and integration depth.
47
+
48
+ | Tier | Support Level | Use Case |
49
+ |------|---------------|----------|
50
+ | Tier 1 | Full | Enterprise applications requiring streaming, islands, and full hydration |
51
+ | Tier 2 | Standard | Applications needing SSR with simpler hydration requirements |
52
+ | Tier 3 | HTML-First | Server-driven applications with minimal client-side JavaScript |
53
+
54
+ ### Capability Matrix
55
+
56
+ | Capability | Tier 1 | Tier 2 | Tier 3 |
57
+ |------------|--------|--------|--------|
58
+ | SSR | Yes | Yes | Yes |
59
+ | Streaming | Yes | Some | No |
60
+ | Islands | Yes | Some | No |
61
+ | Resumable | Qwik only | No | No |
62
+ | CSR Fallback | Yes | Yes | Limited |
63
+ | Server Components | React only | No | No |
64
+
65
+ ---
66
+
67
+ ## Tier 1: Full Support Adapters
68
+
69
+ Tier 1 adapters provide complete SSR, streaming, hydration, and islands architecture support.
70
+
71
+ ### Available Adapters
72
+
73
+ - React 18+
74
+ - Vue 3
75
+ - Angular 17+
76
+ - Svelte 5
77
+ - Solid
78
+ - Qwik
79
+
80
+ ### React Adapter
81
+
82
+ Full-featured React 18+ SSR adapter with streaming and hydration support.
83
+
84
+ **Capabilities**:
85
+ - Streaming SSR via `renderToReadableStream`
86
+ - Progressive hydration
87
+ - Islands architecture
88
+ - React Server Components
89
+ - SSG and CSR fallback
90
+
91
+ **Basic Usage**:
92
+
93
+ ```typescript
94
+ import { defineUI } from '@flight-framework/ui';
95
+ import { react } from '@flight-framework/ui/react';
96
+
97
+ export default defineUI(react());
98
+ ```
99
+
100
+ **With Options**:
101
+
102
+ ```typescript
103
+ import { react } from '@flight-framework/ui/react';
104
+
105
+ const adapter = react({
106
+ streaming: true,
107
+ serverComponents: true,
108
+ onError: (error) => {
109
+ console.error('[SSR Error]', error.message);
110
+ },
111
+ onShellReady: () => {
112
+ console.log('Shell rendered');
113
+ },
114
+ });
115
+ ```
116
+
117
+ **Streaming SSR Example**:
118
+
119
+ ```typescript
120
+ import { react } from '@flight-framework/ui/react';
121
+
122
+ const adapter = react();
123
+
124
+ // Component definition
125
+ function App({ user }) {
126
+ return (
127
+ <main>
128
+ <h1>Welcome, {user.name}</h1>
129
+ <Suspense fallback={<Spinner />}>
130
+ <UserDashboard userId={user.id} />
131
+ </Suspense>
132
+ </main>
133
+ );
134
+ }
135
+
136
+ // Streaming render
137
+ const { stream, done, abort } = adapter.renderToStream(
138
+ { component: App, props: { user } },
139
+ { url: request.url },
140
+ {
141
+ timeout: 10000,
142
+ onShellReady: () => {
143
+ // Send headers when shell is ready
144
+ response.writeHead(200, {
145
+ 'Content-Type': 'text/html',
146
+ 'Transfer-Encoding': 'chunked',
147
+ });
148
+ },
149
+ onError: (error) => {
150
+ console.error('[Stream Error]', error);
151
+ },
152
+ }
153
+ );
154
+
155
+ // Pipe stream to response
156
+ const reader = stream.getReader();
157
+ while (true) {
158
+ const { done: readerDone, value } = await reader.read();
159
+ if (readerDone) break;
160
+ response.write(value);
161
+ }
162
+ response.end();
163
+ ```
164
+
165
+ **Islands Architecture**:
166
+
167
+ ```typescript
168
+ const adapter = react();
169
+
170
+ // Create an island for partial hydration
171
+ const profileIsland = adapter.createIsland(
172
+ 'UserProfile',
173
+ { userId: '123' },
174
+ {
175
+ hydrate: 'visible', // Hydrate when visible
176
+ priority: 10,
177
+ }
178
+ );
179
+
180
+ // Render island placeholder
181
+ const html = `
182
+ <div class="page">
183
+ <header>Static content</header>
184
+ ${profileIsland.placeholder}
185
+ <footer>Static content</footer>
186
+ </div>
187
+ `;
188
+ ```
189
+
190
+ ### Vue Adapter
191
+
192
+ Vue 3 SSR adapter with streaming and islands support.
193
+
194
+ **Basic Usage**:
195
+
196
+ ```typescript
197
+ import { vue } from '@flight-framework/ui/vue';
198
+
199
+ const adapter = vue({
200
+ streaming: true,
201
+ });
202
+ ```
203
+
204
+ **SSR Example**:
205
+
206
+ ```typescript
207
+ import { vue } from '@flight-framework/ui/vue';
208
+ import App from './App.vue';
209
+
210
+ const adapter = vue();
211
+
212
+ const result = await adapter.renderToString({
213
+ component: App,
214
+ props: { title: 'My Page' },
215
+ });
216
+
217
+ // result.html contains rendered HTML
218
+ // result.hydrationData contains state for client
219
+ ```
220
+
221
+ ### Solid Adapter
222
+
223
+ Solid SSR adapter with streaming support.
224
+
225
+ ```typescript
226
+ import { solid } from '@flight-framework/ui/solid';
227
+
228
+ const adapter = solid({
229
+ streaming: true,
230
+ });
231
+
232
+ // Streaming render
233
+ const { stream, done } = adapter.renderToStream({
234
+ component: App,
235
+ props: { data },
236
+ });
237
+ ```
238
+
239
+ ### Qwik Adapter
240
+
241
+ Qwik adapter with resumable hydration support.
242
+
243
+ **Resumability**: Qwik serializes component state to HTML, enabling zero-JS-until-interaction. This is the only Tier 1 adapter with `resumable: true`.
244
+
245
+ ```typescript
246
+ import { qwik } from '@flight-framework/ui/qwik';
247
+
248
+ const adapter = qwik();
249
+
250
+ const result = await adapter.renderToString({
251
+ component: App,
252
+ props: { data },
253
+ });
254
+
255
+ // Serialize state for resumability
256
+ const serialized = adapter.serializeState(result.hydrationData);
257
+
258
+ // Resume on client (no JS needed until interaction)
259
+ const state = adapter.resumeFromState(serialized);
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Tier 2: Standard Support Adapters
265
+
266
+ Tier 2 adapters provide SSR and hydration with framework-specific optimizations.
267
+
268
+ ### Available Adapters
269
+
270
+ - Preact
271
+ - Lit
272
+ - Marko (with streaming)
273
+ - Stencil
274
+ - Mithril
275
+ - Inferno
276
+
277
+ ### Preact Adapter
278
+
279
+ Lightweight React alternative with smaller bundle size.
280
+
281
+ ```typescript
282
+ import { preact } from '@flight-framework/ui/preact';
283
+
284
+ const adapter = preact();
285
+
286
+ const result = await adapter.renderToString({
287
+ component: App,
288
+ props: { count: 0 },
289
+ });
290
+ ```
291
+
292
+ ### Lit Adapter
293
+
294
+ Web Components SSR with declarative hydration.
295
+
296
+ ```typescript
297
+ import { lit } from '@flight-framework/ui/lit';
298
+
299
+ const adapter = lit();
300
+
301
+ // Lit uses web components with shadow DOM
302
+ const result = await adapter.renderToString({
303
+ component: 'my-element',
304
+ props: { data: 'value' },
305
+ });
306
+ ```
307
+
308
+ ### Marko Adapter
309
+
310
+ Streaming SSR with progressive enhancement.
311
+
312
+ ```typescript
313
+ import { marko } from '@flight-framework/ui/marko';
314
+
315
+ const adapter = marko();
316
+
317
+ // Marko supports streaming out of the box
318
+ const { stream, done } = adapter.renderToStream({
319
+ component: template,
320
+ props: { items },
321
+ });
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Tier 3: HTML-First Adapters
327
+
328
+ Tier 3 adapters focus on server-rendered HTML with minimal JavaScript.
329
+
330
+ ### Available Adapters
331
+
332
+ - HTMX
333
+ - Alpine.js
334
+ - Hotwire (Turbo + Stimulus)
335
+ - Stimulus
336
+ - Petite-vue
337
+ - Vanilla (Pure Web Components)
338
+
339
+ ### HTMX Adapter
340
+
341
+ Server-driven UI with HTML over the wire.
342
+
343
+ **Philosophy**: HTMX keeps state on the server. Clicking a button sends a request, the server returns HTML, and HTMX swaps it into the DOM. No client-side JavaScript framework needed.
344
+
345
+ **Basic Usage**:
346
+
347
+ ```typescript
348
+ import { htmx } from '@flight-framework/ui/htmx';
349
+
350
+ const adapter = htmx({
351
+ version: '2.0.2',
352
+ extensions: ['json-enc', 'loading-states'],
353
+ });
354
+ ```
355
+
356
+ **Template Helpers**:
357
+
358
+ ```typescript
359
+ import { htmx, hxGet, hxPost, hx, attrsToString } from '@flight-framework/ui/htmx';
360
+
361
+ // Create hx-get attributes
362
+ const loadMoreAttrs = hxGet('/api/items?page=2', {
363
+ target: '#item-list',
364
+ swap: 'beforeend',
365
+ trigger: 'click',
366
+ });
367
+
368
+ // Build HTML with helpers
369
+ const button = hx('button', loadMoreAttrs, 'Load More');
370
+ // <button hx-get="/api/items?page=2" hx-target="#item-list" hx-swap="beforeend" hx-trigger="click">Load More</button>
371
+ ```
372
+
373
+ **Full Page Example**:
374
+
375
+ ```typescript
376
+ const adapter = htmx({
377
+ extensions: ['loading-states'],
378
+ });
379
+
380
+ function renderPage(items) {
381
+ return `
382
+ <!DOCTYPE html>
383
+ <html>
384
+ <head>
385
+ <title>Items</title>
386
+ </head>
387
+ <body>
388
+ <main>
389
+ <h1>Items</h1>
390
+
391
+ <ul id="item-list">
392
+ ${items.map(item => `
393
+ <li>
394
+ ${item.name}
395
+ <button hx-delete="/api/items/${item.id}"
396
+ hx-target="closest li"
397
+ hx-swap="outerHTML"
398
+ hx-confirm="Delete this item?">
399
+ Delete
400
+ </button>
401
+ </li>
402
+ `).join('')}
403
+ </ul>
404
+
405
+ <form hx-post="/api/items" hx-target="#item-list" hx-swap="beforeend">
406
+ <input name="name" placeholder="New item" required />
407
+ <button type="submit">Add</button>
408
+ </form>
409
+ </main>
410
+
411
+ ${adapter.getHydrationScript({ html: '' })}
412
+ </body>
413
+ </html>
414
+ `;
415
+ }
416
+ ```
417
+
418
+ ### Alpine.js Adapter
419
+
420
+ Declarative reactivity sprinkled on HTML.
421
+
422
+ ```typescript
423
+ import { alpine, xData } from '@flight-framework/ui/alpine';
424
+
425
+ const adapter = alpine({
426
+ plugins: ['intersect', 'persist'],
427
+ });
428
+
429
+ // x-data helper
430
+ const counter = xData({ count: 0 }, `
431
+ <div>
432
+ <button @click="count++">Increment</button>
433
+ <span x-text="count"></span>
434
+ </div>
435
+ `);
436
+ ```
437
+
438
+ ### Hotwire Adapter
439
+
440
+ Turbo + Stimulus for server-rendered applications.
441
+
442
+ ```typescript
443
+ import { hotwire } from '@flight-framework/ui/hotwire';
444
+
445
+ const adapter = hotwire({
446
+ turbo: true,
447
+ stimulus: true,
448
+ });
449
+ ```
450
+
451
+ ---
452
+
453
+ ## Advanced Patterns
454
+
455
+ ### Pattern 1: Multi-Framework Rendering
456
+
457
+ Render different parts of your application with different frameworks.
458
+
459
+ ```typescript
460
+ import { adapterRegistry, registerBuiltinAdapters } from '@flight-framework/ui';
461
+
462
+ registerBuiltinAdapters();
463
+
464
+ async function renderPage(request) {
465
+ // Use React for main content
466
+ const reactAdapter = await adapterRegistry.get('react');
467
+ const mainContent = await reactAdapter.renderToString({
468
+ component: MainApp,
469
+ props: { user },
470
+ });
471
+
472
+ // Use HTMX for dynamic sidebar
473
+ const htmxAdapter = await adapterRegistry.get('htmx');
474
+ const sidebar = await htmxAdapter.renderToString({
475
+ component: () => `
476
+ <aside hx-get="/api/notifications" hx-trigger="every 30s">
477
+ Loading...
478
+ </aside>
479
+ `,
480
+ props: {},
481
+ });
482
+
483
+ return `
484
+ <!DOCTYPE html>
485
+ <html>
486
+ <body>
487
+ <div id="main">${mainContent.html}</div>
488
+ ${sidebar.html}
489
+ ${reactAdapter.getHydrationScript(mainContent)}
490
+ ${htmxAdapter.getHydrationScript(sidebar)}
491
+ </body>
492
+ </html>
493
+ `;
494
+ }
495
+ ```
496
+
497
+ ### Pattern 2: Progressive Enhancement
498
+
499
+ Start with HTML, enhance with JavaScript where needed.
500
+
501
+ ```typescript
502
+ import { htmx } from '@flight-framework/ui/htmx';
503
+ import { react } from '@flight-framework/ui/react';
504
+
505
+ // Render base page with HTMX
506
+ const htmxAdapter = htmx();
507
+ const basePage = await htmxAdapter.renderToString({
508
+ component: () => `
509
+ <main>
510
+ <article>${articleContent}</article>
511
+
512
+ <!-- Island for interactive comments -->
513
+ <div id="comments-island"
514
+ data-flight-island="comments"
515
+ data-props='${JSON.stringify({ articleId })}'>
516
+ <noscript>
517
+ <!-- Fallback HTMX for no-JS -->
518
+ <div hx-get="/api/comments/${articleId}" hx-trigger="load">
519
+ Loading comments...
520
+ </div>
521
+ </noscript>
522
+ </div>
523
+ </main>
524
+ `,
525
+ props: {},
526
+ });
527
+
528
+ // Hydrate React island on client
529
+ // (handled by client-side code)
530
+ ```
531
+
532
+ ### Pattern 3: Capability-Based Adapter Selection
533
+
534
+ Select adapters based on required capabilities.
535
+
536
+ ```typescript
537
+ import { adapterRegistry, registerBuiltinAdapters } from '@flight-framework/ui';
538
+
539
+ registerBuiltinAdapters();
540
+
541
+ function getAdapterForFeatures(requirements) {
542
+ // List adapters with required capabilities
543
+ const streamingAdapters = adapterRegistry.listByCapability('streaming');
544
+ const islandAdapters = adapterRegistry.listByCapability('islands');
545
+
546
+ if (requirements.streaming && requirements.islands) {
547
+ // Need both - find intersection
548
+ const both = streamingAdapters.filter(a => islandAdapters.includes(a));
549
+ return both[0] || 'react'; // Default to React
550
+ }
551
+
552
+ if (requirements.streaming) {
553
+ return streamingAdapters[0] || 'react';
554
+ }
555
+
556
+ if (requirements.islands) {
557
+ return islandAdapters[0] || 'react';
558
+ }
559
+
560
+ // Default to lightweight option
561
+ return 'htmx';
562
+ }
563
+
564
+ const adapterId = getAdapterForFeatures({ streaming: true, islands: false });
565
+ const adapter = await adapterRegistry.get(adapterId);
566
+ ```
567
+
568
+ ### Pattern 4: Error Boundaries with Streaming
569
+
570
+ Handle errors gracefully during streaming SSR.
571
+
572
+ ```typescript
573
+ import { react } from '@flight-framework/ui/react';
574
+
575
+ const adapter = react();
576
+
577
+ const { stream, done, abort } = adapter.renderToStream(
578
+ { component: App, props: {} },
579
+ { url: '/page' },
580
+ {
581
+ onShellError: (error) => {
582
+ // Shell failed - return error page
583
+ console.error('Shell error:', error);
584
+ abort();
585
+ return renderErrorPage(500);
586
+ },
587
+ onError: (error) => {
588
+ // Error in Suspense boundary - log but continue
589
+ console.error('Render error:', error);
590
+ // HTMX fallback for failed section
591
+ },
592
+ onAllReady: () => {
593
+ console.log('Page fully rendered');
594
+ },
595
+ }
596
+ );
597
+ ```
598
+
599
+ ### Pattern 5: Caching SSR Results
600
+
601
+ Cache rendered HTML for frequently accessed pages.
602
+
603
+ ```typescript
604
+ import { react } from '@flight-framework/ui/react';
605
+
606
+ const adapter = react();
607
+ const cache = new Map();
608
+
609
+ async function renderWithCache(cacheKey, component, props, ttl = 60000) {
610
+ const cached = cache.get(cacheKey);
611
+ if (cached && Date.now() - cached.time < ttl) {
612
+ return cached.result;
613
+ }
614
+
615
+ const result = await adapter.renderToString({
616
+ component,
617
+ props,
618
+ });
619
+
620
+ cache.set(cacheKey, {
621
+ result,
622
+ time: Date.now(),
623
+ });
624
+
625
+ return result;
626
+ }
627
+
628
+ // Usage
629
+ const html = await renderWithCache(
630
+ `product:${productId}`,
631
+ ProductPage,
632
+ { productId },
633
+ 5 * 60 * 1000 // 5 minute TTL
634
+ );
635
+ ```
636
+
637
+ ---
638
+
639
+ ## Creating Custom Adapters
640
+
641
+ Extend `BaseUIAdapter` to create custom framework adapters.
642
+
643
+ ### Minimal Implementation
644
+
645
+ ```typescript
646
+ import { BaseUIAdapter } from '@flight-framework/ui/core';
647
+ import type {
648
+ Component,
649
+ RenderResult,
650
+ RenderContext,
651
+ AdapterCapabilities
652
+ } from '@flight-framework/ui/core/types';
653
+
654
+ export class MyFrameworkAdapter extends BaseUIAdapter {
655
+ readonly id = 'my-framework';
656
+ readonly name = 'My Framework';
657
+ readonly framework = 'my-framework';
658
+ readonly tier = 'tier-2' as const;
659
+
660
+ override readonly capabilities: AdapterCapabilities = {
661
+ streaming: false,
662
+ partialHydration: false,
663
+ islands: false,
664
+ resumable: false,
665
+ ssg: true,
666
+ csr: true,
667
+ serverComponents: false,
668
+ };
669
+
670
+ async renderToString(
671
+ component: Component,
672
+ context?: RenderContext
673
+ ): Promise<RenderResult> {
674
+ const startTime = performance.now();
675
+
676
+ // Your framework's SSR logic
677
+ const html = await myFramework.render(
678
+ component.component,
679
+ component.props
680
+ );
681
+
682
+ return {
683
+ html,
684
+ hydrationData: {
685
+ props: component.props,
686
+ componentId: this.generateId(),
687
+ },
688
+ timing: this.createTiming(startTime),
689
+ };
690
+ }
691
+
692
+ getHydrationScript(result: RenderResult): string {
693
+ return `
694
+ <script>
695
+ window.__FLIGHT_DATA__ = ${this.serializeProps(result.hydrationData)};
696
+ // Your hydration initialization code
697
+ </script>
698
+ `.trim();
699
+ }
700
+
701
+ getClientEntry(): string {
702
+ return `
703
+ export function hydrate() {
704
+ const data = window.__FLIGHT_DATA__;
705
+ // Your hydration logic
706
+ }
707
+ `.trim();
708
+ }
709
+ }
710
+
711
+ export function myFramework(): MyFrameworkAdapter {
712
+ return new MyFrameworkAdapter();
713
+ }
714
+ ```
715
+
716
+ ### Adding Streaming Support
717
+
718
+ ```typescript
719
+ import { StreamingRenderResult, StreamingOptions } from '@flight-framework/ui/core/types';
720
+
721
+ export class MyStreamingAdapter extends BaseUIAdapter {
722
+ // ... base implementation ...
723
+
724
+ override readonly capabilities: AdapterCapabilities = {
725
+ streaming: true, // Enable streaming
726
+ // ... other capabilities
727
+ };
728
+
729
+ renderToStream(
730
+ component: Component,
731
+ context?: RenderContext,
732
+ options?: StreamingOptions
733
+ ): StreamingRenderResult {
734
+ let resolvePromise: () => void;
735
+ let rejectPromise: (error: Error) => void;
736
+
737
+ const done = new Promise<void>((resolve, reject) => {
738
+ resolvePromise = resolve;
739
+ rejectPromise = reject;
740
+ });
741
+
742
+ const stream = new ReadableStream<Uint8Array>({
743
+ start: async (controller) => {
744
+ try {
745
+ // Your streaming logic
746
+ const chunks = myFramework.renderStream(component);
747
+
748
+ for await (const chunk of chunks) {
749
+ controller.enqueue(new TextEncoder().encode(chunk));
750
+ }
751
+
752
+ controller.close();
753
+ resolvePromise();
754
+ } catch (error) {
755
+ const err = error instanceof Error ? error : new Error(String(error));
756
+ options?.onError?.(err);
757
+ controller.error(err);
758
+ rejectPromise(err);
759
+ }
760
+ },
761
+ });
762
+
763
+ return {
764
+ stream,
765
+ done,
766
+ abort: () => {
767
+ resolvePromise();
768
+ },
769
+ };
770
+ }
771
+ }
772
+ ```
773
+
774
+ ### Registering Custom Adapters
775
+
776
+ ```typescript
777
+ import { adapterRegistry } from '@flight-framework/ui';
778
+ import { myFramework } from './my-framework-adapter';
779
+
780
+ // Register your adapter
781
+ adapterRegistry.register('my-framework', () => myFramework());
782
+
783
+ // Use it
784
+ const adapter = await adapterRegistry.get('my-framework');
785
+ ```
786
+
787
+ ---
788
+
789
+ ## Performance Considerations
790
+
791
+ ### Streaming vs String Rendering
792
+
793
+ | Approach | TTFB | Total Time | Memory | Use Case |
794
+ |----------|------|------------|--------|----------|
795
+ | renderToString | Slower | Faster | Higher | Small pages, caching |
796
+ | renderToStream | Faster | Slightly slower | Lower | Large pages, real-time |
797
+
798
+ **Recommendation**: Use streaming for pages with async data or Suspense boundaries.
799
+
800
+ ### Hydration Strategies
801
+
802
+ | Strategy | Bundle Size | Interactivity | Best For |
803
+ |----------|-------------|---------------|----------|
804
+ | Full | Largest | Immediate | SPAs, complex UIs |
805
+ | Progressive | Medium | Gradual | Content sites |
806
+ | Islands | Smallest | Per-component | Mostly static sites |
807
+ | None (HTMX) | Minimal | Server-roundtrip | CRUD applications |
808
+
809
+ ### Bundle Size by Tier
810
+
811
+ | Tier | Typical JS Bundle | Example |
812
+ |------|-------------------|---------|
813
+ | Tier 1 | 50-150 KB | React, Vue, Angular |
814
+ | Tier 2 | 10-50 KB | Preact, Lit, Inferno |
815
+ | Tier 3 | 0-15 KB | HTMX, Alpine |
816
+
817
+ ---
818
+
819
+ ## Migration Guide
820
+
821
+ ### From React to Preact
822
+
823
+ ```diff
824
+ - import { react } from '@flight-framework/ui/react';
825
+ + import { preact } from '@flight-framework/ui/preact';
826
+
827
+ - export default defineUI(react());
828
+ + export default defineUI(preact());
829
+ ```
830
+
831
+ Component compatibility: Most React components work in Preact. Check for:
832
+ - Class components (prefer functional)
833
+ - React-specific hooks (useId, useSyncExternalStore)
834
+ - Concurrent features (Suspense for data)
835
+
836
+ ### From Vue to Svelte
837
+
838
+ Both have similar reactivity models but different syntax.
839
+
840
+ ```diff
841
+ - import { vue } from '@flight-framework/ui/vue';
842
+ + import { svelte } from '@flight-framework/ui/svelte';
843
+
844
+ - export default defineUI(vue());
845
+ + export default defineUI(svelte());
846
+ ```
847
+
848
+ ### From Any Framework to HTMX
849
+
850
+ For applications that can move state to the server:
851
+
852
+ 1. Identify interactive sections
853
+ 2. Replace with `hx-*` attributes
854
+ 3. Create server endpoints for each interaction
855
+ 4. Remove client-side state management
856
+
857
+ ```diff
858
+ - // React component with client state
859
+ - function Counter() {
860
+ - const [count, setCount] = useState(0);
861
+ - return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
862
+ - }
863
+
864
+ + // HTMX with server state
865
+ + // Server endpoint: POST /api/counter increments session counter
866
+ + function Counter({ count }) {
867
+ + return `
868
+ + <button hx-post="/api/counter" hx-swap="outerHTML">
869
+ + ${count}
870
+ + </button>
871
+ + `;
872
+ + }
873
+ ```
874
+
875
+ ---
876
+
877
+ ## API Reference
878
+
879
+ ### UIAdapterV2 Interface
880
+
881
+ All adapters implement this interface.
882
+
883
+ ```typescript
884
+ interface UIAdapterV2 {
885
+ // Identification
886
+ readonly id: string;
887
+ readonly name: string;
888
+ readonly framework: string;
889
+ readonly frameworkVersion?: string;
890
+ readonly tier: AdapterTier;
891
+ readonly capabilities: AdapterCapabilities;
892
+
893
+ // Core rendering (required)
894
+ renderToString(component: Component, context?: RenderContext): Promise<RenderResult>;
895
+ getHydrationScript(result: RenderResult): string;
896
+ getClientEntry(): string;
897
+
898
+ // Streaming (optional)
899
+ renderToStream?(component: Component, context?: RenderContext, options?: StreamingOptions): StreamingRenderResult;
900
+
901
+ // Islands (optional)
902
+ createIsland?(component: unknown, props?: Record<string, unknown>, options?: IslandOptions): Island;
903
+ hydrateIsland?(element: Element, island: Island): void;
904
+
905
+ // Resumability (optional)
906
+ serializeState?(state: unknown): string;
907
+ resumeFromState?(serialized: string): unknown;
908
+
909
+ // Lifecycle (optional)
910
+ init?(): Promise<void>;
911
+ dispose?(): Promise<void>;
912
+ }
913
+ ```
914
+
915
+ ### AdapterCapabilities
916
+
917
+ ```typescript
918
+ interface AdapterCapabilities {
919
+ streaming: boolean; // renderToStream support
920
+ partialHydration: boolean; // Selective hydration
921
+ islands: boolean; // Islands architecture
922
+ resumable: boolean; // Qwik-style resumability
923
+ ssg: boolean; // Static generation
924
+ csr: boolean; // Client-side rendering
925
+ serverComponents: boolean; // RSC support
926
+ }
927
+ ```
928
+
929
+ ### AdapterRegistry
930
+
931
+ ```typescript
932
+ // Get adapter by ID
933
+ const adapter = await adapterRegistry.get('react');
934
+
935
+ // Check if adapter exists
936
+ const hasVue = adapterRegistry.has('vue');
937
+
938
+ // List adapters by tier
939
+ const tier1 = adapterRegistry.listByTier('tier-1');
940
+
941
+ // List adapters by capability
942
+ const streaming = adapterRegistry.listByCapability('streaming');
943
+
944
+ // Register custom adapter
945
+ adapterRegistry.register('custom', () => new CustomAdapter());
946
+ ```