@volter/twin-standard 1.0.88 → 1.0.89
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/assessment.js +1 -1
- package/dist/src/conformance.js +1 -1
- package/dist/src/walk/behavior-journey.d.ts +3 -1
- package/dist/src/walk/behavior-journey.js +11 -7
- package/dist/src/walk/pack-directory.d.ts +1 -1
- package/dist/src/walk/pack-directory.js +2 -2
- package/dist/src/walk/vendor-fetch.d.ts +3 -3
- package/dist/src/walk/vendor-fetch.js +6 -6
- package/package.json +2 -2
- package/src/assessment.ts +1 -1
- package/src/conformance.ts +1 -1
- package/src/walk/behavior-journey.ts +11 -7
- package/src/walk/pack-directory.ts +2 -2
- package/src/walk/vendor-fetch.ts +6 -6
package/dist/src/assessment.js
CHANGED
|
@@ -36,7 +36,7 @@ export async function assessPack(packDir, options = {}) {
|
|
|
36
36
|
const units = [packDir, ...lanes.map((name) => join(packDir, name))];
|
|
37
37
|
if (packDir.split(/[\\/]/).at(-1) !== vendor)
|
|
38
38
|
throw new Error('Place the artifact in a directory named for its vendor before assessment');
|
|
39
|
-
usePacks(join(packDir, '..'));
|
|
39
|
+
usePacks(join(packDir, '..'), { dependenciesFrom: packDir });
|
|
40
40
|
const gates = await p3Gate(units);
|
|
41
41
|
const life = expandFills(read(join(packDir, 'journeys/customer-life.json')));
|
|
42
42
|
if (!Array.isArray(life.steps) || !life.steps.length)
|
package/dist/src/conformance.js
CHANGED
|
@@ -356,7 +356,7 @@ async function lifeServed(pack) {
|
|
|
356
356
|
if (!existsSync(lifePath))
|
|
357
357
|
return ['journeys/customer-life.json is missing'];
|
|
358
358
|
const { inMemoryWorld, usePacks, walk } = await import("./walk/behavior-journey.js");
|
|
359
|
-
usePacks(dirname(pack));
|
|
359
|
+
usePacks(dirname(pack), { dependenciesFrom: pack });
|
|
360
360
|
const env = globalThis.process.env;
|
|
361
361
|
const served = async (root, options) => {
|
|
362
362
|
const before = env.VOLTER_WORLD_BOOT_ID;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type TwinStream } from '@volter/world-core/runtime';
|
|
2
2
|
import { type Captured, type GitStep, type Journey, type JourneyStep, type LineStep, type RespStep } from './journey-kit.js';
|
|
3
|
-
export declare function usePacks(dir: string
|
|
3
|
+
export declare function usePacks(dir: string, options?: {
|
|
4
|
+
dependenciesFrom?: string;
|
|
5
|
+
}): void;
|
|
4
6
|
/** The walk's World: the twin's own code on its in-memory World and blob stores (the backends a serverless
|
|
5
7
|
* entry runs on), so a walk of any length is state changes in memory and never touches the disk; the World
|
|
6
8
|
* clock, pinned to 2026-01-01, is world state in the same store, and the World is sealed. Each root is a fresh key
|
|
@@ -22,6 +22,8 @@ import { walkPackDirectory } from "./pack-directory.js";
|
|
|
22
22
|
import { xmlMembers } from "./xml-view.js";
|
|
23
23
|
/** The pack repository the walk finds a vendor's pack in, set by the caller before its first walk. */
|
|
24
24
|
let packsRoot;
|
|
25
|
+
/** Installed companions resolve from the selected pack, including its workspace-local dependencies. */
|
|
26
|
+
let dependenciesRoot;
|
|
25
27
|
/** Where a socket step opens: an absolute `ws://` or `wss://` url on its own host, a path on the life's host otherwise. */
|
|
26
28
|
function socketAt(open, host) {
|
|
27
29
|
if (!/^wss?:\/\//.test(open))
|
|
@@ -34,8 +36,9 @@ let walkFacts = baselineFacts;
|
|
|
34
36
|
let walkHosts = createHostTable(walkFacts).hosts;
|
|
35
37
|
const directories = new Map();
|
|
36
38
|
const installedFacts = new Set();
|
|
37
|
-
export function usePacks(dir) {
|
|
39
|
+
export function usePacks(dir, options = {}) {
|
|
38
40
|
packsRoot = dir;
|
|
41
|
+
dependenciesRoot = options.dependenciesFrom ?? dir;
|
|
39
42
|
const installed = {};
|
|
40
43
|
for (const name of readdirSync(dir)) {
|
|
41
44
|
const file = join(dir, name, 'generated/pack-facts.json');
|
|
@@ -53,9 +56,10 @@ export function usePacks(dir) {
|
|
|
53
56
|
}
|
|
54
57
|
const packsDir = () => { if (packsRoot === undefined)
|
|
55
58
|
throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
|
|
59
|
+
const dependenciesDir = () => dependenciesRoot ?? packsDir();
|
|
56
60
|
function directoryFor(vendor) {
|
|
57
61
|
if (!directories.has(vendor)) {
|
|
58
|
-
const directory = walkPackDirectory(packsDir(), vendor);
|
|
62
|
+
const directory = walkPackDirectory(packsDir(), vendor, dependenciesDir());
|
|
59
63
|
directories.set(vendor, directory);
|
|
60
64
|
if (directory) {
|
|
61
65
|
const name = vendor.split('/')[0];
|
|
@@ -119,7 +123,7 @@ function defaultType(body) {
|
|
|
119
123
|
return 'text/plain;charset=UTF-8';
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
|
-
const vendorFetch = (vendor) => vendorFetchFrom(packsDir(), vendor);
|
|
126
|
+
const vendorFetch = (vendor) => vendorFetchFrom(packsDir(), vendor, dependenciesDir());
|
|
123
127
|
const shapes = new Map();
|
|
124
128
|
const shapeOf = (vendor) => { if (!shapes.has(vendor))
|
|
125
129
|
shapes.set(vendor, shapeChecker(directoryFor(vendor))); return shapes.get(vendor); };
|
|
@@ -218,7 +222,7 @@ export async function walk(journey, root, collect, opts) {
|
|
|
218
222
|
return {};
|
|
219
223
|
}
|
|
220
224
|
const factory = await vendorFetch(journey.vendor);
|
|
221
|
-
const views = await vendorAnswerViews(packsDir(), journey.vendor);
|
|
225
|
+
const views = await vendorAnswerViews(packsDir(), journey.vendor, dependenciesDir());
|
|
222
226
|
if (!factory) {
|
|
223
227
|
collect.push({ step: '(setup)', beat: 'ADAPTER', detail: `no create<Name>TwinFetch adapter enrolled for ${journey.vendor}` });
|
|
224
228
|
return {};
|
|
@@ -370,7 +374,7 @@ export async function walk(journey, root, collect, opts) {
|
|
|
370
374
|
}
|
|
371
375
|
const viaServed = served?.stream;
|
|
372
376
|
if (!viaServed)
|
|
373
|
-
streams ??= await vendorStream(packsDir(), journey.vendor);
|
|
377
|
+
streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
|
|
374
378
|
if (!viaServed && !streams) {
|
|
375
379
|
collect.push({ step: step.name, beat: 'ADAPTER', detail: `no create<Name>TwinStream adapter enrolled for ${journey.vendor}` });
|
|
376
380
|
return;
|
|
@@ -437,7 +441,7 @@ export async function walk(journey, root, collect, opts) {
|
|
|
437
441
|
if (served?.stream && Object.keys(step.connect).length)
|
|
438
442
|
throw new Error('a served RESP walk configures its server, not the client connection');
|
|
439
443
|
if (!served?.stream)
|
|
440
|
-
streams ??= await vendorStream(packsDir(), journey.vendor);
|
|
444
|
+
streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
|
|
441
445
|
const factory = served?.stream ?? (respStream ??= streams?.(root, step.connect));
|
|
442
446
|
if (!factory)
|
|
443
447
|
throw new Error('no byte-stream adapter');
|
|
@@ -511,7 +515,7 @@ export async function walk(journey, root, collect, opts) {
|
|
|
511
515
|
let body;
|
|
512
516
|
if (step.signIn) {
|
|
513
517
|
if (!served?.stream)
|
|
514
|
-
streams ??= await vendorStream(packsDir(), journey.vendor);
|
|
518
|
+
streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
|
|
515
519
|
const factory = served?.stream ?? streams?.(root, {});
|
|
516
520
|
if (!factory)
|
|
517
521
|
throw new Error(`no create<Name>TwinStream adapter enrolled for ${journey.vendor}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function walkPackDirectory(from: string, vendor: string): string | undefined;
|
|
1
|
+
export declare function walkPackDirectory(from: string, vendor: string, dependenciesFrom?: string): string | undefined;
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { installedVendorPacks, isDerivedPack, packVendor } from '@volter/world-core/runtime';
|
|
5
|
-
export function walkPackDirectory(from, vendor) {
|
|
5
|
+
export function walkPackDirectory(from, vendor, dependenciesFrom = from) {
|
|
6
6
|
if (!/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?$/.test(vendor))
|
|
7
7
|
return undefined;
|
|
8
8
|
const [name, lane] = vendor.split('/');
|
|
9
9
|
const sibling = join(from, name);
|
|
10
10
|
const root = isDerivedPack(sibling) && packVendor(sibling) === name
|
|
11
11
|
? sibling
|
|
12
|
-
: installedVendorPacks(
|
|
12
|
+
: installedVendorPacks(dependenciesFrom, { vendors: [name] }).get(name);
|
|
13
13
|
const unit = root && (lane ? join(root, lane) : root);
|
|
14
14
|
return unit && existsSync(join(unit, 'src', 'index.ts')) ? unit : undefined;
|
|
15
15
|
}
|
|
@@ -7,14 +7,14 @@ export type VendorFetchFactory = (root: string, options?: {
|
|
|
7
7
|
scenarioPath?: string;
|
|
8
8
|
application?: ApplicationStandIn;
|
|
9
9
|
}) => (request: Request) => Response | Promise<Response>;
|
|
10
|
-
export declare function vendorFetch(twinDir: string, vendor: string): Promise<VendorFetchFactory | undefined>;
|
|
10
|
+
export declare function vendorFetch(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<VendorFetchFactory | undefined>;
|
|
11
11
|
/** Named views of an answer: what a journey step's `view` asserts against. The kit's own: `jwt:<field>`, the payload of the
|
|
12
12
|
* JSON Web Token at that field of the answer, decoded as the application verifying it reads it (a session token's
|
|
13
13
|
* claims). A pack's own (`answerViews` in its index) beside them. */
|
|
14
14
|
export type AnswerViews = Record<string, (body: unknown) => unknown>;
|
|
15
|
-
export declare function vendorAnswerViews(twinDir: string, vendor: string): Promise<AnswerViews>;
|
|
15
|
+
export declare function vendorAnswerViews(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<AnswerViews>;
|
|
16
16
|
/** A vendor's byte-stream wire (a line protocol), in process: the pack's `create<Name>TwinStream`, what a
|
|
17
17
|
* hosted World hands a WebSocket's bytes. The walk drives it for a journey's line steps. */
|
|
18
18
|
export type VendorStreamFactory = (root: string, options?: Record<string, unknown>) => TwinStream;
|
|
19
|
-
export declare function vendorStream(twinDir: string, vendor: string): Promise<VendorStreamFactory | undefined>;
|
|
19
|
+
export declare function vendorStream(twinDir: string, vendor: string, dependenciesFrom?: string): Promise<VendorStreamFactory | undefined>;
|
|
20
20
|
export {};
|
|
@@ -13,8 +13,8 @@ import { join } from 'node:path';
|
|
|
13
13
|
import { startPgliteHost } from "./walk-database.js";
|
|
14
14
|
import { worldNow } from '@volter/world-core/runtime';
|
|
15
15
|
import { walkPackDirectory } from "./pack-directory.js";
|
|
16
|
-
export async function vendorFetch(twinDir, vendor) {
|
|
17
|
-
const directory = walkPackDirectory(twinDir, vendor);
|
|
16
|
+
export async function vendorFetch(twinDir, vendor, dependenciesFrom = twinDir) {
|
|
17
|
+
const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
|
|
18
18
|
if (!directory)
|
|
19
19
|
return undefined;
|
|
20
20
|
let mod;
|
|
@@ -59,9 +59,9 @@ const jwtView = (field) => (body) => {
|
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
|
-
export async function vendorAnswerViews(twinDir, vendor) {
|
|
62
|
+
export async function vendorAnswerViews(twinDir, vendor, dependenciesFrom = twinDir) {
|
|
63
63
|
let own = {};
|
|
64
|
-
const directory = walkPackDirectory(twinDir, vendor);
|
|
64
|
+
const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
|
|
65
65
|
try {
|
|
66
66
|
if (directory)
|
|
67
67
|
own = (await import(__rewriteRelativeImportExtension(join(directory, 'src', 'index.ts')))).answerViews ?? {};
|
|
@@ -69,8 +69,8 @@ export async function vendorAnswerViews(twinDir, vendor) {
|
|
|
69
69
|
catch { /* none of its own */ }
|
|
70
70
|
return new Proxy(own, { get: (views, name) => (typeof name === 'string' && name.startsWith('jwt:') ? jwtView(name.slice(4)) : views[name]) });
|
|
71
71
|
}
|
|
72
|
-
export async function vendorStream(twinDir, vendor) {
|
|
73
|
-
const directory = walkPackDirectory(twinDir, vendor);
|
|
72
|
+
export async function vendorStream(twinDir, vendor, dependenciesFrom = twinDir) {
|
|
73
|
+
const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
|
|
74
74
|
if (!directory)
|
|
75
75
|
return undefined;
|
|
76
76
|
let mod;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-standard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.89",
|
|
4
4
|
"description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"yaml": "2.9.0"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@volter/world-core": "3.0.
|
|
78
|
+
"@volter/world-core": "3.0.97",
|
|
79
79
|
"@volter/world-runtime": "*",
|
|
80
80
|
"playwright": "^1.63.0"
|
|
81
81
|
},
|
package/src/assessment.ts
CHANGED
|
@@ -27,7 +27,7 @@ export async function assessPack(packDir: string, options: AssessmentOptions = {
|
|
|
27
27
|
const lanes = readdirSync(packDir).filter((name) => existsSync(join(packDir, name, 'src/manifest.ts')) && !existsSync(join(packDir, name, 'package.json')));
|
|
28
28
|
const units = [packDir, ...lanes.map((name) => join(packDir, name))];
|
|
29
29
|
if (packDir.split(/[\\/]/).at(-1) !== vendor) throw new Error('Place the artifact in a directory named for its vendor before assessment');
|
|
30
|
-
usePacks(join(packDir, '..'));
|
|
30
|
+
usePacks(join(packDir, '..'), { dependenciesFrom: packDir });
|
|
31
31
|
const gates = await p3Gate(units);
|
|
32
32
|
const life = expandFills(read(join(packDir, 'journeys/customer-life.json'))) as Journey;
|
|
33
33
|
if (!Array.isArray(life.steps) || !life.steps.length) throw new Error('Assessment needs a nonempty customer journey');
|
package/src/conformance.ts
CHANGED
|
@@ -318,7 +318,7 @@ async function lifeServed(pack: string): Promise<string[]> {
|
|
|
318
318
|
const lifePath = join(pack, 'journeys', 'customer-life.json');
|
|
319
319
|
if (!existsSync(lifePath)) return ['journeys/customer-life.json is missing'];
|
|
320
320
|
const { inMemoryWorld, usePacks, walk } = await import('./walk/behavior-journey.ts');
|
|
321
|
-
usePacks(dirname(pack));
|
|
321
|
+
usePacks(dirname(pack), { dependenciesFrom: pack });
|
|
322
322
|
const env = (globalThis as { process: { env: Record<string, string | undefined> } }).process.env;
|
|
323
323
|
const served = async (root: string, options: { scenarioPath?: string }) => {
|
|
324
324
|
const before = env.VOLTER_WORLD_BOOT_ID;
|
|
@@ -24,6 +24,8 @@ import { xmlMembers } from './xml-view.ts';
|
|
|
24
24
|
|
|
25
25
|
/** The pack repository the walk finds a vendor's pack in, set by the caller before its first walk. */
|
|
26
26
|
let packsRoot: string | undefined;
|
|
27
|
+
/** Installed companions resolve from the selected pack, including its workspace-local dependencies. */
|
|
28
|
+
let dependenciesRoot: string | undefined;
|
|
27
29
|
/** Where a socket step opens: an absolute `ws://` or `wss://` url on its own host, a path on the life's host otherwise. */
|
|
28
30
|
function socketAt(open: string, host: string): { host: string; path: string } {
|
|
29
31
|
if (!/^wss?:\/\//.test(open)) return { host, path: open };
|
|
@@ -36,8 +38,9 @@ let walkFacts: HostFacts = baselineFacts;
|
|
|
36
38
|
let walkHosts = createHostTable(walkFacts).hosts;
|
|
37
39
|
const directories = new Map<string, string | undefined>();
|
|
38
40
|
const installedFacts = new Set<string>();
|
|
39
|
-
export function usePacks(dir: string): void {
|
|
41
|
+
export function usePacks(dir: string, options: { dependenciesFrom?: string } = {}): void {
|
|
40
42
|
packsRoot = dir;
|
|
43
|
+
dependenciesRoot = options.dependenciesFrom ?? dir;
|
|
41
44
|
const installed: Record<string, HostFacts[string]> = {};
|
|
42
45
|
for (const name of readdirSync(dir)) {
|
|
43
46
|
const file = join(dir, name, 'generated/pack-facts.json');
|
|
@@ -51,9 +54,10 @@ export function usePacks(dir: string): void {
|
|
|
51
54
|
directories.clear();
|
|
52
55
|
}
|
|
53
56
|
const packsDir = (): string => { if (packsRoot === undefined) throw new Error('usePacks(<packs dir>) first'); return packsRoot; };
|
|
57
|
+
const dependenciesDir = (): string => dependenciesRoot ?? packsDir();
|
|
54
58
|
function directoryFor(vendor: string): string {
|
|
55
59
|
if (!directories.has(vendor)) {
|
|
56
|
-
const directory = walkPackDirectory(packsDir(), vendor);
|
|
60
|
+
const directory = walkPackDirectory(packsDir(), vendor, dependenciesDir());
|
|
57
61
|
directories.set(vendor, directory);
|
|
58
62
|
if (directory) {
|
|
59
63
|
const name = vendor.split('/')[0]!;
|
|
@@ -107,7 +111,7 @@ function defaultType(body: unknown): string {
|
|
|
107
111
|
if (typeof body !== 'string') return 'application/json';
|
|
108
112
|
try { parseExactJson(body); return 'application/json'; } catch { return 'text/plain;charset=UTF-8'; }
|
|
109
113
|
}
|
|
110
|
-
const vendorFetch = (vendor: string) => vendorFetchFrom(packsDir(), vendor);
|
|
114
|
+
const vendorFetch = (vendor: string) => vendorFetchFrom(packsDir(), vendor, dependenciesDir());
|
|
111
115
|
const shapes = new Map<string, Promise<ShapeChecker | undefined>>();
|
|
112
116
|
const shapeOf = (vendor: string): Promise<ShapeChecker | undefined> => { if (!shapes.has(vendor)) shapes.set(vendor, shapeChecker(directoryFor(vendor))); return shapes.get(vendor)!; };
|
|
113
117
|
|
|
@@ -210,7 +214,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
|
|
|
210
214
|
// the life's host is a hostname (the vendor's, `api.x.ai`); a URL there would put a second scheme in every request
|
|
211
215
|
if (journey.host !== undefined && !/^[A-Za-z0-9.-]+(:\d+)?$/.test(journey.host)) { collect.push({ step: '(setup)', beat: 'HOST', detail: `the life's host "${journey.host}" is no hostname: write the vendor's host alone (api.example.com), no scheme or path` }); return {}; }
|
|
212
216
|
const factory = await vendorFetch(journey.vendor);
|
|
213
|
-
const views = await vendorAnswerViews(packsDir(), journey.vendor);
|
|
217
|
+
const views = await vendorAnswerViews(packsDir(), journey.vendor, dependenciesDir());
|
|
214
218
|
if (!factory) { collect.push({ step: '(setup)', beat: 'ADAPTER', detail: `no create<Name>TwinFetch adapter enrolled for ${journey.vendor}` }); return {}; }
|
|
215
219
|
// the life's companions (`companions`): the packs its vendor calls, each over this walk's root, on the same World and
|
|
216
220
|
// clock; reached in process by the vendor's ctx.vendorFetch and by a step to a URL their host rules claim
|
|
@@ -323,7 +327,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
|
|
|
323
327
|
// never a way back to the in-process stream (which would pass a listener wired apart from the World)
|
|
324
328
|
if (served?.stream && Object.keys(step.connect).length) { collect.push({ step: step.name, beat: 'CONNECTION', detail: `a served walk opens "${step.on}" as the application does, over TCP to the listener; connect options (${Object.keys(step.connect).join(', ')}) configure only the in-process stream` }); return; }
|
|
325
329
|
const viaServed = served?.stream;
|
|
326
|
-
if (!viaServed) streams ??= await vendorStream(packsDir(), journey.vendor);
|
|
330
|
+
if (!viaServed) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
|
|
327
331
|
if (!viaServed && !streams) { collect.push({ step: step.name, beat: 'ADAPTER', detail: `no create<Name>TwinStream adapter enrolled for ${journey.vendor}` }); return; }
|
|
328
332
|
const opened: Connection = { conn: undefined as unknown as TwinStreamConnection, out: '', closed: false, shape: {} };
|
|
329
333
|
opened.conn = (viaServed ?? streams!(root, step.connect))({ write: (bytes) => { opened.out += decoder.decode(bytes); }, end: () => { opened.closed = true; } }, '127.0.0.1');
|
|
@@ -367,7 +371,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
|
|
|
367
371
|
try {
|
|
368
372
|
if (step.connect) {
|
|
369
373
|
if (served?.stream && Object.keys(step.connect).length) throw new Error('a served RESP walk configures its server, not the client connection');
|
|
370
|
-
if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor);
|
|
374
|
+
if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
|
|
371
375
|
const factory = served?.stream ?? (respStream ??= streams?.(root, step.connect)); if (!factory) throw new Error('no byte-stream adapter');
|
|
372
376
|
const c = { conn: undefined as unknown as TwinStreamConnection, out: '', closed: false };
|
|
373
377
|
c.conn = factory({ write: (b) => { c.out += redis.binaryText(b); }, end: () => { c.closed = true; } }, '127.0.0.1');
|
|
@@ -405,7 +409,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
|
|
|
405
409
|
try {
|
|
406
410
|
let body: Record<string, unknown> | undefined;
|
|
407
411
|
if (step.signIn) {
|
|
408
|
-
if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor);
|
|
412
|
+
if (!served?.stream) streams ??= await vendorStream(packsDir(), journey.vendor, dependenciesDir());
|
|
409
413
|
const factory = served?.stream ?? streams?.(root, {}); if (!factory) throw new Error(`no create<Name>TwinStream adapter enrolled for ${journey.vendor}`);
|
|
410
414
|
let out: number[] = [];
|
|
411
415
|
const conn = factory({ write: (b) => { out.push(...b); }, end: () => {} }, '127.0.0.1');
|
|
@@ -3,13 +3,13 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { installedVendorPacks, isDerivedPack, packVendor } from '@volter/world-core/runtime';
|
|
5
5
|
|
|
6
|
-
export function walkPackDirectory(from: string, vendor: string): string | undefined {
|
|
6
|
+
export function walkPackDirectory(from: string, vendor: string, dependenciesFrom = from): string | undefined {
|
|
7
7
|
if (!/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?$/.test(vendor)) return undefined;
|
|
8
8
|
const [name, lane] = vendor.split('/');
|
|
9
9
|
const sibling = join(from, name!);
|
|
10
10
|
const root = isDerivedPack(sibling) && packVendor(sibling) === name
|
|
11
11
|
? sibling
|
|
12
|
-
: installedVendorPacks(
|
|
12
|
+
: installedVendorPacks(dependenciesFrom, { vendors: [name!] }).get(name!);
|
|
13
13
|
const unit = root && (lane ? join(root, lane) : root);
|
|
14
14
|
return unit && existsSync(join(unit, 'src', 'index.ts')) ? unit : undefined;
|
|
15
15
|
}
|
package/src/walk/vendor-fetch.ts
CHANGED
|
@@ -13,8 +13,8 @@ type ApplicationStandIn = NonNullable<PackFetchOptions['application']>;
|
|
|
13
13
|
* life's application answers stand in for the application the vendor asks (`application`, this fetch's alone). */
|
|
14
14
|
export type VendorFetchFactory = (root: string, options?: { scenarioPath?: string; application?: ApplicationStandIn }) => (request: Request) => Response | Promise<Response>;
|
|
15
15
|
|
|
16
|
-
export async function vendorFetch(twinDir: string, vendor: string): Promise<VendorFetchFactory | undefined> {
|
|
17
|
-
const directory = walkPackDirectory(twinDir, vendor);
|
|
16
|
+
export async function vendorFetch(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<VendorFetchFactory | undefined> {
|
|
17
|
+
const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
|
|
18
18
|
if (!directory) return undefined;
|
|
19
19
|
let mod: Record<string, unknown>;
|
|
20
20
|
try { mod = (await import(join(directory, 'src', 'index.ts'))) as Record<string, unknown>; } catch { return undefined; }
|
|
@@ -52,9 +52,9 @@ const jwtView = (field: string) => (body: unknown): unknown => {
|
|
|
52
52
|
try { return JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8')); } catch { return null; }
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
export async function vendorAnswerViews(twinDir: string, vendor: string): Promise<AnswerViews> {
|
|
55
|
+
export async function vendorAnswerViews(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<AnswerViews> {
|
|
56
56
|
let own: AnswerViews = {};
|
|
57
|
-
const directory = walkPackDirectory(twinDir, vendor);
|
|
57
|
+
const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
|
|
58
58
|
try {
|
|
59
59
|
if (directory) own = ((await import(join(directory, 'src', 'index.ts'))) as { answerViews?: AnswerViews }).answerViews ?? {};
|
|
60
60
|
} catch { /* none of its own */ }
|
|
@@ -65,8 +65,8 @@ export async function vendorAnswerViews(twinDir: string, vendor: string): Promis
|
|
|
65
65
|
* hosted World hands a WebSocket's bytes. The walk drives it for a journey's line steps. */
|
|
66
66
|
export type VendorStreamFactory = (root: string, options?: Record<string, unknown>) => TwinStream;
|
|
67
67
|
|
|
68
|
-
export async function vendorStream(twinDir: string, vendor: string): Promise<VendorStreamFactory | undefined> {
|
|
69
|
-
const directory = walkPackDirectory(twinDir, vendor);
|
|
68
|
+
export async function vendorStream(twinDir: string, vendor: string, dependenciesFrom = twinDir): Promise<VendorStreamFactory | undefined> {
|
|
69
|
+
const directory = walkPackDirectory(twinDir, vendor, dependenciesFrom);
|
|
70
70
|
if (!directory) return undefined;
|
|
71
71
|
let mod: Record<string, unknown>;
|
|
72
72
|
try { mod = (await import(join(directory, 'src', 'index.ts'))) as Record<string, unknown>; } catch { return undefined; }
|