@junejs/server 0.0.14 → 0.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junejs/server",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,8 +30,8 @@
30
30
  "typecheck": "tsc --noEmit"
31
31
  },
32
32
  "dependencies": {
33
- "@junejs/core": "0.0.14",
34
- "@junejs/db": "0.0.14",
33
+ "@junejs/core": "0.0.16",
34
+ "@junejs/db": "0.0.16",
35
35
  "marked": "^18.0.5",
36
36
  "rolldown": "^1.1.0",
37
37
  "postcss": "^8.4.0",
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export { loadJuneConfig } from "./config-loader";
29
29
  export {
30
30
  migrate,
31
31
  migrateApp,
32
+ typesApp,
32
33
  classify,
33
34
  readMigrations,
34
35
  blockedMessage,
package/src/migrate.ts CHANGED
@@ -112,6 +112,24 @@ export async function migrateApp(
112
112
  }
113
113
  }
114
114
 
115
+ // `june db types`: open the declared db, bring it to head (safe migrations only, so
116
+ // the schema is present even on a fresh checkout), then hand it to the data layer's
117
+ // codegen hook for the type-declaration text. Returns null when there's no db or the
118
+ // data layer doesn't generate types — the CLI owns the messaging. Introspection is
119
+ // read-only; the safe migrate is idempotent (the ledger skips applied ones).
120
+ export async function typesApp(root: string, config: JuneConfig): Promise<string | null> {
121
+ const factory = config.resources?.db;
122
+ const emit = config.dataLayer?.emitTypes;
123
+ if (!factory || !emit) return null;
124
+ const db = await factory.open();
125
+ try {
126
+ await migrate(db, join(root, "db", "migrations"), { allowDestructive: false });
127
+ return await emit(db);
128
+ } finally {
129
+ await db.close();
130
+ }
131
+ }
132
+
115
133
  // The "this migration was gated" guidance, shared by dev (warns, keeps serving)
116
134
  // and `june db migrate` (exits non-zero).
117
135
  export function blockedMessage(m: ClassifiedMigration): string {
package/src/pipeline.ts CHANGED
@@ -124,25 +124,6 @@ function StreamedView({
124
124
  return provideLoaderData(data, def.view ? def.view(data, ctx) : null);
125
125
  }
126
126
 
127
- // renderToReadableStream does not emit the doctype; prepend it without buffering
128
- // the React stream (streaming stays incremental).
129
- function withDoctype(source: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
130
- const reader = source.getReader();
131
- return new ReadableStream<Uint8Array>({
132
- start(controller) {
133
- controller.enqueue(new TextEncoder().encode("<!doctype html>\n"));
134
- },
135
- async pull(controller) {
136
- const { done, value } = await reader.read();
137
- if (done) controller.close();
138
- else controller.enqueue(value);
139
- },
140
- cancel(reason) {
141
- void reader.cancel(reason);
142
- },
143
- });
144
- }
145
-
146
127
  // The default favicon: the site name's first character in a rounded square —
147
128
  // a plain SVG string, so it needs no fonts, no rasterizer, and works for CJK
148
129
  // names as readily as latin ones. Served at /favicon.svg AND /favicon.ico
@@ -187,8 +168,9 @@ export function createPipeline(cfg: PipelineConfig): Pipeline {
187
168
  React.createElement(Document, { config: docConfigForRender(), metadata, children: wrapped }),
188
169
  );
189
170
  await stream.allReady; // fully resolved markup (no streamed Suspense fallbacks)
190
- const html = "<!doctype html>\n" + (await new Response(stream).text());
191
- return new Response(html, { status, headers: htmlHeaders() });
171
+ // React 19 emits <!DOCTYPE html> itself for an <html> root — don't prepend a
172
+ // second one.
173
+ return new Response(stream, { status, headers: htmlHeaders() });
192
174
  }
193
175
 
194
176
  // Route A: the [data-june-root] inner HTML for a soft-nav / live-apply request
@@ -249,8 +231,9 @@ export function createPipeline(cfg: PipelineConfig): Pipeline {
249
231
  React.createElement(Document, { config: docConfigForRender(), metadata, children: wrapped }),
250
232
  { onError: (e: unknown) => console.error("[june] streaming render error:", e) },
251
233
  );
252
- // NO allReady — return the live stream (shell first), doctype prepended.
253
- return new Response(withDoctype(stream), { status: 200, headers: htmlHeaders() });
234
+ // NO allReady — return the live stream (shell first). React 19 streams the
235
+ // <!DOCTYPE html> as the first bytes, so nothing to prepend.
236
+ return new Response(stream, { status: 200, headers: htmlHeaders() });
254
237
  }
255
238
 
256
239
  function notFoundResponse(target: RenderTarget, pathname: string): Promise<Response> | Response {
@@ -300,6 +283,23 @@ export function createPipeline(cfg: PipelineConfig): Pipeline {
300
283
  return notFoundResponse(target, ctx.url.pathname);
301
284
  }
302
285
 
286
+ const res = await renderTarget(target, def, data, ctx, chain);
287
+ // Every projection here is chosen by the Accept header at the SAME url (a full
288
+ // page, its fragment, its .md and .json all live at the clean path). Without
289
+ // Vary, a shared/browser cache can hand a soft-nav fragment to a real page
290
+ // load — which surfaces as a document that starts mid-body. Make Accept part
291
+ // of the cache key.
292
+ res.headers.set("vary", "accept");
293
+ return res;
294
+ }
295
+
296
+ async function renderTarget(
297
+ target: RenderTarget,
298
+ def: Resolved["def"],
299
+ data: unknown,
300
+ ctx: RouteContext,
301
+ chain: LayoutComponent[],
302
+ ): Promise<Response> {
303
303
  if (target === "md") return renderMarkdown(def, data, ctx);
304
304
  if (target === "json") {
305
305
  // Convention: a json() fn customizes; absent → serialize the loader data.