@volter/world-core 3.0.3 → 3.0.4

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.
@@ -8,6 +8,7 @@
8
8
  import { ORIGINAL_PATH_HEADER } from "./sigv4.js";
9
9
  import { TWIN_PREFIX_HEADER } from "./twin-fetch.js";
10
10
  import { PREFIX_PARAMS_HEADER } from "./derived.js";
11
+ import { withHeaders } from "./with-headers.js";
11
12
  import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, coreFor, crossCutting, derivedRequestScopes, vendorError } from "./derived-core.js";
12
13
  import { compileSurface, createDerivedFetch, matchOperation } from "./derived.js";
13
14
  import { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, runAsVendorMove } from "./request-scope.js";
@@ -195,7 +196,7 @@ export function createPackFetch(parts, options = {}) {
195
196
  }
196
197
  if (decision.kind === 'fault')
197
198
  return Response.json(decision.result.body, { status: decision.result.status, headers: decision.result.headers ?? {} });
198
- return api(new Request(request, { headers: new Headers([...request.headers, [SCENARIO_DECISION_HEADER, JSON.stringify(decision)]]) }));
199
+ return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER, JSON.stringify(decision)]])));
199
200
  };
200
201
  const doors = (m.doors ?? []).map((d) => {
201
202
  const answer = parts.doors?.[d.id];
@@ -257,7 +258,7 @@ export function createPackFetch(parts, options = {}) {
257
258
  return vendorError(m, m.encodings?.undecodable ?? { status: 400, message: `the body does not decode as its Content-Encoding (${arrived.headers.get('content-encoding')}) says` });
258
259
  const incoming = routed(readable);
259
260
  // a twin started read-only marks every request so, so every context it opens knows (isReadOnlyRequest)
260
- const request = options.readOnly && !isReadOnlyRequest(incoming) ? new Request(incoming, { headers: new Headers([...incoming.headers, [READ_ONLY_REQUEST_HEADER, '1']]) }) : incoming;
261
+ const request = options.readOnly && !isReadOnlyRequest(incoming) ? withHeaders(incoming, new Headers([...incoming.headers, [READ_ONLY_REQUEST_HEADER, '1']])) : incoming;
261
262
  const url = new URL(request.url);
262
263
  const path = url.pathname.replace(/\/+$/, '') || '/';
263
264
  const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
@@ -400,7 +401,7 @@ function laneRouter(vendor, decl, lanes, rootServes = () => false) {
400
401
  const named = (request) => {
401
402
  const headers = new Headers(request.headers);
402
403
  headers.set(LANE_HEADER, name);
403
- return part.fetch(new Request(request, { headers, ...(request.body ? { duplex: 'half' } : {}) }));
404
+ return part.fetch(withHeaders(request, headers));
404
405
  };
405
406
  return decl.cors ? withCors(decl.cors, named) : named;
406
407
  };
package/dist/src/serve.js CHANGED
@@ -12,6 +12,7 @@
12
12
  import { AsyncLocalStorage } from 'node:async_hooks';
13
13
  import { serveHttp, setServeDecorator, WORLD_BOOT_PATH } from "./serve-http.js";
14
14
  import { performAtHead } from "./head.js";
15
+ import { withHeaders } from "./with-headers.js";
15
16
  import { createHash } from 'node:crypto';
16
17
  import { dirname, join } from 'node:path';
17
18
  import { hashFieldValue } from "./hash.js";
@@ -350,7 +351,7 @@ function journalingFetch(config, callSiteVendor) {
350
351
  if (caller !== undefined) {
351
352
  const headers = new Headers(request.headers);
352
353
  headers.delete(CALLER_HEADER);
353
- request = new Request(request, { headers });
354
+ request = withHeaders(request, headers);
354
355
  }
355
356
  // The World's own boot probe (serve-http.ts, WORLD_BOOT_PATH) is not the app's traffic: never journaled.
356
357
  if (!twinRequestJournalEnabled() || new URL(request.url).pathname === WORLD_BOOT_PATH)
@@ -0,0 +1,5 @@
1
+ /** `request` with `headers` in place of its own, its body passed through as the same stream. Bun's
2
+ * `new Request(request, { headers })` drops a streamed body (one a node:http server gives as `Readable.toWeb`, duplex
3
+ * half): reading the copy waits forever, and so does the twin that reads it. Measured on Bun 1.3.10; rebuilding from
4
+ * the URL with `body: request.body` keeps it readable. */
5
+ export declare function withHeaders(request: Request, headers: Headers): Request;
@@ -0,0 +1,8 @@
1
+ /** `request` with `headers` in place of its own, its body passed through as the same stream. Bun's
2
+ * `new Request(request, { headers })` drops a streamed body (one a node:http server gives as `Readable.toWeb`, duplex
3
+ * half): reading the copy waits forever, and so does the twin that reads it. Measured on Bun 1.3.10; rebuilding from
4
+ * the URL with `body: request.body` keeps it readable. */
5
+ export function withHeaders(request, headers) {
6
+ const body = request.method === 'GET' || request.method === 'HEAD' ? null : request.body;
7
+ return new Request(request.url, { method: request.method, headers, body, signal: request.signal, redirect: request.redirect, ...(body ? { duplex: 'half' } : {}) });
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
package/src/pack-fetch.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { ORIGINAL_PATH_HEADER } from './sigv4.ts';
9
9
  import { TWIN_PREFIX_HEADER } from './twin-fetch.ts';
10
10
  import { PREFIX_PARAMS_HEADER } from './derived.ts';
11
+ import { withHeaders } from './with-headers.ts';
11
12
  import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, coreFor, crossCutting, derivedRequestScopes, vendorError, type CoreScope, type DerivedManifest, type ErrorSpec, type Handler, type HandlerContext, type LanesDecl, type VendorManifest } from './derived-core.ts';
12
13
  import { compileSurface, createDerivedFetch, matchOperation, type DerivedFetch, type DerivedOperation, type DerivedOwner, type DerivedSurface } from './derived.ts';
13
14
  import { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, runAsVendorMove } from './request-scope.ts';
@@ -230,7 +231,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
230
231
  return vendorError(m, { status: 500, code: 'scenario_error', message: `the World's scenario: ${error.message}` });
231
232
  }
232
233
  if (decision.kind === 'fault') return Response.json(decision.result.body, { status: decision.result.status, headers: decision.result.headers ?? {} });
233
- return api(new Request(request, { headers: new Headers([...request.headers, [SCENARIO_DECISION_HEADER, JSON.stringify(decision)]]) }));
234
+ return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER, JSON.stringify(decision)]])));
234
235
  };
