@cloudwerk/ui 0.1.0 → 0.3.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/dist/index.d.ts CHANGED
@@ -74,6 +74,21 @@ interface HtmlOptions {
74
74
  */
75
75
  headers?: Record<string, string>;
76
76
  }
77
+ /**
78
+ * Options for streaming render.
79
+ */
80
+ interface StreamRenderOptions {
81
+ /**
82
+ * HTTP status code for the response.
83
+ * @default 200
84
+ */
85
+ status?: number;
86
+ /**
87
+ * Additional response headers.
88
+ * These are merged with the default Content-Type and Transfer-Encoding headers.
89
+ */
90
+ headers?: Record<string, string>;
91
+ }
77
92
  /**
78
93
  * Props for components that receive children.
79
94
  *
@@ -162,6 +177,12 @@ declare function registerRenderer(name: string, renderer: Renderer): void;
162
177
  * // ['hono-jsx']
163
178
  */
164
179
  declare function getAvailableRenderers(): string[];
180
+ /**
181
+ * Reset renderers to default state.
182
+ *
183
+ * @internal Used for testing only - ensures test isolation.
184
+ */
185
+ declare function _resetRenderers(): void;
165
186
 
166
187
  /**
167
188
  * @cloudwerk/ui - Hono JSX Renderer
@@ -184,6 +205,33 @@ declare function getAvailableRenderers(): string[];
184
205
  * Note: Streaming support via renderToReadableStream will be added in issue #38.
185
206
  */
186
207
  declare const honoJsxRenderer: Renderer;
208
+ /**
209
+ * Create a streaming HTML Response that sends loading UI immediately,
210
+ * then streams the final content when the content promise resolves.
211
+ *
212
+ * This uses a chunked transfer encoding to send HTML in two parts:
213
+ * 1. Loading UI (sent immediately)
214
+ * 2. Final content with script to replace loading UI (sent when ready)
215
+ *
216
+ * Note: The innerHTML assignment in the client script is safe because we only
217
+ * use server-rendered content that we control. No user input is directly
218
+ * inserted into the HTML.
219
+ *
220
+ * @param loadingElement - Loading UI to show immediately (JSX element)
221
+ * @param contentPromise - Promise that resolves to final content (JSX element)
222
+ * @param options - Streaming render options
223
+ * @returns Response object with streaming HTML content
224
+ *
225
+ * @example
226
+ * const loadingElement = <Loading params={{}} searchParams={{}} pathname="/dashboard" />
227
+ * const contentPromise = (async () => {
228
+ * const data = await loader()
229
+ * return <Page {...data} />
230
+ * })()
231
+ *
232
+ * return renderStream(loadingElement, contentPromise)
233
+ */
234
+ declare function renderStream(loadingElement: unknown, contentPromise: Promise<unknown>, options?: StreamRenderOptions): Response;
187
235
 
188
236
  /**
189
237
  * @cloudwerk/ui
@@ -267,4 +315,4 @@ declare function html(content: string, options?: HtmlOptions): Response;
267
315
  */
268
316
  declare function hydrate(element: unknown, root: Element): void;
269
317
 
270
- export { type HtmlOptions, type PropsWithChildren, type RenderOptions, type Renderer, getActiveRenderer, getActiveRendererName, getAvailableRenderers, honoJsxRenderer, html, hydrate, registerRenderer, render, setActiveRenderer };
318
+ export { type HtmlOptions, type PropsWithChildren, type RenderOptions, type Renderer, type StreamRenderOptions, _resetRenderers, getActiveRenderer, getActiveRendererName, getAvailableRenderers, honoJsxRenderer, html, hydrate, registerRenderer, render, renderStream, setActiveRenderer };
package/dist/index.js CHANGED
@@ -58,6 +58,50 @@ var honoJsxRenderer = {
58
58
  );
59
59
  }
60
60
  };
61
+ function renderStream(loadingElement, contentPromise, options = {}) {
62
+ const { status = 200, headers = {} } = options;
63
+ const stream = new ReadableStream({
64
+ async start(controller) {
65
+ const encoder = new TextEncoder();
66
+ try {
67
+ const loadingHtml = String(loadingElement);
68
+ const loadingWrapper = `<!DOCTYPE html><div id="__cloudwerk_loading">${loadingHtml}</div>`;
69
+ controller.enqueue(encoder.encode(loadingWrapper));
70
+ const finalElement = await contentPromise;
71
+ const finalHtml = String(finalElement);
72
+ const replacementScript = `
73
+ <script>
74
+ (function() {
75
+ var loading = document.getElementById('__cloudwerk_loading');
76
+ if (loading) {
77
+ var content = document.getElementById('__cloudwerk_content');
78
+ if (content) {
79
+ document.body.innerHTML = content.innerHTML;
80
+ }
81
+ }
82
+ })();
83
+ </script>
84
+ <div id="__cloudwerk_content" style="display:none">${finalHtml}</div>
85
+ `;
86
+ controller.enqueue(encoder.encode(replacementScript));
87
+ controller.close();
88
+ } catch (error) {
89
+ const errorMessage = error instanceof Error ? error.message : String(error);
90
+ const errorHtml = `<div style="color:red;padding:20px;">Error loading content: ${errorMessage}</div>`;
91
+ controller.enqueue(encoder.encode(errorHtml));
92
+ controller.close();
93
+ }
94
+ }
95
+ });
96
+ return new Response(stream, {
97
+ status,
98
+ headers: {
99
+ "Content-Type": "text/html; charset=utf-8",
100
+ "Transfer-Encoding": "chunked",
101
+ ...headers
102
+ }
103
+ });
104
+ }
61
105
 
62
106
  // src/renderer.ts
63
107
  var renderers = {
@@ -91,6 +135,15 @@ function registerRenderer(name, renderer) {
91
135
  function getAvailableRenderers() {
92
136
  return Object.keys(renderers);
93
137
  }
138
+ function _resetRenderers() {
139
+ for (const name of Object.keys(renderers)) {
140
+ if (name !== "hono-jsx") {
141
+ delete renderers[name];
142
+ }
143
+ }
144
+ activeRenderer = honoJsxRenderer;
145
+ activeRendererName = "hono-jsx";
146
+ }
94
147
 
95
148
  // src/index.ts
96
149
  function render(element, options) {
@@ -103,6 +156,7 @@ function hydrate(element, root) {
103
156
  return getActiveRenderer().hydrate(element, root);
104
157
  }
105
158
  export {
159
+ _resetRenderers,
106
160
  getActiveRenderer,
107
161
  getActiveRendererName,
108
162
  getAvailableRenderers,
@@ -111,5 +165,6 @@ export {
111
165
  hydrate,
112
166
  registerRenderer,
113
167
  render,
168
+ renderStream,
114
169
  setActiveRenderer
115
170
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/ui",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "UI rendering abstraction for Cloudwerk",
5
5
  "repository": {
6
6
  "type": "git",