@volter/world-core 3.0.126 → 3.0.128
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/src/derived-core.d.ts +2 -0
- package/dist/src/derived-core.js +12 -4
- package/dist/src/pack-fetch.js +2 -2
- package/package.json +1 -1
- package/src/derived-core.ts +12 -4
- package/src/pack-fetch.ts +2 -2
|
@@ -1151,6 +1151,8 @@ export declare function coreFor(m: DerivedManifest, scope?: CoreScope): {
|
|
|
1151
1151
|
/** The header the kernel names a scenario's decision in, set by createPackFetch alone (it strips it from every request it
|
|
1152
1152
|
* is sent). */
|
|
1153
1153
|
export declare const SCENARIO_DECISION_HEADER = "x-volter-scenario-decision";
|
|
1154
|
+
/** HTTP field values are bytes; JSON escapes retain Unicode without putting it directly in a header. */
|
|
1155
|
+
export declare function scenarioDecisionHeader(decision: ScenarioDecision): string;
|
|
1154
1156
|
export declare function authRefusal(m: DerivedManifest, request: Request, root?: string): Response | undefined;
|
|
1155
1157
|
export declare function crossCutting(m: DerivedManifest, opts?: CoreScope & {
|
|
1156
1158
|
readOnly?: boolean;
|
package/dist/src/derived-core.js
CHANGED
|
@@ -697,6 +697,8 @@ function recordedInput(m, call, merged) {
|
|
|
697
697
|
}
|
|
698
698
|
/** The scope each call is served in (serveCore, contextFor), for the contexts a write opens for its hooks. */
|
|
699
699
|
const scopeOfCall = new WeakMap();
|
|
700
|
+
/** The intact copy contextFor already keeps for core(), before a handler can consume its request. */
|
|
701
|
+
const requestOfCall = new WeakMap();
|
|
700
702
|
async function writeDetailed(m, call, resource, id, fields, operation, params, root, occurredAt, checks) {
|
|
701
703
|
const before = stateBeforeWrite(m, resource, id, root);
|
|
702
704
|
const withFiles = async (input) => { const files = input.operationId ? await keptFiles(m, call, root) : undefined; return files ? { ...input, files } : input; };
|
|
@@ -736,16 +738,17 @@ async function finishWrite(m, call, resource, row, result, operation, params, ro
|
|
|
736
738
|
const body = view(m, resource, row);
|
|
737
739
|
// the write's own context for its hooks, in the scope the call was served in (a walk's application answers, a tenant,
|
|
738
740
|
// the managed database), at the write's moment
|
|
739
|
-
const
|
|
741
|
+
const request = requestOfCall.get(call) ?? call.request;
|
|
742
|
+
const context = () => contextFor(m, { ...call, request: request.clone() }, { ...(scopeOfCall.get(call) ?? {}), ...(root !== undefined ? { root } : {}), clock: () => occurredAt });
|
|
740
743
|
if (m.onWrite)
|
|
741
|
-
await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request:
|
|
744
|
+
await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
|
|
742
745
|
if (m.events) {
|
|
743
746
|
const events = m.events;
|
|
744
|
-
const write = { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request:
|
|
747
|
+
const write = { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
|
|
745
748
|
await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values(await context(), write, type) : undefined);
|
|
746
749
|
}
|
|
747
750
|
if (m.sockets?.length)
|
|
748
|
-
socketWrite(m.service, root, { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request:
|
|
751
|
+
socketWrite(m.service, root, { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
|
|
749
752
|
// a read held until the World writes (held-reads.ts) hears it
|
|
750
753
|
heldReadsWrite(m.service, root, storedType(m, resource));
|
|
751
754
|
// the subject's id as it stands after the write: the vendor's, when a live head minted one
|
|
@@ -1311,6 +1314,7 @@ export async function contextFor(m, call, scope) {
|
|
|
1311
1314
|
// every read of the body is of a copy, so the handler's own `call.request` and the core's answer (`core()`) each
|
|
1312
1315
|
// still find it unread, whichever of them reads it first
|
|
1313
1316
|
const pristine = call.request.clone();
|
|
1317
|
+
requestOfCall.set(call, pristine);
|
|
1314
1318
|
const params = await boundaryParams(m, { ...call, request: call.request.clone() }, root).catch((error) => {
|
|
1315
1319
|
// An around context opens before the credential/JSON guard. Keep non-body parameters available to it without
|
|
1316
1320
|
// turning the guard's declared malformed-body refusal into a context-construction exception.
|
|
@@ -1582,6 +1586,10 @@ const hex = (s) => Array.from(new TextEncoder().encode(s), (b) => b.toString(16)
|
|
|
1582
1586
|
/** The header the kernel names a scenario's decision in, set by createPackFetch alone (it strips it from every request it
|
|
1583
1587
|
* is sent). */
|
|
1584
1588
|
export const SCENARIO_DECISION_HEADER = 'x-volter-scenario-decision';
|
|
1589
|
+
/** HTTP field values are bytes; JSON escapes retain Unicode without putting it directly in a header. */
|
|
1590
|
+
export function scenarioDecisionHeader(decision) {
|
|
1591
|
+
return JSON.stringify(decision).replace(/[\u007f-\uffff]/g, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
1592
|
+
}
|
|
1585
1593
|
function scenarioDecisionOf(request) {
|
|
1586
1594
|
const raw = request.headers.get(SCENARIO_DECISION_HEADER);
|
|
1587
1595
|
if (!raw)
|
package/dist/src/pack-fetch.js
CHANGED
|
@@ -11,7 +11,7 @@ import { ORIGINAL_PATH_HEADER } from "./sigv4.js";
|
|
|
11
11
|
import { placedCookies, TWIN_PREFIX_HEADER } from "./twin-fetch.js";
|
|
12
12
|
import { PREFIX_PARAMS_HEADER } from "./derived.js";
|
|
13
13
|
import { withHeaders } from "./with-headers.js";
|
|
14
|
-
import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, coreFor, crossCutting, derivedRequestScopes, vendorError } from "./derived-core.js";
|
|
14
|
+
import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, scenarioDecisionHeader, coreFor, crossCutting, derivedRequestScopes, vendorError } from "./derived-core.js";
|
|
15
15
|
import { compileSurface, createDerivedFetch, matchOperation, pathParams } from "./derived.js";
|
|
16
16
|
import { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, runAsVendorMove } from "./request-scope.js";
|
|
17
17
|
import { parseScenarioDocument, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from "./scenario.js";
|
|
@@ -220,7 +220,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
220
220
|
}
|
|
221
221
|
if (decision.kind === 'fault')
|
|
222
222
|
return Response.json(decision.result.body, { status: decision.result.status, headers: decision.result.headers ?? {} });
|
|
223
|
-
return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER,
|
|
223
|
+
return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER, scenarioDecisionHeader(decision)]])));
|
|
224
224
|
};
|
|
225
225
|
const doors = (m.doors ?? []).map((d) => {
|
|
226
226
|
const answer = parts.doors?.[d.id];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.128",
|
|
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/derived-core.ts
CHANGED
|
@@ -1165,6 +1165,8 @@ function recordedInput(m: DerivedManifest, call: DerivedCall, merged: Record<str
|
|
|
1165
1165
|
|
|
1166
1166
|
/** The scope each call is served in (serveCore, contextFor), for the contexts a write opens for its hooks. */
|
|
1167
1167
|
const scopeOfCall = new WeakMap<DerivedCall, CoreScope>();
|
|
1168
|
+
/** The intact copy contextFor already keeps for core(), before a handler can consume its request. */
|
|
1169
|
+
const requestOfCall = new WeakMap<DerivedCall, Request>();
|
|
1168
1170
|
|
|
1169
1171
|
async function writeDetailed(m: DerivedManifest, call: DerivedCall, resource: string, id: string, fields: Record<string, unknown>, operation: string, params: Record<string, unknown>, root: string | undefined, occurredAt: string, checks?: StateChecks): Promise<{ body: Record<string, unknown>; id: string; vendorData?: unknown }> {
|
|
1170
1172
|
const before = stateBeforeWrite(m, resource, id, root);
|
|
@@ -1215,14 +1217,15 @@ async function finishWrite(m: DerivedManifest, call: DerivedCall, resource: stri
|
|
|
1215
1217
|
const body = view(m, resource, row);
|
|
1216
1218
|
// the write's own context for its hooks, in the scope the call was served in (a walk's application answers, a tenant,
|
|
1217
1219
|
// the managed database), at the write's moment
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
+
const request = requestOfCall.get(call) ?? call.request;
|
|
1221
|
+
const context = (): Promise<SemanticsContext> => contextFor(m, { ...call, request: request.clone() }, { ...(scopeOfCall.get(call) ?? {}), ...(root !== undefined ? { root } : {}), clock: () => occurredAt });
|
|
1222
|
+
if (m.onWrite) await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone(), context });
|
|
1220
1223
|
if (m.events) {
|
|
1221
1224
|
const events = m.events;
|
|
1222
|
-
const write = { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request:
|
|
1225
|
+
const write = { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() };
|
|
1223
1226
|
await deliverEvents(m.service, events, write, async (type) => (events.render ? events.render(await context(), write, type) : body), undefined, events.values ? async (type) => events.values!(await context(), write, type) : undefined);
|
|
1224
1227
|
}
|
|
1225
|
-
if (m.sockets?.length) socketWrite(m.service, root, { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request:
|
|
1228
|
+
if (m.sockets?.length) socketWrite(m.service, root, { operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: request.clone() });
|
|
1226
1229
|
// a read held until the World writes (held-reads.ts) hears it
|
|
1227
1230
|
heldReadsWrite(m.service, root, storedType(m, resource));
|
|
1228
1231
|
// the subject's id as it stands after the write: the vendor's, when a live head minted one
|
|
@@ -1929,6 +1932,7 @@ export async function contextFor(m: DerivedManifest, call: DerivedCall, scope: C
|
|
|
1929
1932
|
// every read of the body is of a copy, so the handler's own `call.request` and the core's answer (`core()`) each
|
|
1930
1933
|
// still find it unread, whichever of them reads it first
|
|
1931
1934
|
const pristine = call.request.clone();
|
|
1935
|
+
requestOfCall.set(call, pristine);
|
|
1932
1936
|
const params = await boundaryParams(m, { ...call, request: call.request.clone() }, root).catch((error) => {
|
|
1933
1937
|
// An around context opens before the credential/JSON guard. Keep non-body parameters available to it without
|
|
1934
1938
|
// turning the guard's declared malformed-body refusal into a context-construction exception.
|
|
@@ -2184,6 +2188,10 @@ const hex = (s: string): string => Array.from(new TextEncoder().encode(s), (b) =
|
|
|
2184
2188
|
/** The header the kernel names a scenario's decision in, set by createPackFetch alone (it strips it from every request it
|
|
2185
2189
|
* is sent). */
|
|
2186
2190
|
export const SCENARIO_DECISION_HEADER = 'x-volter-scenario-decision';
|
|
2191
|
+
/** HTTP field values are bytes; JSON escapes retain Unicode without putting it directly in a header. */
|
|
2192
|
+
export function scenarioDecisionHeader(decision: ScenarioDecision): string {
|
|
2193
|
+
return JSON.stringify(decision).replace(/[\u007f-\uffff]/g, (unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
2194
|
+
}
|
|
2187
2195
|
function scenarioDecisionOf(request: Request): ScenarioDecision | undefined {
|
|
2188
2196
|
const raw = request.headers.get(SCENARIO_DECISION_HEADER);
|
|
2189
2197
|
if (!raw) return undefined;
|
package/src/pack-fetch.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { ORIGINAL_PATH_HEADER } from './sigv4.ts';
|
|
|
11
11
|
import { placedCookies, TWIN_PREFIX_HEADER } from './twin-fetch.ts';
|
|
12
12
|
import { PREFIX_PARAMS_HEADER } from './derived.ts';
|
|
13
13
|
import { withHeaders } from './with-headers.ts';
|
|
14
|
-
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';
|
|
14
|
+
import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, scenarioDecisionHeader, coreFor, crossCutting, derivedRequestScopes, vendorError, type CoreScope, type DerivedManifest, type ErrorSpec, type Handler, type HandlerContext, type LanesDecl, type VendorManifest } from './derived-core.ts';
|
|
15
15
|
import { compileSurface, createDerivedFetch, matchOperation, pathParams, type DerivedFetch, type DerivedOperation, type DerivedOwner, type DerivedSurface, type Workspace, type BoardFrame } from './derived.ts';
|
|
16
16
|
import { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, runAsVendorMove } from './request-scope.ts';
|
|
17
17
|
import { parseScenarioDocument, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest, type PackScenarioAdapter } from './scenario.ts';
|
|
@@ -274,7 +274,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
274
274
|
return vendorError(m, { status: 500, code: 'scenario_error', message: `the World's scenario: ${error.message}` });
|
|
275
275
|
}
|
|
276
276
|
if (decision.kind === 'fault') return Response.json(decision.result.body, { status: decision.result.status, headers: decision.result.headers ?? {} });
|
|
277
|
-
return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER,
|
|
277
|
+
return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER, scenarioDecisionHeader(decision)]])));
|
|
278
278
|
};
|
|
279
279
|
const doors = (m.doors ?? []).map((d) => {
|
|
280
280
|
const answer = parts.doors?.[d.id];
|