@volter/world-core 3.0.156 → 3.0.157
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/attach.cjs +2 -0
- package/capability-decision.cjs +6 -4
- package/dist/attach.cjs +2 -0
- package/dist/capability-decision.cjs +6 -4
- package/dist/generated/pack-facts.json +77 -8
- package/dist/network-policy.cjs +11 -1
- package/dist/network-policy.d.cts +1 -0
- package/dist/pack-facts.d.cts +3 -0
- package/dist/required-credential-transport.cjs +29 -0
- package/dist/required-credential-transport.d.cts +3 -0
- package/dist/src/blob-store.d.ts +16 -0
- package/dist/src/blob-store.js +76 -1
- package/dist/src/derived-core.d.ts +72 -6
- package/dist/src/derived-core.js +192 -51
- package/dist/src/derived-real.js +97 -15
- package/dist/src/derived.d.ts +5 -0
- package/dist/src/executor.d.ts +7 -0
- package/dist/src/executor.js +15 -1
- package/dist/src/git/lfs.d.ts +13 -3
- package/dist/src/git/lfs.js +41 -9
- package/dist/src/head.d.ts +2 -0
- package/dist/src/head.js +6 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/live/ingest.js +9 -13
- package/dist/src/live/rtmp.js +7 -5
- package/dist/src/live/segmenter.js +7 -1
- package/dist/src/machines.js +5 -1
- package/dist/src/managed-go-programs.d.ts +55 -0
- package/dist/src/managed-go-programs.js +133 -0
- package/dist/src/mongodb/stream.d.ts +23 -0
- package/dist/src/pack-fetch.d.ts +6 -1
- package/dist/src/pack-fetch.js +49 -6
- package/dist/src/pack-paths.d.ts +2 -1
- package/dist/src/pack-paths.js +6 -1
- package/dist/src/resource-blob.d.ts +4 -0
- package/dist/src/resource-blob.js +8 -1
- package/dist/src/runtime.d.ts +3 -3
- package/dist/src/runtime.js +2 -2
- package/dist/src/serve-http.js +53 -3
- package/dist/src/sockets.d.ts +4 -1
- package/dist/src/sockets.js +59 -6
- package/dist/src/vendor-call.js +58 -0
- package/dist/src/world-store.d.ts +15 -1
- package/dist/src/world-store.js +78 -2
- package/generated/pack-facts.json +77 -8
- package/network-policy.cjs +11 -1
- package/network-policy.d.cts +1 -0
- package/pack-facts.d.cts +3 -0
- package/package.json +8 -1
- package/required-credential-transport.cjs +29 -0
- package/required-credential-transport.d.cts +3 -0
- package/src/blob-store.ts +59 -1
- package/src/derived-core.ts +215 -51
- package/src/derived-real.ts +80 -14
- package/src/derived.ts +6 -1
- package/src/executor.ts +17 -3
- package/src/git/lfs.ts +40 -9
- package/src/head.ts +6 -0
- package/src/index.ts +1 -0
- package/src/live/ingest.ts +7 -3
- package/src/live/rtmp.ts +7 -5
- package/src/live/segmenter.ts +7 -1
- package/src/machines.ts +5 -1
- package/src/managed-go-programs.ts +146 -0
- package/src/mongodb/stream.ts +10 -0
- package/src/pack-fetch.ts +46 -8
- package/src/pack-paths.ts +5 -1
- package/src/resource-blob.ts +9 -1
- package/src/runtime.ts +4 -2
- package/src/serve-http.ts +43 -3
- package/src/sockets.ts +39 -8
- package/src/vendor-call.ts +54 -0
- package/src/world-store.ts +66 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface NativeGoKitReference {
|
|
2
|
+
package: {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
};
|
|
6
|
+
target: 'wasm32-wali-go-single-thread';
|
|
7
|
+
releaseSha256: string;
|
|
8
|
+
producerSha256: string;
|
|
9
|
+
sourceSha256: string;
|
|
10
|
+
selection: {
|
|
11
|
+
path: string;
|
|
12
|
+
sha256: string;
|
|
13
|
+
};
|
|
14
|
+
recipe: {
|
|
15
|
+
path: string;
|
|
16
|
+
sha256: string;
|
|
17
|
+
};
|
|
18
|
+
installedRoot: string;
|
|
19
|
+
}
|
|
20
|
+
export interface NativeGoOperation {
|
|
21
|
+
sourceService: string;
|
|
22
|
+
role: 'server' | 'prerequisite' | 'health';
|
|
23
|
+
kit: string;
|
|
24
|
+
program: {
|
|
25
|
+
name: string;
|
|
26
|
+
version: string;
|
|
27
|
+
manifestId: string;
|
|
28
|
+
manifestSha256: string;
|
|
29
|
+
path: string;
|
|
30
|
+
};
|
|
31
|
+
/** Resolved arguments require separate comparison with the full source. */
|
|
32
|
+
args: string[];
|
|
33
|
+
/** Names of existing World values, never caller secrets in this record. */
|
|
34
|
+
environmentBindings: Record<string, string>;
|
|
35
|
+
requires: {
|
|
36
|
+
service: string;
|
|
37
|
+
condition: 'service_started' | 'service_healthy' | 'service_completed_successfully';
|
|
38
|
+
}[];
|
|
39
|
+
}
|
|
40
|
+
export interface NativeGoReference {
|
|
41
|
+
version: 2;
|
|
42
|
+
implementation: string;
|
|
43
|
+
release: string;
|
|
44
|
+
directory: string;
|
|
45
|
+
snapshotSha256: string;
|
|
46
|
+
declarationSha256: string;
|
|
47
|
+
image: string;
|
|
48
|
+
kits: Record<string, NativeGoKitReference>;
|
|
49
|
+
server: NativeGoOperation;
|
|
50
|
+
prerequisites: NativeGoOperation[];
|
|
51
|
+
health: NativeGoOperation;
|
|
52
|
+
}
|
|
53
|
+
/** All fields are declared data. The later owned consumer must authenticate
|
|
54
|
+
* the complete source, kit, original image and admitted installed manifests. */
|
|
55
|
+
export declare function nativeGoReference(value: unknown): NativeGoReference;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/** Tagged managed Go operation metadata. This structural reader grants no
|
|
2
|
+
* source/image correspondence, filesystem custody, catalog admission or exec. */
|
|
3
|
+
import { posix } from 'node:path';
|
|
4
|
+
const fail = (field) => { throw new Error('Native Go operation metadata is invalid: ' + field); };
|
|
5
|
+
function object(value, fields, label) {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
7
|
+
return fail(label);
|
|
8
|
+
const row = value;
|
|
9
|
+
if (Object.keys(row).length !== fields.length || fields.some(key => !Object.hasOwn(row, key)))
|
|
10
|
+
return fail(label);
|
|
11
|
+
return row;
|
|
12
|
+
}
|
|
13
|
+
function text(value, label) {
|
|
14
|
+
if (typeof value !== 'string' || !value || value.includes('\0'))
|
|
15
|
+
return fail(label);
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function digest(value, label) {
|
|
19
|
+
const out = text(value, label);
|
|
20
|
+
if (!/^[a-f0-9]{64}$/u.test(out))
|
|
21
|
+
return fail(label);
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function relativePath(value, label) {
|
|
25
|
+
const out = text(value, label);
|
|
26
|
+
if (out.startsWith('/') || out.includes('\\') || posix.normalize(out) !== out
|
|
27
|
+
|| out.split('/').some(part => !part || part === '.' || part === '..'))
|
|
28
|
+
return fail(label);
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
function installedPath(value, label) {
|
|
32
|
+
const out = text(value, label);
|
|
33
|
+
if (!out.startsWith('/opt/programs/') || out.includes('\\') || posix.normalize(out) !== out
|
|
34
|
+
|| out.split('/').slice(1).some(part => !part || part === '.' || part === '..'))
|
|
35
|
+
return fail(label);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function pin(value, label) {
|
|
39
|
+
const row = object(value, ['path', 'sha256'], label);
|
|
40
|
+
return { path: relativePath(row.path, label + '.path'), sha256: digest(row.sha256, label + '.sha256') };
|
|
41
|
+
}
|
|
42
|
+
function kit(value, label) {
|
|
43
|
+
const row = object(value, ['package', 'target', 'releaseSha256', 'producerSha256', 'sourceSha256', 'selection', 'recipe', 'installedRoot'], label);
|
|
44
|
+
const pack = object(row.package, ['name', 'version'], label + '.package');
|
|
45
|
+
if (row.target !== 'wasm32-wali-go-single-thread')
|
|
46
|
+
return fail(label + '.target');
|
|
47
|
+
return { package: { name: text(pack.name, label + '.package.name'), version: text(pack.version, label + '.package.version') },
|
|
48
|
+
target: row.target, releaseSha256: digest(row.releaseSha256, label + '.releaseSha256'),
|
|
49
|
+
producerSha256: digest(row.producerSha256, label + '.producerSha256'), sourceSha256: digest(row.sourceSha256, label + '.sourceSha256'),
|
|
50
|
+
selection: pin(row.selection, label + '.selection'), recipe: pin(row.recipe, label + '.recipe'),
|
|
51
|
+
installedRoot: installedPath(row.installedRoot, label + '.installedRoot') };
|
|
52
|
+
}
|
|
53
|
+
function operation(value, role, kits, label) {
|
|
54
|
+
const row = object(value, ['sourceService', 'role', 'kit', 'program', 'args', 'environmentBindings', 'requires'], label);
|
|
55
|
+
const selected = text(row.kit, label + '.kit');
|
|
56
|
+
if (row.role !== role || !Object.hasOwn(kits, selected))
|
|
57
|
+
return fail(label + '.role/kit');
|
|
58
|
+
const provider = kits[selected];
|
|
59
|
+
const command = object(row.program, ['name', 'version', 'manifestId', 'manifestSha256', 'path'], label + '.program');
|
|
60
|
+
const path = installedPath(command.path, label + '.program.path'), version = text(command.version, label + '.program.version');
|
|
61
|
+
if (version !== provider.package.version || !path.startsWith(provider.installedRoot + '/'))
|
|
62
|
+
return fail(label + '.program placement/version');
|
|
63
|
+
if (!Array.isArray(row.args))
|
|
64
|
+
return fail(label + '.args');
|
|
65
|
+
const args = [];
|
|
66
|
+
for (const arg of row.args) {
|
|
67
|
+
if (typeof arg !== 'string' || arg.includes('\0'))
|
|
68
|
+
return fail(label + '.args.' + args.length);
|
|
69
|
+
args.push(arg);
|
|
70
|
+
}
|
|
71
|
+
if (!row.environmentBindings || typeof row.environmentBindings !== 'object' || Array.isArray(row.environmentBindings))
|
|
72
|
+
return fail(label + '.environmentBindings');
|
|
73
|
+
const bindings = Object.create(null);
|
|
74
|
+
for (const [name, value] of Object.entries(row.environmentBindings)) {
|
|
75
|
+
const source = text(value, label + '.environmentBindings.' + name);
|
|
76
|
+
if (![name, source].every(item => /^[A-Za-z_][A-Za-z0-9_]*$/u.test(item)))
|
|
77
|
+
return fail(label + '.environmentBindings name');
|
|
78
|
+
bindings[name] = source;
|
|
79
|
+
}
|
|
80
|
+
if (!Array.isArray(row.requires))
|
|
81
|
+
return fail(label + '.requires');
|
|
82
|
+
const dependencies = [];
|
|
83
|
+
for (const value of row.requires) {
|
|
84
|
+
const edge = object(value, ['service', 'condition'], label + '.requires');
|
|
85
|
+
if (edge.condition !== 'service_started' && edge.condition !== 'service_healthy' && edge.condition !== 'service_completed_successfully')
|
|
86
|
+
return fail(label + '.requires.condition');
|
|
87
|
+
const service = text(edge.service, label + '.requires.service');
|
|
88
|
+
if (dependencies.some(prior => prior.service === service))
|
|
89
|
+
return fail(label + '.requires duplicate service');
|
|
90
|
+
dependencies.push({ service, condition: edge.condition });
|
|
91
|
+
}
|
|
92
|
+
return { sourceService: text(row.sourceService, label + '.sourceService'), role, kit: selected,
|
|
93
|
+
program: { name: text(command.name, label + '.program.name'), version, manifestId: text(command.manifestId, label + '.program.manifestId'),
|
|
94
|
+
manifestSha256: digest(command.manifestSha256, label + '.program.manifestSha256'), path },
|
|
95
|
+
args, environmentBindings: bindings, requires: dependencies };
|
|
96
|
+
}
|
|
97
|
+
/** All fields are declared data. The later owned consumer must authenticate
|
|
98
|
+
* the complete source, kit, original image and admitted installed manifests. */
|
|
99
|
+
export function nativeGoReference(value) {
|
|
100
|
+
const row = object(value, ['version', 'implementation', 'release', 'directory', 'snapshotSha256', 'declarationSha256', 'image', 'kits', 'server', 'prerequisites', 'health'], 'reference');
|
|
101
|
+
if (row.version !== 2)
|
|
102
|
+
return fail('version');
|
|
103
|
+
const image = text(row.image, 'image');
|
|
104
|
+
if (!/^[^\s@]+@sha256:[a-f0-9]{64}$/u.test(image))
|
|
105
|
+
return fail('image');
|
|
106
|
+
if (!row.kits || typeof row.kits !== 'object' || Array.isArray(row.kits) || !Object.keys(row.kits).length)
|
|
107
|
+
return fail('kits');
|
|
108
|
+
const kits = Object.create(null);
|
|
109
|
+
for (const [name, value] of Object.entries(row.kits))
|
|
110
|
+
kits[text(name, 'kit name')] = kit(value, 'kits.' + name);
|
|
111
|
+
const server = operation(row.server, 'server', kits, 'server'), health = operation(row.health, 'health', kits, 'health');
|
|
112
|
+
const implementation = text(row.implementation, 'implementation'), release = text(row.release, 'release');
|
|
113
|
+
if (kits[server.kit].package.name !== implementation || kits[server.kit].package.version !== release
|
|
114
|
+
|| health.sourceService !== server.sourceService)
|
|
115
|
+
return fail('server/health implementation');
|
|
116
|
+
if (!Array.isArray(row.prerequisites))
|
|
117
|
+
return fail('prerequisites');
|
|
118
|
+
const prerequisites = [];
|
|
119
|
+
for (const value of row.prerequisites)
|
|
120
|
+
prerequisites.push(operation(value, 'prerequisite', kits, 'prerequisites.' + prerequisites.length));
|
|
121
|
+
if (new Set(prerequisites.map(item => item.sourceService)).size !== prerequisites.length
|
|
122
|
+
|| prerequisites.some(item => item.sourceService === server.sourceService))
|
|
123
|
+
return fail('prerequisite service');
|
|
124
|
+
const usedKits = new Set([server.kit, health.kit, ...prerequisites.map(item => item.kit)]);
|
|
125
|
+
if (Object.keys(kits).some(name => !usedKits.has(name)))
|
|
126
|
+
return fail('unconsumed kit');
|
|
127
|
+
for (const item of prerequisites)
|
|
128
|
+
if (!server.requires.some(edge => edge.service === item.sourceService && edge.condition === 'service_completed_successfully'))
|
|
129
|
+
return fail('server prerequisite completion edge');
|
|
130
|
+
return { version: 2, implementation, release, directory: relativePath(row.directory, 'directory'),
|
|
131
|
+
snapshotSha256: digest(row.snapshotSha256, 'snapshotSha256'), declarationSha256: digest(row.declarationSha256, 'declarationSha256'),
|
|
132
|
+
image, kits, server, prerequisites, health };
|
|
133
|
+
}
|
|
@@ -7,6 +7,29 @@ export type MongoCodec = {
|
|
|
7
7
|
serialize(document: MongoDocument): Uint8Array;
|
|
8
8
|
deserialize(bytes: Uint8Array): MongoDocument;
|
|
9
9
|
};
|
|
10
|
+
/** What a pack's stream reads of the `bson` library its generated fetch's `loadBson` resolves from the pack: the codec,
|
|
11
|
+
* extended JSON, and the number kinds a reply names. The kernel declares the shape and imports nothing of it. */
|
|
12
|
+
export type BsonLibrary = {
|
|
13
|
+
serialize(document: MongoDocument): Uint8Array;
|
|
14
|
+
deserialize(bytes: Uint8Array, options?: {
|
|
15
|
+
promoteValues?: boolean;
|
|
16
|
+
promoteLongs?: boolean;
|
|
17
|
+
bsonRegExp?: boolean;
|
|
18
|
+
}): MongoDocument;
|
|
19
|
+
EJSON: {
|
|
20
|
+
serialize(value: unknown, options?: {
|
|
21
|
+
relaxed?: boolean;
|
|
22
|
+
}): MongoDocument;
|
|
23
|
+
deserialize(value: unknown, options?: {
|
|
24
|
+
relaxed?: boolean;
|
|
25
|
+
}): MongoDocument;
|
|
26
|
+
};
|
|
27
|
+
Double: new (value: number) => unknown;
|
|
28
|
+
Int32: new (value: number) => unknown;
|
|
29
|
+
Long: {
|
|
30
|
+
fromNumber(value: number): unknown;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
10
33
|
/** What the sender's message said beside its command: whether it will take more than one reply to it (OP_MSG's exhaustAllowed). */
|
|
11
34
|
export type MongoAsked = {
|
|
12
35
|
exhaustAllowed: boolean;
|
package/dist/src/pack-fetch.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DerivedManifest, type Handler, type HandlerContext, type VendorManifest } from './derived-core.js';
|
|
1
|
+
import { type DerivedManifest, type Handler, type HandlerContext, type ScreenDecl, type VendorManifest } from './derived-core.js';
|
|
2
2
|
import { type DerivedFetch, type DerivedSurface, type BoardFrame } from './derived.js';
|
|
3
3
|
import { type PackScenarioAdapter } from './scenario.js';
|
|
4
4
|
import type { ApplicationStandIn } from './events.js';
|
|
@@ -86,5 +86,10 @@ export type PackFetchOptions = {
|
|
|
86
86
|
/** the application a walk stands in for (a life's `application` answers): this fetch's `ctx.ask` is answered by it */
|
|
87
87
|
application?: ApplicationStandIn;
|
|
88
88
|
};
|
|
89
|
+
/** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
|
|
90
|
+
* that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
|
|
91
|
+
* page). Every other parameter a page's path names is the asking page's, and a page that cannot be filled is passed
|
|
92
|
+
* over. Undefined when nothing is asked; the path (with no `show`) otherwise. */
|
|
93
|
+
export declare function shownRecord(url: URL, workspaces: ReadonlyArray<ScreenDecl>, params: Record<string, string>): string | undefined;
|
|
89
94
|
export declare function createPackFetch(parts: PackParts, options?: PackFetchOptions): DerivedFetch;
|
|
90
95
|
export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): DerivedFetch;
|
package/dist/src/pack-fetch.js
CHANGED
|
@@ -23,7 +23,7 @@ import { withCors } from "./cors.js";
|
|
|
23
23
|
import { contentAddressOf } from "./content-route.js";
|
|
24
24
|
import { getPack } from "./packRegistry.js";
|
|
25
25
|
import { hostRules } from '../host-routing.cjs';
|
|
26
|
-
import { socketUpgrade } from "./sockets.js";
|
|
26
|
+
import { expireSocketWaits, socketUpgrade } from "./sockets.js";
|
|
27
27
|
import { liveIngest } from "./live/ingest.js";
|
|
28
28
|
import { readResourceBlob, removeResourceBlob, writeResourceBlob } from "./resource-blob.js";
|
|
29
29
|
import { resourcesOfType } from "./derived-core.js";
|
|
@@ -71,6 +71,32 @@ function hostMatcher(pattern) {
|
|
|
71
71
|
}
|
|
72
72
|
/** The host a request is for: the vendor host the World's injector names, else its own. */
|
|
73
73
|
const hostOf = (request) => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
|
|
74
|
+
/** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
|
|
75
|
+
* that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
|
|
76
|
+
* page). Every other parameter a page's path names is the asking page's, and a page that cannot be filled is passed
|
|
77
|
+
* over. Undefined when nothing is asked; the path (with no `show`) otherwise. */
|
|
78
|
+
export function shownRecord(url, workspaces, params) {
|
|
79
|
+
const asked = /^([^:]+):(.+)$/.exec(url.searchParams.get('show') ?? '');
|
|
80
|
+
if (!asked)
|
|
81
|
+
return undefined;
|
|
82
|
+
const [, type, id] = asked;
|
|
83
|
+
const fill = (path, extra) => {
|
|
84
|
+
let unfilled = false;
|
|
85
|
+
const filled = path.replace(/\{([^}]+)\}|<([^>]+)>/g, (_, a, b) => {
|
|
86
|
+
const v = { ...params, ...extra }[(a ?? b)];
|
|
87
|
+
if (v === undefined || v === '') {
|
|
88
|
+
unfilled = true;
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
return encodeURIComponent(v);
|
|
92
|
+
});
|
|
93
|
+
return unfilled ? undefined : filled;
|
|
94
|
+
};
|
|
95
|
+
const shows = workspaces.filter((s) => s.shows?.type === type).map((s) => fill(s.path, { [s.shows.param]: id })).find((p) => p !== undefined);
|
|
96
|
+
const lists = workspaces.filter((s) => s.lists?.includes(type)).map((s) => fill(s.path, {})).find((p) => p !== undefined);
|
|
97
|
+
const home = workspaces.find((s) => s.path.replace(/\/+$/, '') === '') ? '/' : undefined;
|
|
98
|
+
return shows ?? lists ?? home ?? url.pathname;
|
|
99
|
+
}
|
|
74
100
|
/** The answer for a path no operation, door or screen serves, and for an operation nothing models: the manifest's
|
|
75
101
|
* `gap`, its `{method}` and `{path}` filled, else the manifest's not-found refusal. */
|
|
76
102
|
function gapAnswer(m, request, otherMethod = false) {
|
|
@@ -125,6 +151,16 @@ async function encoded(m, request, res) {
|
|
|
125
151
|
/** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
|
|
126
152
|
/** A door's path as it is served and listed: under `/_twin/`, whether the manifest writes the prefix or not. */
|
|
127
153
|
const doorPath = (path) => (path.startsWith('/_twin/') ? path : `/_twin${path.startsWith('/') ? '' : '/'}${path}`);
|
|
154
|
+
/** A request with its path's repeated slashes merged, as a front that merges them routes it (the manifest's
|
|
155
|
+
* `mergeSlashes`); one with none is itself. */
|
|
156
|
+
function slashesMerged(request) {
|
|
157
|
+
const url = new URL(request.url);
|
|
158
|
+
const merged = url.pathname.replace(/\/{2,}/g, '/');
|
|
159
|
+
if (merged === url.pathname)
|
|
160
|
+
return request;
|
|
161
|
+
url.pathname = merged;
|
|
162
|
+
return new Request(url, request);
|
|
163
|
+
}
|
|
128
164
|
export function createPackFetch(parts, options = {}) {
|
|
129
165
|
const m = parts.manifest;
|
|
130
166
|
const scope = {
|
|
@@ -258,7 +294,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
258
294
|
}
|
|
259
295
|
};
|
|
260
296
|
// a context the kernel opens for what no surface operation names: a door, a screen, the clock, the board
|
|
261
|
-
const open = (request, id, method, path, params) => contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' }, params }, scope);
|
|
297
|
+
const open = (request, id, method, path, params, streamed) => contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' }, params, ...(streamed ? { streamed } : {}) }, scope);
|
|
262
298
|
const advanceClock = async (request) => {
|
|
263
299
|
if (parts.clock)
|
|
264
300
|
await runAsVendorMove(async () => parts.clock(await open(new Request(request.url, m.tenant ? { headers: request.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
|
|
@@ -370,7 +406,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
370
406
|
return refusedReadOnly();
|
|
371
407
|
if (parts.clock)
|
|
372
408
|
await runAsVendorMove(async () => parts.clock(await open(new Request(asked.url, m.tenant ? { headers: asked.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
|
|
373
|
-
return hit.s.answer(await open(asked, hit.s.decl.id, method, hit.s.decl.path, { ...(hit.s.host(host)?.params ?? {}), ...hit.hit.params }));
|
|
409
|
+
return hit.s.answer(await open(asked, hit.s.decl.id, method, hit.s.decl.path, { ...(hit.s.host(host)?.params ?? {}), ...hit.hit.params }, true));
|
|
374
410
|
};
|
|
375
411
|
/** A request as the kernel reads it: the headers only the kernel names taken off, its body decoded (undefined when it
|
|
376
412
|
* does not decode as its Content-Encoding says). */
|
|
@@ -393,7 +429,8 @@ export function createPackFetch(parts, options = {}) {
|
|
|
393
429
|
const readable = await prepared(arrived);
|
|
394
430
|
return readable instanceof Response ? readable : readable ? atContentHost(readable) : undecodable(arrived);
|
|
395
431
|
};
|
|
396
|
-
const fetch = async (
|
|
432
|
+
const fetch = async (received) => {
|
|
433
|
+
const arrived = m.mergeSlashes ? slashesMerged(received) : received;
|
|
397
434
|
redisServiceTimes.lap('1 request made by the stream');
|
|
398
435
|
// the discovery door is the pack's own, whichever lane would take its path
|
|
399
436
|
const asked = new URL(arrived.url).pathname.replace(/\/+$/, '') || '/';
|
|
@@ -469,6 +506,9 @@ export function createPackFetch(parts, options = {}) {
|
|
|
469
506
|
// the vendor's own moves, not the caller's: its context carries none of the request's headers (a Stripe-Account
|
|
470
507
|
// header would make a payout time makes the caller's), but a tenant's, when the vendor keeps tenants apart
|
|
471
508
|
await advanceClock(request);
|
|
509
|
+
// a reply a session awaits that the World's clock has run out (sockets.ts)
|
|
510
|
+
if (m.sockets?.length)
|
|
511
|
+
expireSocketWaits(m.service, options.root);
|
|
472
512
|
if (path.startsWith('/_twin/')) {
|
|
473
513
|
// the World's machine pool, enrolled by the kernel for a vendor that runs images (machines.ts)
|
|
474
514
|
if (m.machines && path === '/_twin/machine-pool' && request.method === 'POST') {
|
|
@@ -526,9 +566,12 @@ export function createPackFetch(parts, options = {}) {
|
|
|
526
566
|
?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? (addressed ? undefined : hits.find((x) => !rooted(x))
|
|
527
567
|
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined)));
|
|
528
568
|
if (byPath) {
|
|
569
|
+
const asked = byPath.s.decl.kind === 'workspace' && request.method === 'GET' ? shownRecord(url, screens.filter((x) => x.decl.kind === 'workspace').map((x) => x.decl), { ...(byPath.onHost?.params ?? {}), ...byPath.hit.params }) : undefined;
|
|
570
|
+
if (asked !== undefined && asked !== path)
|
|
571
|
+
return new Response(null, { status: 302, headers: { location: `${(request.headers.get(TWIN_PREFIX_HEADER) ?? '').replace(/\/+$/, '')}${asked}` } });
|
|
529
572
|
if (readOnly && request.method !== 'GET' && request.method !== 'HEAD')
|
|
530
573
|
return refusedReadOnly();
|
|
531
|
-
return byPath.s.answer(await open(request, byPath.s.decl.id, request.method, byPath.s.decl.path, { ...(byPath.onHost?.params ?? {}), ...byPath.hit.params }));
|
|
574
|
+
return byPath.s.answer(await open(request, byPath.s.decl.id, request.method, byPath.s.decl.path, { ...(byPath.onHost?.params ?? {}), ...byPath.hit.params }, true));
|
|
532
575
|
}
|
|
533
576
|
for (const route of m.machineRoutes ?? []) {
|
|
534
577
|
const host = request.headers.get('x-volter-twin-original-host') ?? new URL(request.url).hostname;
|
|
@@ -619,7 +662,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
619
662
|
for (const d of m.sockets ?? [])
|
|
620
663
|
if (!parts.sockets?.[d.id])
|
|
621
664
|
throw new Error(`${m.vendor}: the manifest declares the socket ${d.id}, and semantics/sockets.ts exports no ${d.id}`);
|
|
622
|
-
const ownUpgrade = m.sockets?.length ? socketUpgrade(m.service, options.root, m.sockets, parts.sockets, (request, d) => open(request, `socket:${d.id}`, 'GET', d.path,
|
|
665
|
+
const ownUpgrade = m.sockets?.length ? socketUpgrade(m.service, options.root, m.sockets, parts.sockets, (request, d, params) => open(request, `socket:${d.id}`, 'GET', d.path, params), hostOf, (request) => new URL(routed(request).url).pathname,
|
|
623
666
|
// a turn a session speaks (a voice agent's reply) is the World's scenario's, as a turn over HTTP is
|
|
624
667
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
625
668
|
const upgrade = laneUpgrades(parts.lanes ?? {}, ownUpgrade);
|
package/dist/src/pack-paths.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export declare function installedVendorPacks(from: string, options?: {
|
|
|
8
8
|
}): Map<string, string>;
|
|
9
9
|
/** Node's nearest installed package, including linked packages and hoisted workspace dependencies. */
|
|
10
10
|
export declare function installedPackageDir(from: string, name: string): string | undefined;
|
|
11
|
-
/** A pack repository itself, or
|
|
11
|
+
/** A pack repository itself, or a platform checkout's P3 repository: the one `VOLTER_PACKS` names (a worktree's own
|
|
12
|
+
* pinned packs, as the repository's scripts read it), else the one beside it. Applications do not acquire a
|
|
12
13
|
* sibling checkout merely because one happens to be beside them. */
|
|
13
14
|
export declare function packRepository(from: string): string | undefined;
|
|
14
15
|
/** Resolve one vendor's package directory for host-side reads (assets, infrastructure launchers). */
|
package/dist/src/pack-paths.js
CHANGED
|
@@ -121,7 +121,8 @@ export function installedPackageDir(from, name) {
|
|
|
121
121
|
dir = up;
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
/** A pack repository itself, or
|
|
124
|
+
/** A pack repository itself, or a platform checkout's P3 repository: the one `VOLTER_PACKS` names (a worktree's own
|
|
125
|
+
* pinned packs, as the repository's scripts read it), else the one beside it. Applications do not acquire a
|
|
125
126
|
* sibling checkout merely because one happens to be beside them. */
|
|
126
127
|
export function packRepository(from) {
|
|
127
128
|
let dir = resolve(from);
|
|
@@ -129,6 +130,10 @@ export function packRepository(from) {
|
|
|
129
130
|
if (packageName(dir) === 'twin-packs-p3')
|
|
130
131
|
return dir;
|
|
131
132
|
if (packageName(join(dir, 'packages', 'world-core')) === '@volter/world-core') {
|
|
133
|
+
const env = globalThis.process?.env?.VOLTER_PACKS;
|
|
134
|
+
const named = env ? resolve(env) : undefined;
|
|
135
|
+
if (named && packageName(named) === 'twin-packs-p3')
|
|
136
|
+
return named;
|
|
132
137
|
const sibling = join(dirname(dir), 'twin-packs-p3');
|
|
133
138
|
if (packageName(sibling) === 'twin-packs-p3')
|
|
134
139
|
return sibling;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { type BlobWritten } from './blob-store.js';
|
|
1
2
|
/** Read a resource payload at this branch, then its retained ancestors. */
|
|
2
3
|
export declare function readResourceBlob(service: string, key: string, root?: string): Promise<Uint8Array | null>;
|
|
3
4
|
/** Write a resource payload on this branch (a stored object's bytes), where a read finds it first. */
|
|
4
5
|
export declare function writeResourceBlob(service: string, key: string, bytes: Uint8Array, root?: string): Promise<void>;
|
|
6
|
+
/** Write a resource payload on this branch from a body as it arrives (a model's weights, an upload's bytes), kept only
|
|
7
|
+
* when `accept` holds for its size and SHA-256 (the blob seam's `writeBlobStream`). True when kept. */
|
|
8
|
+
export declare function writeResourceBlobStream(service: string, key: string, body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>, accept: (written: BlobWritten) => boolean, root?: string): Promise<boolean>;
|
|
5
9
|
/** Remove this branch's copy of a resource payload; an ancestor's stays its own (the row that named it is what a
|
|
6
10
|
* deletion ends). */
|
|
7
11
|
export declare function removeResourceBlob(service: string, key: string, root?: string): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, join, relative } from 'node:path';
|
|
2
|
-
import { getActiveBlobStore, readBlobRange } from "./blob-store.js";
|
|
2
|
+
import { getActiveBlobStore, readBlobRange, streamChunks, writeBlobStream } from "./blob-store.js";
|
|
3
3
|
import { readBranchMeta } from "./log.js";
|
|
4
4
|
import { worldPaths } from "./storage.js";
|
|
5
5
|
/** Read a resource payload at this branch, then its retained ancestors. */
|
|
@@ -17,6 +17,13 @@ export async function writeResourceBlob(service, key, bytes, root) {
|
|
|
17
17
|
assertResourceKey(key);
|
|
18
18
|
await getActiveBlobStore().put(resourceBlobFile(worldPaths(service, root).resources, key), bytes);
|
|
19
19
|
}
|
|
20
|
+
/** Write a resource payload on this branch from a body as it arrives (a model's weights, an upload's bytes), kept only
|
|
21
|
+
* when `accept` holds for its size and SHA-256 (the blob seam's `writeBlobStream`). True when kept. */
|
|
22
|
+
export async function writeResourceBlobStream(service, key, body, accept, root) {
|
|
23
|
+
assertResourceKey(key);
|
|
24
|
+
const chunks = body instanceof ReadableStream ? streamChunks(body) : body;
|
|
25
|
+
return writeBlobStream(resourceBlobFile(worldPaths(service, root).resources, key), chunks, accept);
|
|
26
|
+
}
|
|
20
27
|
/** Remove this branch's copy of a resource payload; an ancestor's stays its own (the row that named it is what a
|
|
21
28
|
* deletion ends). */
|
|
22
29
|
export async function removeResourceBlob(service, key, root) {
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -36,8 +36,8 @@ export type { WorldStore, WorldStat, HydrationSource, HydrationSink } from './wo
|
|
|
36
36
|
export type { CheckLoader } from './head.js';
|
|
37
37
|
export { SqlWorldStore } from './world-store-sql.js';
|
|
38
38
|
export type { SqlExec } from './world-store-sql.js';
|
|
39
|
-
export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, } from './blob-store.js';
|
|
40
|
-
export type { BlobStore } from './blob-store.js';
|
|
39
|
+
export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, streamChunks, writeBlobStream, } from './blob-store.js';
|
|
40
|
+
export type { BlobStore, BlobWritten } from './blob-store.js';
|
|
41
41
|
export { applyTwinWrite, applyTwinWrites, applyTwinWriteAtomic, createTwinServer, CALLER_HEADER, callerForHeader, callerFromHeader, journalTwinRequest, readTwinRequestJournalFrom, type TwinRequestJournalCursor, twinRequestJournalFailures, twinRequestCredentials, readTwinRequestJournal, recordedInput, resolveTwinRead, twinRequestJournalEnabled, twinRequestJournalPath, twinResources, } from './serve.js';
|
|
42
42
|
export type { AtomicTwinWriteDecision, TwinRequestJournalEntry, TwinResource, TwinWriteInput, TwinWriteResult, } from './serve.js';
|
|
43
43
|
export { createTwinProxy } from './proxy.js';
|
|
@@ -79,7 +79,7 @@ export { corsHeaders, withCors, type CorsDecl } from './cors.js';
|
|
|
79
79
|
export { setInProcessTwins, vendorFetch, VendorUnreachableError, type InProcessTwin } from './vendor-call.js';
|
|
80
80
|
export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver, type DatabaseDriver, type DatabaseSession } from './managed-database.js';
|
|
81
81
|
export type { Batch as EngineBatch, ManagedDatabase, Statement as EngineStatement, StatementResult as EngineStatementResult } from './managed-database.js';
|
|
82
|
-
export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob } from './resource-blob.js';
|
|
82
|
+
export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob, writeResourceBlobStream } from './resource-blob.js';
|
|
83
83
|
export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withHistoryLock, withHistoryLocks, withStateLock, withStateRemoval } from './ancestry.js';
|
|
84
84
|
export { captureHistory, captureParentHistory, historyChanges, historyAtInstant, historyDigest, historyEntries, historyLength, historyPrefix, inheritedHistory, originHead, publishOriginHistory, readHistoryView } from './history.js';
|
|
85
85
|
export type { HistoryView, HistoryLayout, HistoryReference, HistoryOrigin } from './history.js';
|
package/dist/src/runtime.js
CHANGED
|
@@ -54,7 +54,7 @@ export { FsWorldStore, MemoryWorldStore, getActiveWorldStore, setActiveWorldStor
|
|
|
54
54
|
export { SqlWorldStore } from "./world-store-sql.js";
|
|
55
55
|
// The blob seam (runtime contract R11): byte storage behind byte-carrying handlers, so a
|
|
56
56
|
// serverless namespace puts bytes in object storage while local worlds keep today's layout.
|
|
57
|
-
export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, } from "./blob-store.js";
|
|
57
|
+
export { FsBlobStore, MemoryBlobStore, blobDigest, getActiveBlobStore, setActiveBlobStore, withBlobStore, readBlobRange, streamChunks, writeBlobStream, } from "./blob-store.js";
|
|
58
58
|
export { applyTwinWrite, applyTwinWrites, applyTwinWriteAtomic, createTwinServer, CALLER_HEADER, callerForHeader, callerFromHeader, journalTwinRequest, readTwinRequestJournalFrom, twinRequestJournalFailures, twinRequestCredentials, readTwinRequestJournal, recordedInput, resolveTwinRead, twinRequestJournalEnabled, twinRequestJournalPath, twinResources, } from "./serve.js";
|
|
59
59
|
export { createTwinProxy } from "./proxy.js";
|
|
60
60
|
export { forkTwin, isFork, readForkMeta, } from "./fork.js";
|
|
@@ -100,7 +100,7 @@ export { multipartBoundary, multipartParts, multipartEnvelope, inferMultipartBou
|
|
|
100
100
|
export { corsHeaders, withCors } from "./cors.js";
|
|
101
101
|
export { setInProcessTwins, vendorFetch, VendorUnreachableError } from "./vendor-call.js";
|
|
102
102
|
export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver } from "./managed-database.js";
|
|
103
|
-
export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob } from "./resource-blob.js";
|
|
103
|
+
export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob, writeResourceBlobStream } from "./resource-blob.js";
|
|
104
104
|
export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withHistoryLock, withHistoryLocks, withStateLock, withStateRemoval } from "./ancestry.js";
|
|
105
105
|
export { captureHistory, captureParentHistory, historyChanges, historyAtInstant, historyDigest, historyEntries, historyLength, historyPrefix, inheritedHistory, originHead, publishOriginHistory, readHistoryView } from "./history.js";
|
|
106
106
|
export { foldHistory } from "./log.js";
|
package/dist/src/serve-http.js
CHANGED
|
@@ -60,6 +60,37 @@ export async function serveHttp(options) {
|
|
|
60
60
|
// the global `Bun.serve` — the request journal's wrap of it (serve.ts) applies to packs and to
|
|
61
61
|
// this seam alike, so the seam never decorates twice on Bun
|
|
62
62
|
const upgrade = options.upgrade;
|
|
63
|
+
// the sockets open now, by the seam's own count: Bun keeps a socket its server closed (a refused key's 1008, from
|
|
64
|
+
// `open` or later) in `pendingWebSockets` after its close handler ran, and `stop()`'s promise then never settles
|
|
65
|
+
// (measured on Bun 1.3.11, macOS arm64, alone: a Bun.serve whose open() calls ws.close(1008) leaves
|
|
66
|
+
// pendingWebSockets at 1 with its close handler called once, and an awaited stop(true) had not settled after 3 s;
|
|
67
|
+
// unawaited, the listener closed and the process used 4 ms of CPU over the next 2 s; a client-initiated close
|
|
68
|
+
// stops in 0 ms). Conform's life hung on it after OpenAI's keyless Realtime step.
|
|
69
|
+
// and the requests whose handlers have not answered: a stop that drops connections (closeActive) aborts each one's
|
|
70
|
+
// signal, and Bun's stop still waits for the handler to settle, which a handler that never answers never does
|
|
71
|
+
// (measured on Bun 1.3.11, macOS arm64, alone: a Bun.serve whose fetch never resolves, its request's signal aborted
|
|
72
|
+
// by stop(true), pendingRequests 1, the stop unsettled after 3 s). Conform's client check hung on a pack served with
|
|
73
|
+
// a scenario that answered nothing. Node's path below reports such a handler as cleanup and resolves; so does this one.
|
|
74
|
+
const live = new Set();
|
|
75
|
+
const inflight = new Set();
|
|
76
|
+
const served = options.fetch;
|
|
77
|
+
const tracked = (request, ...rest) => {
|
|
78
|
+
inflight.add(request);
|
|
79
|
+
let answer;
|
|
80
|
+
try {
|
|
81
|
+
answer = served(request, ...rest);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
inflight.delete(request);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
if (!(answer instanceof Promise)) {
|
|
88
|
+
inflight.delete(request);
|
|
89
|
+
return answer;
|
|
90
|
+
}
|
|
91
|
+
return answer.finally(() => { inflight.delete(request); });
|
|
92
|
+
};
|
|
93
|
+
options = { ...options, fetch: tracked };
|
|
63
94
|
const server = bun.serve({ ...options, hostname, port: options.port ?? 0,
|
|
64
95
|
...(upgrade ? {
|
|
65
96
|
fetch: (request, server) => {
|
|
@@ -70,14 +101,33 @@ export async function serveHttp(options) {
|
|
|
70
101
|
return options.fetch(request);
|
|
71
102
|
},
|
|
72
103
|
websocket: {
|
|
73
|
-
open: (peer) => upgrade.open(peer, peer.data.request),
|
|
104
|
+
open: (peer) => { live.add(peer); return upgrade.open(peer, peer.data.request); },
|
|
74
105
|
message: (peer, data) => upgrade.message(peer, data),
|
|
75
|
-
close: upgrade.close,
|
|
106
|
+
close: (peer, ...rest) => { live.delete(peer); return upgrade.close(peer, ...rest); },
|
|
76
107
|
},
|
|
77
108
|
} : {}),
|
|
78
109
|
});
|
|
79
110
|
const port = server.port ?? 0;
|
|
80
|
-
return keepHandler({ hostname, port, url: server.url instanceof URL ? server.url : new URL(`http://${urlHost}:${port}/`), stop: async (closeActive = true) => {
|
|
111
|
+
return keepHandler({ hostname, port, url: server.url instanceof URL ? server.url : new URL(`http://${urlHost}:${port}/`), stop: async (closeActive = true) => {
|
|
112
|
+
const stopping = Promise.resolve(server.stop(closeActive));
|
|
113
|
+
// stop closes the sockets still open (closeActive), each a close Bun may then keep counting, and drops the
|
|
114
|
+
// connections of handlers still running: once every socket the seam opened has closed and every handler left is
|
|
115
|
+
// one whose request was dropped, Bun's stop would never settle on them (above), and the seam has stopped
|
|
116
|
+
let settled = false;
|
|
117
|
+
stopping.then(() => { settled = true; }, () => { settled = true; });
|
|
118
|
+
while (!settled) {
|
|
119
|
+
const orphaned = [...inflight].every((request) => request.signal.aborted);
|
|
120
|
+
const counted = (server.pendingWebSockets ?? 0) > 0;
|
|
121
|
+
if (live.size === 0 && orphaned && (counted || (closeActive && inflight.size > 0))) {
|
|
122
|
+
stopping.catch(() => { });
|
|
123
|
+
if (inflight.size)
|
|
124
|
+
console.error(`serveHttp: forced stop left pending handler cleanup: ${inflight.size} request(s)`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
128
|
+
}
|
|
129
|
+
await stopping;
|
|
130
|
+
} }, options.fetch);
|
|
81
131
|
}
|
|
82
132
|
return keepHandler(await serveOnNode({ ...options, hostname, urlHost, fetch: decorate ? decorate(options) : options.fetch }), options.fetch);
|
|
83
133
|
}
|
package/dist/src/sockets.d.ts
CHANGED
|
@@ -44,8 +44,11 @@ export type SocketEngine<C> = {
|
|
|
44
44
|
};
|
|
45
45
|
/** The open sessions of one socket of the vendor in a World (a handler reaches them as `ctx.sockets(id)`). */
|
|
46
46
|
export declare function openSessions(service: string, root: string | undefined, socket: string): SocketSession[];
|
|
47
|
+
/** What a session awaits that the World's clock has run out (a life's `wait` past a relay's timeout), rejected as the
|
|
48
|
+
* machine's clock would reject it; called as the vendor serves a request ("A request answered by a session"). */
|
|
49
|
+
export declare function expireSocketWaits(service: string, root: string | undefined): void;
|
|
47
50
|
/** Offer a write to every open session of the vendor, each in its turn after what it is already doing. Queued, never
|
|
48
51
|
* awaited: the write may be one a session's own frame made, which that session finishes before it is told. */
|
|
49
52
|
export declare function socketWrite(service: string, root: string | undefined, write: EventWrite): void;
|
|
50
53
|
/** The upgrade the serve seam takes: a request to a declared socket's path (and host) opens a session of its engine. */
|
|
51
|
-
export declare function socketUpgrade<C>(service: string, root: string | undefined, decls: ReadonlyArray<SocketDecl>, engines: Record<string, SocketEngine<C>>, context: (request: Request, decl: SocketDecl) => Promise<C>, hostOf: (request: Request) => string, pathOf?: (request: Request) => string, decide?: (asked: unknown) => Promise<ScenarioServed | undefined>): WebSocketUpgrade;
|
|
54
|
+
export declare function socketUpgrade<C>(service: string, root: string | undefined, decls: ReadonlyArray<SocketDecl>, engines: Record<string, SocketEngine<C>>, context: (request: Request, decl: SocketDecl, params: Record<string, string>) => Promise<C>, hostOf: (request: Request) => string, pathOf?: (request: Request) => string, decide?: (asked: unknown) => Promise<ScenarioServed | undefined>): WebSocketUpgrade;
|