@volter/world-core 3.0.40 → 3.0.42
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.d.ts +7 -0
- package/dist/src/derived.js +7 -0
- package/dist/src/pack-paths.d.ts +5 -0
- package/dist/src/pack-paths.js +86 -3
- package/dist/src/runtime.d.ts +4 -4
- package/dist/src/runtime.js +3 -3
- package/package.json +1 -1
- package/src/derived.ts +9 -0
- package/src/pack-paths.ts +75 -3
- package/src/runtime.ts +4 -4
package/dist/src/derived.d.ts
CHANGED
|
@@ -175,5 +175,12 @@ export type BoardFrame = {
|
|
|
175
175
|
/** set by the kernel, never the pack: whether a built screen of the vendor's (any of its lanes') draws the url */
|
|
176
176
|
drawn?: boolean;
|
|
177
177
|
};
|
|
178
|
+
/** Host-side measurement of actual dispatch, including declared gaps. It changes no request or answer. */
|
|
179
|
+
export type DerivedDispatch = {
|
|
180
|
+
surface: DerivedSurface;
|
|
181
|
+
operation: DerivedOperation;
|
|
182
|
+
owner: DerivedOwner;
|
|
183
|
+
};
|
|
184
|
+
export declare function observeDerivedDispatch(observer: (dispatch: DerivedDispatch) => void): () => void;
|
|
178
185
|
export declare function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch;
|
|
179
186
|
export {};
|
package/dist/src/derived.js
CHANGED
|
@@ -144,6 +144,11 @@ export function pathParams(routes, pathname) {
|
|
|
144
144
|
}
|
|
145
145
|
/** The header the kernel carries a path prefix's named groups in, from routing to the call (never a client's). */
|
|
146
146
|
export const PREFIX_PARAMS_HEADER = 'x-volter-path-prefix-params';
|
|
147
|
+
let dispatchObservers;
|
|
148
|
+
export function observeDerivedDispatch(observer) {
|
|
149
|
+
(dispatchObservers ??= new Set()).add(observer);
|
|
150
|
+
return () => { dispatchObservers?.delete(observer); };
|
|
151
|
+
}
|
|
147
152
|
export function createDerivedFetch(options) {
|
|
148
153
|
const handlers = options.handlers ?? {};
|
|
149
154
|
const known = new Set(options.surface.operations.map((o) => o.id));
|
|
@@ -161,6 +166,8 @@ export function createDerivedFetch(options) {
|
|
|
161
166
|
: matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers);
|
|
162
167
|
if (!matched)
|
|
163
168
|
return fallback(request, undefined, 'no operation at this path');
|
|
169
|
+
for (const observer of dispatchObservers ?? [])
|
|
170
|
+
observer({ surface: options.surface, operation: matched.operation, owner: handlers[matched.operation.id] ? 'handler' : options.core?.owns(matched.operation) ? 'core' : 'gap' });
|
|
164
171
|
// the body read once, as text, for everything the dispatch does with it (bodyTextOf)
|
|
165
172
|
if (request.method !== 'GET' && request.method !== 'HEAD' && request.body && !(request.headers.get('content-type') ?? '').includes('multipart'))
|
|
166
173
|
tagBody(request, await request.clone().text());
|
package/dist/src/pack-paths.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** The vendor is published data, independent of its publisher's npm name. No package code is imported. */
|
|
2
|
+
export declare function packVendor(dir: string): string | undefined;
|
|
3
|
+
/** Nearest installed package wins only over another installation of the SAME package. A World's explicit
|
|
4
|
+
* package choice wins over a catalog recommendation; different publishers' versions never compete. */
|
|
5
|
+
export declare function installedVendorPacks(from: string): Map<string, string>;
|
|
1
6
|
/** Node's nearest installed package, including linked packages and hoisted workspace dependencies. */
|
|
2
7
|
export declare function installedPackageDir(from: string, name: string): string | undefined;
|
|
3
8
|
/** A pack repository itself, or the P3 repository beside a platform checkout. Applications do not acquire a
|
package/dist/src/pack-paths.js
CHANGED
|
@@ -10,6 +10,88 @@ function packageName(dir) {
|
|
|
10
10
|
return undefined;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
/** The vendor is published data, independent of its publisher's npm name. No package code is imported. */
|
|
14
|
+
export function packVendor(dir) {
|
|
15
|
+
const facts = join(dir, 'generated', 'pack-facts.json');
|
|
16
|
+
if (existsSync(facts)) {
|
|
17
|
+
const document = JSON.parse(readFileSync(facts, 'utf8'));
|
|
18
|
+
const vendors = Object.keys(document.packs ?? {});
|
|
19
|
+
if (vendors.length !== 1 || !/^[a-z][a-z0-9-]*$/.test(vendors[0]) || document.packs[vendors[0]].protocol?.declared !== '3')
|
|
20
|
+
throw new Error(`Invalid pack identity in ${facts}`);
|
|
21
|
+
return vendors[0];
|
|
22
|
+
}
|
|
23
|
+
// Source checkouts predate release facts; their conventional name remains sufficient for development.
|
|
24
|
+
const match = /^@[^/]+\/twin-([a-z][a-z0-9-]*)$/.exec(packageName(dir) ?? '');
|
|
25
|
+
return match?.[1] === 'standard' || match?.[1] === 'catalog' ? undefined : match?.[1];
|
|
26
|
+
}
|
|
27
|
+
/** Nearest installed package wins only over another installation of the SAME package. A World's explicit
|
|
28
|
+
* package choice wins over a catalog recommendation; different publishers' versions never compete. */
|
|
29
|
+
export function installedVendorPacks(from) {
|
|
30
|
+
const packages = new Map();
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const pins = new Map();
|
|
33
|
+
let at = resolve(from);
|
|
34
|
+
for (;;) {
|
|
35
|
+
const config = join(at, '.volter', 'world.json');
|
|
36
|
+
if (existsSync(config)) {
|
|
37
|
+
const document = JSON.parse(readFileSync(config, 'utf8'));
|
|
38
|
+
for (const service of document.services ?? []) {
|
|
39
|
+
const name = service.source?.package ?? service.package;
|
|
40
|
+
if (name && !pins.has(service.id))
|
|
41
|
+
pins.set(service.id, name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const modules = join(at, 'node_modules');
|
|
45
|
+
if (existsSync(modules)) {
|
|
46
|
+
const directories = readdirSync(modules, { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith('.')).flatMap((e) => e.name.startsWith('@') ? readdirSync(join(modules, e.name)).map((n) => join(modules, e.name, n)) : [join(modules, e.name)]);
|
|
47
|
+
for (const dir of directories) {
|
|
48
|
+
const name = packageName(dir);
|
|
49
|
+
if (!name || seen.has(name))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(name);
|
|
52
|
+
if (!existsSync(join(dir, 'generated', 'pack-facts.json')) && !/^@[^/]+\/twin-/.test(name))
|
|
53
|
+
continue;
|
|
54
|
+
if (!isDerivedPack(dir))
|
|
55
|
+
continue;
|
|
56
|
+
const vendor = packVendor(dir);
|
|
57
|
+
if (vendor)
|
|
58
|
+
packages.set(name, { dir, vendor });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const parent = dirname(at);
|
|
62
|
+
if (parent === at)
|
|
63
|
+
break;
|
|
64
|
+
at = parent;
|
|
65
|
+
}
|
|
66
|
+
const index = installedPackageDir(from, '@volter/twin-catalog');
|
|
67
|
+
const file = index && join(index, 'recommendations.json');
|
|
68
|
+
const recommendations = file && existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {};
|
|
69
|
+
const revokedFile = index && join(index, 'revocations.json');
|
|
70
|
+
const revocations = revokedFile && existsSync(revokedFile) ? JSON.parse(readFileSync(revokedFile, 'utf8')) : [];
|
|
71
|
+
for (const [id, name] of [...pins]) {
|
|
72
|
+
const vendor = packages.get(name)?.vendor;
|
|
73
|
+
if (vendor && vendor !== id) {
|
|
74
|
+
if (pins.has(vendor) && pins.get(vendor) !== name)
|
|
75
|
+
throw new Error(`${vendor}: a World must select one package implementation`);
|
|
76
|
+
pins.set(vendor, name);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const result = new Map();
|
|
80
|
+
for (const vendor of new Set([...packages.values()].map((p) => p.vendor))) {
|
|
81
|
+
const candidates = [...packages.entries()].filter(([, p]) => p.vendor === vendor);
|
|
82
|
+
const selected = pins.get(vendor) ?? recommendations[vendor];
|
|
83
|
+
const choice = selected ? candidates.find(([name]) => name === selected) : candidates.length === 1 ? candidates[0] : undefined;
|
|
84
|
+
if (!choice)
|
|
85
|
+
throw new Error(`${vendor}: ${selected ? `selected package ${selected} is not installed` : `multiple implementations installed (${candidates.map(([name]) => name).join(', ')})`}; name the chosen source.package in .volter/world.json`);
|
|
86
|
+
if (!pins.has(vendor)) {
|
|
87
|
+
const version = JSON.parse(readFileSync(join(choice[1].dir, 'package.json'), 'utf8')).version;
|
|
88
|
+
if (revocations.some((r) => r.package === choice[0] && r.version === version))
|
|
89
|
+
throw new Error(`${vendor}: installed ${choice[0]}@${version} is revoked; install an approved release or retain an existing explicit World pin`);
|
|
90
|
+
}
|
|
91
|
+
result.set(vendor, choice[1].dir);
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
13
95
|
/** Node's nearest installed package, including linked packages and hoisted workspace dependencies. */
|
|
14
96
|
export function installedPackageDir(from, name) {
|
|
15
97
|
let dir = resolve(from);
|
|
@@ -45,14 +127,15 @@ export function packRepository(from) {
|
|
|
45
127
|
export function vendorPackageDir(from, vendor) {
|
|
46
128
|
if (!/^[a-z][a-z0-9-]*$/.test(vendor))
|
|
47
129
|
return undefined;
|
|
48
|
-
const
|
|
49
|
-
const installed = candidate && isDerivedPack(candidate) ? candidate : undefined;
|
|
130
|
+
const installed = installedVendorPacks(from).get(vendor);
|
|
50
131
|
const repo = packRepository(from);
|
|
51
132
|
const checkout = repo === undefined ? undefined : join(repo, vendor);
|
|
52
133
|
if (!checkout || packageName(checkout) !== `@volter/twin-${vendor}` || !isDerivedPack(checkout))
|
|
53
134
|
return installed;
|
|
54
135
|
if (!installed)
|
|
55
136
|
return checkout;
|
|
137
|
+
if (packageName(installed) !== packageName(checkout))
|
|
138
|
+
return installed;
|
|
56
139
|
const major = (dir) => {
|
|
57
140
|
try {
|
|
58
141
|
return Number(/^(\d+)/.exec(JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).version ?? '')?.[1] ?? 0);
|
|
@@ -77,7 +160,7 @@ export function vendorOfPackModule(modulePath) {
|
|
|
77
160
|
for (;;) {
|
|
78
161
|
const name = packageName(dir);
|
|
79
162
|
if (name !== undefined)
|
|
80
|
-
return
|
|
163
|
+
return packVendor(dir);
|
|
81
164
|
const up = dirname(dir);
|
|
82
165
|
if (up === dir)
|
|
83
166
|
return undefined;
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { clearRegistry, getPack, hasPack, listPacks, packOf, registerPack, pullPosture, assertContinuousPullAllowed, pullOnSchedule, resolvePullVendor, DEFAULT_PULL_POSTURE, DEFAULT_PULL_TRIGGER, PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from './packRegistry.js';
|
|
1
|
+
export { clearRegistry, getPack, hasPack, listPacks, packOf, registerPack, pullPosture, assertContinuousPullAllowed, pullOnSchedule, resolvePullVendor, DEFAULT_PULL_POSTURE, DEFAULT_PULL_TRIGGER, PROTOCOL_VERSION, PROTOCOL_MAJOR, PACK_ARCHETYPES, protocolStanding } from './packRegistry.js';
|
|
2
2
|
export type { PackTransport, PullPosture, PullTrigger, PullVendor, RoundTripWrite, TwinPack } from './packRegistry.js';
|
|
3
3
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from './scenario.js';
|
|
4
4
|
export { WORLD_CLOCK_ENV, worldNow } from './world-clock.js';
|
|
@@ -6,10 +6,10 @@ export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.js';
|
|
|
6
6
|
export { ORIGINAL_PATH_HEADER } from './sigv4.js';
|
|
7
7
|
export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from './twin-fetch.js';
|
|
8
8
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.js';
|
|
9
|
-
export { compileSurface, createDerivedFetch, matchOperation } from './derived.js';
|
|
9
|
+
export { compileSurface, createDerivedFetch, matchOperation, observeDerivedDispatch } from './derived.js';
|
|
10
10
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.js';
|
|
11
11
|
export type { Traceparent } from './trace-context.js';
|
|
12
|
-
export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.js';
|
|
12
|
+
export type { DerivedCall, DerivedCoreOutcome, DerivedDispatch, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.js';
|
|
13
13
|
export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.js';
|
|
14
14
|
export { deliverEvents, fillEnvelope, setDeliveryObserver, signEvent, signEventAll, verifyEvent } from './events.js';
|
|
15
15
|
export { equalSecrets, hmac, jwks, jwtDecode, jwtSign, jwtVerify, keyId, lettersFrom, md5, sha256, SIGNING_ALGORITHMS, uuidFrom } from './signing.js';
|
|
@@ -49,7 +49,7 @@ export type { ActionProjection, ProjectedDelivery, ProjectedResource, ProjectedR
|
|
|
49
49
|
export { approveChangeset, assertMarkerBelongsTo, assertSafeChangesetName, assertValidVerifiers, buildChangeset, captureMarker, changesetContentHash, changesetHashMatches, changesetReadiness, approverOf, CHANGESET_KIND, diffLedgers, formatApplication, formatChangeset, formatRebaseReport, narrateActions, narrationDrift, rebaseChangeset, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, MARKER_KIND, normalizeChangeset, parseVerifierExpression, replayChangeset, runChangesetVerifiers, summarizeByVendor, twinWriteShape, withApplication, withVerification, worldBootMarker, WORLD_BOOT_MARKER_ID, } from './changeset.js';
|
|
50
50
|
export type { ApplyReceipt, RebaseActionResult, RebaseReport, RebaseTarget, Changeset, ChangesetAction, ChangesetApplication, CompensationEntry, ChangesetApproval, ChangesetReadiness, ChangesetVerification, ChangesetVerifier, ChangesetVerifierResult, LedgerDelta, LedgerPosition, LedgerRef, ReplayActionResult, ReplayReport, ReplayTarget, VendorSummary, WorldMarker, } from './changeset.js';
|
|
51
51
|
export { getActivePackAssets, setActivePackAssets, packAsset, assetContentType, confinedAssetPath, LocalPackAssets, BindingPackAssets, type PackAssetStore } from './pack-assets.js';
|
|
52
|
-
export { installedPackageDir, packRepository, isDerivedPack, vendorPackageDir, vendorOfPackModule } from './pack-paths.js';
|
|
52
|
+
export { installedPackageDir, installedVendorPacks, packVendor, packRepository, isDerivedPack, vendorPackageDir, vendorOfPackModule } from './pack-paths.js';
|
|
53
53
|
export * as git from './git/index.js';
|
|
54
54
|
export * as s3 from './s3/wire.js';
|
|
55
55
|
export { PLACEHOLDER_REMOTE, beginPlaceholderPull, endPlaceholderPull, placeholderPullActive, placeholderPullMarkerPath, withPlaceholderPull, placeholderEventsFor } from './placeholder-remote.js';
|
package/dist/src/runtime.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// contract: the kernel's root"). Removed v1 entry points fail explicitly. Conformance and validation tooling lives in
|
|
6
6
|
// @volter/world-tooling, a dev dependency.
|
|
7
7
|
// Pack registry — vendor twins self-describe (TwinPack) so tooling discovers them.
|
|
8
|
-
export { clearRegistry, getPack, hasPack, listPacks, packOf, registerPack, pullPosture, assertContinuousPullAllowed, pullOnSchedule, resolvePullVendor, DEFAULT_PULL_POSTURE, DEFAULT_PULL_TRIGGER, PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from "./packRegistry.js";
|
|
8
|
+
export { clearRegistry, getPack, hasPack, listPacks, packOf, registerPack, pullPosture, assertContinuousPullAllowed, pullOnSchedule, resolvePullVendor, DEFAULT_PULL_POSTURE, DEFAULT_PULL_TRIGGER, PROTOCOL_VERSION, PROTOCOL_MAJOR, PACK_ARCHETYPES, protocolStanding } from "./packRegistry.js";
|
|
9
9
|
// The DELIVER verb (`emit`) — vendor-agnostic engine + CLI glue; packs declare a TwinEmitter
|
|
10
10
|
// (their event catalog, endpoints-from-state, and signed synthesis) on `TwinPack.emitter`.
|
|
11
11
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from "./scenario.js";
|
|
@@ -14,7 +14,7 @@ export { WORLD_ENV_NAMES_ENV, worldEnvValue } from "./world-env.js";
|
|
|
14
14
|
export { ORIGINAL_PATH_HEADER } from "./sigv4.js";
|
|
15
15
|
export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from "./twin-fetch.js";
|
|
16
16
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from "./request-scope.js";
|
|
17
|
-
export { compileSurface, createDerivedFetch, matchOperation } from "./derived.js";
|
|
17
|
+
export { compileSurface, createDerivedFetch, matchOperation, observeDerivedDispatch } from "./derived.js";
|
|
18
18
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from "./trace-context.js";
|
|
19
19
|
export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from "./derived-core.js";
|
|
20
20
|
export { deliverEvents, fillEnvelope, setDeliveryObserver, signEvent, signEventAll, verifyEvent } from "./events.js";
|
|
@@ -68,7 +68,7 @@ export { appendAction, appendTransactionCommit, checkPrecondition, confirmAction
|
|
|
68
68
|
// world discovery and thin verbs on top.
|
|
69
69
|
export { approveChangeset, assertMarkerBelongsTo, assertSafeChangesetName, assertValidVerifiers, buildChangeset, captureMarker, changesetContentHash, changesetHashMatches, changesetReadiness, approverOf, CHANGESET_KIND, diffLedgers, formatApplication, formatChangeset, formatRebaseReport, narrateActions, narrationDrift, rebaseChangeset, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, MARKER_KIND, normalizeChangeset, parseVerifierExpression, replayChangeset, runChangesetVerifiers, summarizeByVendor, twinWriteShape, withApplication, withVerification, worldBootMarker, WORLD_BOOT_MARKER_ID, } from "./changeset.js";
|
|
70
70
|
export { getActivePackAssets, setActivePackAssets, packAsset, assetContentType, confinedAssetPath, LocalPackAssets, BindingPackAssets } from "./pack-assets.js";
|
|
71
|
-
export { installedPackageDir, packRepository, isDerivedPack, vendorPackageDir, vendorOfPackModule } from "./pack-paths.js";
|
|
71
|
+
export { installedPackageDir, installedVendorPacks, packVendor, packRepository, isDerivedPack, vendorPackageDir, vendorOfPackModule } from "./pack-paths.js";
|
|
72
72
|
// The git plane (contract "The git plane is a kernel library"): objects, packs, refs, smart HTTP.
|
|
73
73
|
export * as git from "./git/index.js";
|
|
74
74
|
// S3's wire (contract "S3's wire is a kernel library too"): its XML, CORS, lifecycle, delete batches, ETags, body bytes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.42",
|
|
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.ts
CHANGED
|
@@ -255,6 +255,14 @@ export type BoardFrame = {
|
|
|
255
255
|
drawn?: boolean;
|
|
256
256
|
};
|
|
257
257
|
|
|
258
|
+
/** Host-side measurement of actual dispatch, including declared gaps. It changes no request or answer. */
|
|
259
|
+
export type DerivedDispatch = { surface: DerivedSurface; operation: DerivedOperation; owner: DerivedOwner };
|
|
260
|
+
let dispatchObservers: Set<(dispatch: DerivedDispatch) => void> | undefined;
|
|
261
|
+
export function observeDerivedDispatch(observer: (dispatch: DerivedDispatch) => void): () => void {
|
|
262
|
+
(dispatchObservers ??= new Set()).add(observer);
|
|
263
|
+
return () => { dispatchObservers?.delete(observer); };
|
|
264
|
+
}
|
|
265
|
+
|
|
258
266
|
export function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch {
|
|
259
267
|
const handlers = options.handlers ?? {};
|
|
260
268
|
const known = new Set(options.surface.operations.map((o) => o.id));
|
|
@@ -271,6 +279,7 @@ export function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch {
|
|
|
271
279
|
? [...routes.keys()].map((m) => matchOperation(routes, m, url.pathname, url.searchParams, request.headers)).find(Boolean)
|
|
272
280
|
: matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers);
|
|
273
281
|
if (!matched) return fallback(request, undefined, 'no operation at this path');
|
|
282
|
+
for (const observer of dispatchObservers ?? []) observer({ surface: options.surface, operation: matched.operation, owner: handlers[matched.operation.id] ? 'handler' : options.core?.owns(matched.operation) ? 'core' : 'gap' });
|
|
274
283
|
// the body read once, as text, for everything the dispatch does with it (bodyTextOf)
|
|
275
284
|
if (request.method !== 'GET' && request.method !== 'HEAD' && request.body && !(request.headers.get('content-type') ?? '').includes('multipart')) tagBody(request, await request.clone().text());
|
|
276
285
|
// the path prefix's named groups (pack-fetch.ts, `pathPrefix`) are the call's parameters too
|
package/src/pack-paths.ts
CHANGED
|
@@ -8,6 +8,78 @@ function packageName(dir: string): string | undefined {
|
|
|
8
8
|
catch { return undefined; }
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** The vendor is published data, independent of its publisher's npm name. No package code is imported. */
|
|
12
|
+
export function packVendor(dir: string): string | undefined {
|
|
13
|
+
const facts = join(dir, 'generated', 'pack-facts.json');
|
|
14
|
+
if (existsSync(facts)) {
|
|
15
|
+
const document = JSON.parse(readFileSync(facts, 'utf8')) as { packs?: Record<string, { protocol?: { declared?: string } }> };
|
|
16
|
+
const vendors = Object.keys(document.packs ?? {});
|
|
17
|
+
if (vendors.length !== 1 || !/^[a-z][a-z0-9-]*$/.test(vendors[0]!) || document.packs![vendors[0]!]!.protocol?.declared !== '3') throw new Error(`Invalid pack identity in ${facts}`);
|
|
18
|
+
return vendors[0];
|
|
19
|
+
}
|
|
20
|
+
// Source checkouts predate release facts; their conventional name remains sufficient for development.
|
|
21
|
+
const match = /^@[^/]+\/twin-([a-z][a-z0-9-]*)$/.exec(packageName(dir) ?? '');
|
|
22
|
+
return match?.[1] === 'standard' || match?.[1] === 'catalog' ? undefined : match?.[1];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Nearest installed package wins only over another installation of the SAME package. A World's explicit
|
|
26
|
+
* package choice wins over a catalog recommendation; different publishers' versions never compete. */
|
|
27
|
+
export function installedVendorPacks(from: string): Map<string, string> {
|
|
28
|
+
const packages = new Map<string, { dir: string; vendor: string }>();
|
|
29
|
+
const seen = new Set<string>();
|
|
30
|
+
const pins = new Map<string, string>();
|
|
31
|
+
let at = resolve(from);
|
|
32
|
+
for (;;) {
|
|
33
|
+
const config = join(at, '.volter', 'world.json');
|
|
34
|
+
if (existsSync(config)) {
|
|
35
|
+
const document = JSON.parse(readFileSync(config, 'utf8')) as { services?: Array<{ id: string; source?: { package?: string }; package?: string }> };
|
|
36
|
+
for (const service of document.services ?? []) {
|
|
37
|
+
const name = service.source?.package ?? service.package;
|
|
38
|
+
if (name && !pins.has(service.id)) pins.set(service.id, name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const modules = join(at, 'node_modules');
|
|
42
|
+
if (existsSync(modules)) {
|
|
43
|
+
const directories = readdirSync(modules, { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith('.')).flatMap((e) => e.name.startsWith('@') ? readdirSync(join(modules, e.name)).map((n) => join(modules, e.name, n)) : [join(modules, e.name)]);
|
|
44
|
+
for (const dir of directories) {
|
|
45
|
+
const name = packageName(dir);
|
|
46
|
+
if (!name || seen.has(name)) continue;
|
|
47
|
+
seen.add(name);
|
|
48
|
+
if (!existsSync(join(dir, 'generated', 'pack-facts.json')) && !/^@[^/]+\/twin-/.test(name)) continue;
|
|
49
|
+
if (!isDerivedPack(dir)) continue;
|
|
50
|
+
const vendor = packVendor(dir);
|
|
51
|
+
if (vendor) packages.set(name, { dir, vendor });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const parent = dirname(at); if (parent === at) break; at = parent;
|
|
55
|
+
}
|
|
56
|
+
const index = installedPackageDir(from, '@volter/twin-catalog');
|
|
57
|
+
const file = index && join(index, 'recommendations.json');
|
|
58
|
+
const recommendations = file && existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) as Record<string, string> : {};
|
|
59
|
+
const revokedFile = index && join(index, 'revocations.json');
|
|
60
|
+
const revocations = revokedFile && existsSync(revokedFile) ? JSON.parse(readFileSync(revokedFile, 'utf8')) as Array<{ package: string; version: string }> : [];
|
|
61
|
+
for (const [id, name] of [...pins]) {
|
|
62
|
+
const vendor = packages.get(name)?.vendor;
|
|
63
|
+
if (vendor && vendor !== id) {
|
|
64
|
+
if (pins.has(vendor) && pins.get(vendor) !== name) throw new Error(`${vendor}: a World must select one package implementation`);
|
|
65
|
+
pins.set(vendor, name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const result = new Map<string, string>();
|
|
69
|
+
for (const vendor of new Set([...packages.values()].map((p) => p.vendor))) {
|
|
70
|
+
const candidates = [...packages.entries()].filter(([, p]) => p.vendor === vendor);
|
|
71
|
+
const selected = pins.get(vendor) ?? recommendations[vendor];
|
|
72
|
+
const choice = selected ? candidates.find(([name]) => name === selected) : candidates.length === 1 ? candidates[0] : undefined;
|
|
73
|
+
if (!choice) throw new Error(`${vendor}: ${selected ? `selected package ${selected} is not installed` : `multiple implementations installed (${candidates.map(([name]) => name).join(', ')})`}; name the chosen source.package in .volter/world.json`);
|
|
74
|
+
if (!pins.has(vendor)) {
|
|
75
|
+
const version = (JSON.parse(readFileSync(join(choice[1].dir, 'package.json'), 'utf8')) as { version?: string }).version;
|
|
76
|
+
if (revocations.some((r) => r.package === choice[0] && r.version === version)) throw new Error(`${vendor}: installed ${choice[0]}@${version} is revoked; install an approved release or retain an existing explicit World pin`);
|
|
77
|
+
}
|
|
78
|
+
result.set(vendor, choice[1].dir);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
11
83
|
/** Node's nearest installed package, including linked packages and hoisted workspace dependencies. */
|
|
12
84
|
export function installedPackageDir(from: string, name: string): string | undefined {
|
|
13
85
|
let dir = resolve(from);
|
|
@@ -39,12 +111,12 @@ export function packRepository(from: string): string | undefined {
|
|
|
39
111
|
/** Resolve one vendor's package directory for host-side reads (assets, infrastructure launchers). */
|
|
40
112
|
export function vendorPackageDir(from: string, vendor: string): string | undefined {
|
|
41
113
|
if (!/^[a-z][a-z0-9-]*$/.test(vendor)) return undefined;
|
|
42
|
-
const
|
|
43
|
-
const installed = candidate && isDerivedPack(candidate) ? candidate : undefined;
|
|
114
|
+
const installed = installedVendorPacks(from).get(vendor);
|
|
44
115
|
const repo = packRepository(from);
|
|
45
116
|
const checkout = repo === undefined ? undefined : join(repo, vendor);
|
|
46
117
|
if (!checkout || packageName(checkout) !== `@volter/twin-${vendor}` || !isDerivedPack(checkout)) return installed;
|
|
47
118
|
if (!installed) return checkout;
|
|
119
|
+
if (packageName(installed) !== packageName(checkout)) return installed;
|
|
48
120
|
const major = (dir: string): number => {
|
|
49
121
|
try { return Number(/^(\d+)/.exec((JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { version?: string }).version ?? '')?.[1] ?? 0); } catch { return 0; }
|
|
50
122
|
};
|
|
@@ -64,7 +136,7 @@ export function vendorOfPackModule(modulePath: string): string | undefined {
|
|
|
64
136
|
let dir = dirname(resolve(modulePath));
|
|
65
137
|
for (;;) {
|
|
66
138
|
const name = packageName(dir);
|
|
67
|
-
if (name !== undefined) return
|
|
139
|
+
if (name !== undefined) return packVendor(dir);
|
|
68
140
|
const up = dirname(dir);
|
|
69
141
|
if (up === dir) return undefined;
|
|
70
142
|
dir = up;
|
package/src/runtime.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// contract: the kernel's root"). Removed v1 entry points fail explicitly. Conformance and validation tooling lives in
|
|
6
6
|
// @volter/world-tooling, a dev dependency.
|
|
7
7
|
// Pack registry — vendor twins self-describe (TwinPack) so tooling discovers them.
|
|
8
|
-
export { clearRegistry, getPack, hasPack, listPacks, packOf, registerPack, pullPosture, assertContinuousPullAllowed, pullOnSchedule, resolvePullVendor, DEFAULT_PULL_POSTURE, DEFAULT_PULL_TRIGGER, PROTOCOL_VERSION, PROTOCOL_MAJOR, protocolStanding } from './packRegistry.ts';
|
|
8
|
+
export { clearRegistry, getPack, hasPack, listPacks, packOf, registerPack, pullPosture, assertContinuousPullAllowed, pullOnSchedule, resolvePullVendor, DEFAULT_PULL_POSTURE, DEFAULT_PULL_TRIGGER, PROTOCOL_VERSION, PROTOCOL_MAJOR, PACK_ARCHETYPES, protocolStanding } from './packRegistry.ts';
|
|
9
9
|
export type { PackTransport, PullPosture, PullTrigger, PullVendor, RoundTripWrite, TwinPack } from './packRegistry.ts';
|
|
10
10
|
// The DELIVER verb (`emit`) — vendor-agnostic engine + CLI glue; packs declare a TwinEmitter
|
|
11
11
|
// (their event catalog, endpoints-from-state, and signed synthesis) on `TwinPack.emitter`.
|
|
@@ -15,10 +15,10 @@ export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.ts';
|
|
|
15
15
|
export { ORIGINAL_PATH_HEADER } from './sigv4.ts';
|
|
16
16
|
export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from './twin-fetch.ts';
|
|
17
17
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.ts';
|
|
18
|
-
export { compileSurface, createDerivedFetch, matchOperation } from './derived.ts';
|
|
18
|
+
export { compileSurface, createDerivedFetch, matchOperation, observeDerivedDispatch } from './derived.ts';
|
|
19
19
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.ts';
|
|
20
20
|
export type { Traceparent } from './trace-context.ts';
|
|
21
|
-
export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.ts';
|
|
21
|
+
export type { DerivedCall, DerivedCoreOutcome, DerivedDispatch, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.ts';
|
|
22
22
|
export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.ts';
|
|
23
23
|
export { deliverEvents, fillEnvelope, setDeliveryObserver, signEvent, signEventAll, verifyEvent } from './events.ts';
|
|
24
24
|
export { equalSecrets, hmac, jwks, jwtDecode, jwtSign, jwtVerify, keyId, lettersFrom, md5, sha256, SIGNING_ALGORITHMS, uuidFrom } from './signing.ts';
|
|
@@ -291,7 +291,7 @@ export type {
|
|
|
291
291
|
WorldMarker,
|
|
292
292
|
} from './changeset.ts';
|
|
293
293
|
export { getActivePackAssets, setActivePackAssets, packAsset, assetContentType, confinedAssetPath, LocalPackAssets, BindingPackAssets, type PackAssetStore } from './pack-assets.ts';
|
|
294
|
-
export { installedPackageDir, packRepository, isDerivedPack, vendorPackageDir, vendorOfPackModule } from './pack-paths.ts';
|
|
294
|
+
export { installedPackageDir, installedVendorPacks, packVendor, packRepository, isDerivedPack, vendorPackageDir, vendorOfPackModule } from './pack-paths.ts';
|
|
295
295
|
|
|
296
296
|
// The git plane (contract "The git plane is a kernel library"): objects, packs, refs, smart HTTP.
|
|
297
297
|
export * as git from './git/index.ts';
|