235
236
  const doors = (m.doors ?? []).map((d) => {
236
237
  const answer = parts.doors?.[d.id];
@@ -287,7 +288,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
287
288
  if (!readable) return vendorError(m, m.encodings?.undecodable ?? { status: 400, message: `the body does not decode as its Content-Encoding (${arrived.headers.get('content-encoding')}) says` });
288
289
  const incoming = routed(readable);
289
290
  // a twin started read-only marks every request so, so every context it opens knows (isReadOnlyRequest)
290
- const request = options.readOnly && !isReadOnlyRequest(incoming) ? new Request(incoming, { headers: new Headers([...incoming.headers, [READ_ONLY_REQUEST_HEADER, '1']]) }) : incoming;
291
+ const request = options.readOnly && !isReadOnlyRequest(incoming) ? withHeaders(incoming, new Headers([...incoming.headers, [READ_ONLY_REQUEST_HEADER, '1']])) : incoming;
291
292
  const url = new URL(request.url);
292
293
  const path = url.pathname.replace(/\/+$/, '') || '/';
293
294
  const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
@@ -407,7 +408,7 @@ function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LaneP
407
408
  const named = (request: Request): Promise<Response> => {
408
409
  const headers = new Headers(request.headers);
409
410
  headers.set(LANE_HEADER, name);
410
- return part.fetch(new Request(request, { headers, ...(request.body ? { duplex: 'half' } : {}) } as RequestInit));
411
+ return part.fetch(withHeaders(request, headers));
411
412
  };
412
413
  return decl.cors ? withCors(decl.cors, named) : named;
413
414
  };
package/src/serve.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  import { AsyncLocalStorage } from 'node:async_hooks';
13
13
  import { serveHttp, setServeDecorator, WORLD_BOOT_PATH, type HttpHandler } from './serve-http.ts';
14
14
  import { performAtHead } from './head.ts';
15
+ import { withHeaders } from './with-headers.ts';
15
16
  import { createHash } from 'node:crypto';
16
17
  import { dirname, join } from 'node:path';
17
18
  import type { SubjectFields } from './hash.ts';
@@ -405,7 +406,7 @@ function journalingFetch(config: Record<string | symbol, unknown> & { fetch: (..
405
406
  const journaling = async function (this: unknown, request: Request, server: unknown) {
406
407
  // who made it is read here and goes no further: no twin's handler (a tunnel's, a proxy's) hands it on
407
408
  const sent = request.headers.get(CALLER_HEADER); const caller = sent === null ? undefined : callerFromHeader(sent);
408
- if (caller !== undefined) { const headers = new Headers(request.headers); headers.delete(CALLER_HEADER); request = new Request(request, { headers }); }
409
+ if (caller !== undefined) { const headers = new Headers(request.headers); headers.delete(CALLER_HEADER); request = withHeaders(request, headers); }
409
410
  // The World's own boot probe (serve-http.ts, WORLD_BOOT_PATH) is not the app's traffic: never journaled.
410
411
  if (!twinRequestJournalEnabled() || new URL(request.url).pathname === WORLD_BOOT_PATH) return inner.call(this, request, server);
411
412
  const slot: { identity?: TwinJournalIdentity } = {};
@@ -0,0 +1,8 @@
1
+ /** `request` with `headers` in place of its own, its body passed through as the same stream. Bun's
2
+ * `new Request(request, { headers })` drops a streamed body (one a node:http server gives as `Readable.toWeb`, duplex
3
+ * half): reading the copy waits forever, and so does the twin that reads it. Measured on Bun 1.3.10; rebuilding from
4
+ * the URL with `body: request.body` keeps it readable. */
5
+ export function withHeaders(request: Request, headers: Headers): Request {
6
+ const body = request.method === 'GET' || request.method === 'HEAD' ? null : request.body;
7
+ return new Request(request.url, { method: request.method, headers, body, signal: request.signal, redirect: request.redirect, ...(body ? { duplex: 'half' } : {}) } as RequestInit);
8
+ }