@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,366 @@
1
+ /**
2
+ * @flightdev/ui - Base Adapter Class
3
+ *
4
+ * Abstract base class providing common functionality for all UI adapters.
5
+ * Extend this class to create new framework adapters.
6
+ *
7
+ * @module @flightdev/ui/core/adapter
8
+ * @version 2.0.0
9
+ */
10
+
11
+ import type {
12
+ UIAdapterV2,
13
+ UIAdapterV1,
14
+ AdapterTier,
15
+ AdapterCapabilities,
16
+ Component,
17
+ RenderContext,
18
+ RenderResult,
19
+ StreamingOptions,
20
+ StreamingRenderResult,
21
+ Island,
22
+ IslandOptions,
23
+ DEFAULT_CAPABILITIES,
24
+ } from './types.js';
25
+
26
+ // ============================================================================
27
+ // Base Adapter Class
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Abstract base class for UI adapters.
32
+ *
33
+ * Provides common functionality and sensible defaults.
34
+ * Subclasses must implement the abstract methods.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * class MyAdapter extends BaseUIAdapter {
39
+ * readonly id = 'my-framework';
40
+ * readonly name = 'My Framework';
41
+ * readonly framework = 'my-framework';
42
+ * readonly tier = 'tier-2';
43
+ *
44
+ * async renderToString(component: Component): Promise<RenderResult> {
45
+ * // Implementation
46
+ * }
47
+ * }
48
+ * ```
49
+ */
50
+ export abstract class BaseUIAdapter implements UIAdapterV2 {
51
+ // === Abstract Properties (Must Override) ===
52
+
53
+ abstract readonly id: string;
54
+ abstract readonly name: string;
55
+ abstract readonly framework: string;
56
+ abstract readonly tier: AdapterTier;
57
+
58
+ // === Default Capabilities (Override as needed) ===
59
+
60
+ readonly capabilities: AdapterCapabilities = {
61
+ streaming: false,
62
+ partialHydration: false,
63
+ islands: false,
64
+ resumable: false,
65
+ ssg: true,
66
+ csr: true,
67
+ serverComponents: false,
68
+ };
69
+
70
+ readonly frameworkVersion?: string;
71
+
72
+ // === Abstract Methods (Must Implement) ===
73
+
74
+ abstract renderToString(
75
+ component: Component,
76
+ context?: RenderContext
77
+ ): Promise<RenderResult>;
78
+
79
+ // === Default Implementations ===
80
+
81
+ /**
82
+ * Generate hydration script.
83
+ * Override for framework-specific hydration.
84
+ */
85
+ getHydrationScript(result: RenderResult): string {
86
+ const data = JSON.stringify(result.hydrationData ?? {});
87
+ return `
88
+ window.__FLIGHT_DATA__ = ${data};
89
+ window.__FLIGHT_ADAPTER__ = '${this.id}';
90
+ import('/flight-client.js').then(m => m.hydrate?.());
91
+ `.trim();
92
+ }
93
+
94
+ /**
95
+ * Get client entry point code.
96
+ * Override for framework-specific client initialization.
97
+ */
98
+ getClientEntry(): string {
99
+ return `
100
+ // ${this.name} Client Entry
101
+ // Override getClientEntry() in your adapter for custom behavior
102
+
103
+ export function hydrate() {
104
+ console.log('[Flight] Hydrating with ${this.name}');
105
+ const data = window.__FLIGHT_DATA__;
106
+ const App = window.__FLIGHT_APP__;
107
+
108
+ if (App && typeof App.mount === 'function') {
109
+ App.mount(document.getElementById('app'), data);
110
+ }
111
+ }
112
+ `.trim();
113
+ }
114
+
115
+ // === Helper Methods (Available to all adapters) ===
116
+
117
+ /**
118
+ * Escape HTML entities to prevent XSS
119
+ */
120
+ protected escapeHtml(str: string): string {
121
+ const htmlEscapes: Record<string, string> = {
122
+ '&': '&amp;',
123
+ '<': '&lt;',
124
+ '>': '&gt;',
125
+ '"': '&quot;',
126
+ "'": '&#39;',
127
+ };
128
+ return str.replace(/[&<>"']/g, (char) => htmlEscapes[char] ?? char);
129
+ }
130
+
131
+ /**
132
+ * Serialize props to JSON for hydration
133
+ */
134
+ protected serializeProps(props: unknown): string {
135
+ if (props === undefined || props === null) {
136
+ return '{}';
137
+ }
138
+ try {
139
+ return JSON.stringify(props, this.jsonReplacer);
140
+ } catch {
141
+ console.warn(`[${this.id}] Failed to serialize props`);
142
+ return '{}';
143
+ }
144
+ }
145
+
146
+ /**
147
+ * JSON replacer for serialization
148
+ */
149
+ protected jsonReplacer(_key: string, value: unknown): unknown {
150
+ // Handle special types
151
+ if (value instanceof Map) {
152
+ return { __type: 'Map', data: Array.from(value.entries()) };
153
+ }
154
+ if (value instanceof Set) {
155
+ return { __type: 'Set', data: Array.from(value) };
156
+ }
157
+ if (value instanceof Date) {
158
+ return { __type: 'Date', data: value.toISOString() };
159
+ }
160
+ if (typeof value === 'bigint') {
161
+ return { __type: 'BigInt', data: value.toString() };
162
+ }
163
+ // Skip functions
164
+ if (typeof value === 'function') {
165
+ return undefined;
166
+ }
167
+ return value;
168
+ }
169
+
170
+ /**
171
+ * Create a hydration marker comment
172
+ */
173
+ protected createHydrationMarker(id: string, type: 'start' | 'end' = 'start'): string {
174
+ return `<!--flight:${type}:${id}-->`;
175
+ }
176
+
177
+ /**
178
+ * Wrap content with hydration markers
179
+ */
180
+ protected wrapWithMarkers(id: string, content: string): string {
181
+ return `${this.createHydrationMarker(id, 'start')}${content}${this.createHydrationMarker(id, 'end')}`;
182
+ }
183
+
184
+ /**
185
+ * Generate a unique component ID
186
+ */
187
+ protected generateId(): string {
188
+ return `f${Math.random().toString(36).slice(2, 9)}`;
189
+ }
190
+
191
+ /**
192
+ * Create timing information
193
+ */
194
+ protected createTiming(startTime: number): { total: number } {
195
+ return { total: performance.now() - startTime };
196
+ }
197
+
198
+ /**
199
+ * Merge render context with defaults
200
+ */
201
+ protected mergeContext(context?: RenderContext): RenderContext {
202
+ return {
203
+ url: '/',
204
+ ...context,
205
+ };
206
+ }
207
+
208
+ // === Optional Methods (Override if capability is enabled) ===
209
+
210
+ /**
211
+ * Render to stream (override if capabilities.streaming is true)
212
+ */
213
+ renderToStream?(
214
+ _component: Component,
215
+ _context?: RenderContext,
216
+ _options?: StreamingOptions
217
+ ): StreamingRenderResult;
218
+
219
+ /**
220
+ * Create an island (default implementation)
221
+ * Override for framework-specific island behavior.
222
+ */
223
+ createIsland(
224
+ component: unknown,
225
+ props?: Record<string, unknown>,
226
+ options?: IslandOptions
227
+ ): Island {
228
+ const id = this.generateId();
229
+ const componentName = typeof component === 'string' ? component : 'island-component';
230
+
231
+ return {
232
+ id,
233
+ component: componentName,
234
+ props: props ?? {},
235
+ placeholder: `<div data-island="${id}" data-component="${componentName}">Loading...</div>`,
236
+ options,
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Hydrate an island on the client
242
+ */
243
+ hydrateIsland?(_element: Element, _island: Island): void;
244
+
245
+ /**
246
+ * Serialize state for resumability
247
+ */
248
+ serializeState?(state: unknown): string {
249
+ return this.serializeProps(state);
250
+ }
251
+
252
+ /**
253
+ * Resume from serialized state
254
+ */
255
+ resumeFromState?(serialized: string): unknown {
256
+ return JSON.parse(serialized, this.jsonReviver);
257
+ }
258
+
259
+ /**
260
+ * JSON reviver for deserialization
261
+ */
262
+ protected jsonReviver(_key: string, value: unknown): unknown {
263
+ if (value && typeof value === 'object' && '__type' in value) {
264
+ const typed = value as { __type: string; data: unknown };
265
+ switch (typed.__type) {
266
+ case 'Map':
267
+ return new Map(typed.data as [unknown, unknown][]);
268
+ case 'Set':
269
+ return new Set(typed.data as unknown[]);
270
+ case 'Date':
271
+ return new Date(typed.data as string);
272
+ case 'BigInt':
273
+ return BigInt(typed.data as string);
274
+ }
275
+ }
276
+ return value;
277
+ }
278
+
279
+ /**
280
+ * Initialize the adapter
281
+ */
282
+ async init(): Promise<void> {
283
+ // Override for custom initialization
284
+ }
285
+
286
+ /**
287
+ * Clean up resources
288
+ */
289
+ async dispose(): Promise<void> {
290
+ // Override for custom cleanup
291
+ }
292
+ }
293
+
294
+ // ============================================================================
295
+ // V1 to V2 Adapter Wrapper
296
+ // ============================================================================
297
+
298
+ /**
299
+ * Wrap a V1 adapter to make it compatible with V2 interface.
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * const legacyAdapter = oldReact();
304
+ * const v2Adapter = wrapV1Adapter(legacyAdapter, 'tier-1');
305
+ * ```
306
+ */
307
+ export function wrapV1Adapter(
308
+ v1Adapter: UIAdapterV1,
309
+ tier: AdapterTier = 'tier-2'
310
+ ): UIAdapterV2 {
311
+ return {
312
+ id: v1Adapter.name.toLowerCase().replace(/\s+/g, '-'),
313
+ name: v1Adapter.name,
314
+ framework: v1Adapter.framework,
315
+ tier,
316
+ capabilities: {
317
+ streaming: false,
318
+ partialHydration: false,
319
+ islands: false,
320
+ resumable: false,
321
+ ssg: true,
322
+ csr: true,
323
+ serverComponents: false,
324
+ },
325
+ renderToString: v1Adapter.renderToString.bind(v1Adapter),
326
+ getHydrationScript: v1Adapter.getHydrationScript.bind(v1Adapter),
327
+ getClientEntry: v1Adapter.getClientEntry.bind(v1Adapter),
328
+ transformForSSR: v1Adapter.transformForSSR?.bind(v1Adapter),
329
+ };
330
+ }
331
+
332
+ // ============================================================================
333
+ // Utility Functions
334
+ // ============================================================================
335
+
336
+ /**
337
+ * Check if an adapter is V2 compatible
338
+ */
339
+ export function isV2Adapter(adapter: unknown): adapter is UIAdapterV2 {
340
+ return (
341
+ adapter !== null &&
342
+ typeof adapter === 'object' &&
343
+ 'id' in adapter &&
344
+ 'tier' in adapter &&
345
+ 'capabilities' in adapter
346
+ );
347
+ }
348
+
349
+ /**
350
+ * Check if an adapter supports a specific capability
351
+ */
352
+ export function hasCapability(
353
+ adapter: UIAdapterV2,
354
+ capability: keyof AdapterCapabilities
355
+ ): boolean {
356
+ return adapter.capabilities[capability] === true;
357
+ }
358
+
359
+ /**
360
+ * Get all capabilities of an adapter as an array
361
+ */
362
+ export function getCapabilities(adapter: UIAdapterV2): (keyof AdapterCapabilities)[] {
363
+ return (Object.entries(adapter.capabilities) as [keyof AdapterCapabilities, boolean][])
364
+ .filter(([, enabled]) => enabled)
365
+ .map(([capability]) => capability);
366
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @flightdev/ui - Core Module Index
3
+ *
4
+ * Core exports for the UI framework adapter system.
5
+ *
6
+ * @module @flightdev/ui/core
7
+ * @version 2.0.0
8
+ */
9
+
10
+ // Types
11
+ export type {
12
+ AdapterCapabilities,
13
+ AdapterTier,
14
+ TierInfo,
15
+ Component,
16
+ RenderContext,
17
+ RenderResult,
18
+ RenderTiming,
19
+ StreamingOptions,
20
+ StreamingRenderResult,
21
+ IslandHydrationStrategy,
22
+ IslandOptions,
23
+ Island,
24
+ UIAdapterV2,
25
+ UIAdapterV1,
26
+ UIAdapter,
27
+ UIConfig,
28
+ ComponentProps,
29
+ HasCapability,
30
+ } from './types.js';
31
+
32
+ export {
33
+ DEFAULT_CAPABILITIES,
34
+ TIER_INFO,
35
+ } from './types.js';
36
+
37
+ // Base Adapter
38
+ export {
39
+ BaseUIAdapter,
40
+ wrapV1Adapter,
41
+ isV2Adapter,
42
+ hasCapability,
43
+ getCapabilities,
44
+ } from './adapter.js';
45
+
46
+ // Registry
47
+ export type {
48
+ AdapterLoader,
49
+ AdapterMetadata,
50
+ RegistryQueryOptions,
51
+ } from './registry.js';
52
+
53
+ export {
54
+ adapterRegistry,
55
+ registerBuiltinAdapters,
56
+ } from './registry.js';