nervur 0.21.2 → 0.21.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -30
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +8 -0
- package/dist/cli/command.js +12 -11
- package/dist/cli/edge.d.ts +1 -1
- package/dist/cli/edge.js +30 -11
- package/dist/cli/harbor.d.ts +1 -1
- package/dist/cli/harbor.js +18 -6
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.js +5 -0
- package/dist/edge/index.d.ts +23 -13
- package/dist/edge/index.js +103 -37
- package/dist/harbor/catalogue.d.ts +1 -0
- package/dist/harbor/catalogue.js +2 -2
- package/dist/harbor/dock.d.ts +1 -0
- package/dist/harbor/dock.js +8 -3
- package/dist/harbor/harbor.d.ts +2 -0
- package/dist/harbor/harbor.js +6 -0
- package/dist/harbor/terrain.d.ts +1 -0
- package/dist/harbor/terrain.js +6 -0
- package/dist/pointer/bodies.d.ts +1 -0
- package/dist/pointer/bodies.js +7 -1
- package/dist/proof/law.js +1 -1
- package/package.json +1 -1
- package/src/browser/index.ts +9 -0
- package/src/cli/command.ts +12 -11
- package/src/cli/edge.ts +30 -11
- package/src/cli/harbor.ts +15 -6
- package/src/contract/index.ts +5 -0
- package/src/edge/index.ts +142 -65
- package/src/harbor/catalogue.ts +4 -2
- package/src/harbor/dock.ts +8 -2
- package/src/harbor/harbor.ts +8 -0
- package/src/harbor/terrain.ts +6 -0
- package/src/pointer/bodies.ts +7 -1
- package/src/proof/law.ts +1 -1
package/dist/harbor/catalogue.js
CHANGED
|
@@ -46,7 +46,7 @@ export class Catalogue extends Faculty {
|
|
|
46
46
|
const refused = loaded instanceof Error
|
|
47
47
|
? loaded.message
|
|
48
48
|
: loaded === null
|
|
49
|
-
? `not found at ${row.from}`
|
|
49
|
+
? (this.#harbor.missing(row.from) ?? `not found at ${row.from}`)
|
|
50
50
|
: loaded.module !== module
|
|
51
51
|
? `${row.from} is module ${loaded.module}`
|
|
52
52
|
: this.#harbor.check([...this.#running.values(), loaded]);
|
|
@@ -64,7 +64,7 @@ export class Catalogue extends Faculty {
|
|
|
64
64
|
if (loaded instanceof Error)
|
|
65
65
|
return { error: loaded.message };
|
|
66
66
|
if (loaded === null)
|
|
67
|
-
return { error: 'no such module' };
|
|
67
|
+
return { error: this.#harbor.missing(args.from) ?? 'no such module' };
|
|
68
68
|
const running = new Map(this.#running);
|
|
69
69
|
running.set(loaded.module, loaded);
|
|
70
70
|
const refused = this.#harbor.run([...running.values()]);
|
package/dist/harbor/dock.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface Hosting {
|
|
|
7
7
|
route(pk: string, at: readonly string[] | null): boolean;
|
|
8
8
|
keep(): Promise<boolean>;
|
|
9
9
|
failures(): Failures;
|
|
10
|
+
readonly foreground: boolean;
|
|
10
11
|
sweep(): Promise<string[]>;
|
|
11
12
|
grounds(kind: string): boolean;
|
|
12
13
|
awaken(key: string): Promise<void>;
|
package/dist/harbor/dock.js
CHANGED
|
@@ -54,11 +54,16 @@ export class Dock extends WardBeing {
|
|
|
54
54
|
super(stance);
|
|
55
55
|
this.#hosting = hosting;
|
|
56
56
|
}
|
|
57
|
-
// The census adds what did not stand, for whoever pilots
|
|
57
|
+
// The census adds what did not stand, for whoever pilots, and whether
|
|
58
|
+
// the harbor lives only as long as a screen.
|
|
58
59
|
describe(asker) {
|
|
59
60
|
const blueprint = super.describe(asker);
|
|
60
|
-
if (pilots(asker, this.notes(asker)))
|
|
61
|
-
blueprint
|
|
61
|
+
if (!pilots(asker, this.notes(asker)))
|
|
62
|
+
return blueprint;
|
|
63
|
+
const notes = blueprint.notes;
|
|
64
|
+
notes.failed = this.#hosting.failures();
|
|
65
|
+
if (this.#hosting.foreground)
|
|
66
|
+
notes.foreground = true;
|
|
62
67
|
return blueprint;
|
|
63
68
|
}
|
|
64
69
|
get hosted() {
|
package/dist/harbor/harbor.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class Harbor extends Carrier implements Hosting, Cataloguing, Res
|
|
|
15
15
|
sweep(): Promise<string[]>;
|
|
16
16
|
move(name: string, seed: string, from: string | undefined, to: string | undefined): Promise<string | null>;
|
|
17
17
|
release(name: string, store: string | undefined): Promise<void>;
|
|
18
|
+
get foreground(): boolean;
|
|
18
19
|
failures(): Failures;
|
|
19
20
|
get box(): Ward;
|
|
20
21
|
get dock(): Dock;
|
|
@@ -28,6 +29,7 @@ export declare class Harbor extends Carrier implements Hosting, Cataloguing, Res
|
|
|
28
29
|
carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
29
30
|
classOf(kind: string): BeingClass | undefined;
|
|
30
31
|
load(from: string): Promise<Loaded | null>;
|
|
32
|
+
missing(from: string): string | null;
|
|
31
33
|
check(modules: readonly Loaded[]): string | null;
|
|
32
34
|
run(modules: readonly Loaded[]): string | null;
|
|
33
35
|
stands(kinds: readonly string[]): boolean;
|
package/dist/harbor/harbor.js
CHANGED
|
@@ -153,6 +153,9 @@ export class Harbor extends Carrier {
|
|
|
153
153
|
return being instanceof Store ? [[key, being]] : [];
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
|
+
get foreground() {
|
|
157
|
+
return this.terrain.foreground;
|
|
158
|
+
}
|
|
156
159
|
// Hosting: what did not stand when the harbor opened, each alone.
|
|
157
160
|
failures() {
|
|
158
161
|
return { wards: { ...this.#failed.wards }, routes: { ...this.#failed.routes } };
|
|
@@ -215,6 +218,9 @@ export class Harbor extends Carrier {
|
|
|
215
218
|
load(from) {
|
|
216
219
|
return this.terrain.loader.load(from);
|
|
217
220
|
}
|
|
221
|
+
missing(from) {
|
|
222
|
+
return this.terrain.loader.missing(from);
|
|
223
|
+
}
|
|
218
224
|
check(modules) {
|
|
219
225
|
return Registry.refusal(this.#kit, modules);
|
|
220
226
|
}
|
package/dist/harbor/terrain.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare abstract class Terrain {
|
|
|
15
15
|
readonly faculties: readonly GroundFaculty[];
|
|
16
16
|
readonly opens: readonly Opening[];
|
|
17
17
|
readonly wakes: boolean;
|
|
18
|
+
get foreground(): boolean;
|
|
18
19
|
claim(): Promise<void>;
|
|
19
20
|
stand(_harbor: Harbor): Promise<void>;
|
|
20
21
|
release(): Promise<void>;
|
package/dist/harbor/terrain.js
CHANGED
|
@@ -6,6 +6,12 @@ export class Terrain {
|
|
|
6
6
|
// Whether its carriers wake and listen where their cells say. A harbor
|
|
7
7
|
// opened for one ask and closed again dials and hears nothing.
|
|
8
8
|
wakes = true;
|
|
9
|
+
// Whether the harbor lives only as long as a screen: a tab's, or a worker
|
|
10
|
+
// one tab owns. It is reached only while that screen is open, and hears
|
|
11
|
+
// no push, so its census says so.
|
|
12
|
+
get foreground() {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
9
15
|
// Before the harbor opens: a terrain whose ground another run holds
|
|
10
16
|
// refuses here. Once it stands: whatever the ground serves beside it.
|
|
11
17
|
// Once its carriers sleep: what that served, gone.
|
package/dist/pointer/bodies.d.ts
CHANGED
package/dist/pointer/bodies.js
CHANGED
|
@@ -28,7 +28,9 @@ export class HeldCustody extends Custody {
|
|
|
28
28
|
return Promise.resolve(this.#seed.slice());
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
// Modules handed to this object, found by the name each declares
|
|
31
|
+
// Modules handed to this object, found by the name each declares: the
|
|
32
|
+
// bundle a ground with no disk to load from carries, an edge's or a
|
|
33
|
+
// browser's. A path, a package or any other name is no module of it.
|
|
32
34
|
export class HeldLoader extends Loader {
|
|
33
35
|
#modules = new Map();
|
|
34
36
|
constructor(modules = []) {
|
|
@@ -40,6 +42,10 @@ export class HeldLoader extends Loader {
|
|
|
40
42
|
const m = this.#modules.get(from);
|
|
41
43
|
return Promise.resolve(m ? { module: m.module, version: m.version, classes: m.classes, from } : null);
|
|
42
44
|
}
|
|
45
|
+
missing(from) {
|
|
46
|
+
const carried = [...this.#modules.keys()];
|
|
47
|
+
return `${from} is no module this bundle carries${carried.length === 0 ? ', and it carries none' : `: it carries ${carried.join(', ')}`}`;
|
|
48
|
+
}
|
|
43
49
|
}
|
|
44
50
|
// Entries that live as long as this object, copied in and out, which is
|
|
45
51
|
// what a restart in one process finds.
|
package/dist/proof/law.js
CHANGED
|
@@ -194,7 +194,7 @@ export const lawScenes = (t, expect) => [
|
|
|
194
194
|
expect.same(lost.names, ['w'], 'the rest of the harbor stands');
|
|
195
195
|
expect.same((await census(lost)).beings.echo?.absent, true);
|
|
196
196
|
expect.same(await lost.root('ask', { being: 'catalogue', method: 'modules' }), {
|
|
197
|
-
answer: { [law.module]: { version: '1', running: null, from: law.module, failed:
|
|
197
|
+
answer: { [law.module]: { version: '1', running: null, from: law.module, failed: `${law.module} is no module this bundle carries, and it carries none` } },
|
|
198
198
|
});
|
|
199
199
|
const again = await world.restart('h', { ...t.again(bodies), loader: new HeldLoader([law]) });
|
|
200
200
|
expect.same((await census(again)).beings.echo?.absent, false);
|
package/package.json
CHANGED
package/src/browser/index.ts
CHANGED
|
@@ -175,6 +175,15 @@ export class BrowserTerrain extends PointerTerrain {
|
|
|
175
175
|
this.where = where;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// A shared or a service worker outlives any one tab, and a service
|
|
179
|
+
// worker is woken for a push with none open. Anywhere else, a page or a
|
|
180
|
+
// worker a tab made, the harbor lives with that tab.
|
|
181
|
+
override get foreground(): boolean {
|
|
182
|
+
const scope = globalThis as unknown as Record<string, unknown>;
|
|
183
|
+
const is = (name: string): boolean => typeof scope[name] === 'function' && globalThis instanceof (scope[name] as new () => unknown);
|
|
184
|
+
return !is('SharedWorkerGlobalScope') && !is('ServiceWorkerGlobalScope');
|
|
185
|
+
}
|
|
186
|
+
|
|
178
187
|
override async claim(): Promise<void> {
|
|
179
188
|
const locks = (globalThis as { navigator?: { locks?: LockManager } }).navigator?.locks;
|
|
180
189
|
if (!locks) return;
|
package/src/cli/command.ts
CHANGED
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
// --with-seed the seed goes too
|
|
20
20
|
// nervur import <file> a carried harbor into an empty
|
|
21
21
|
// folder, `-` being this input
|
|
22
|
-
// nervur edge pack <file>
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// nervur edge dev <file> [--port P]
|
|
22
|
+
// nervur edge pack <file> <name> the harbor for an edge, under its
|
|
23
|
+
// name, in the pack the file holds:
|
|
24
|
+
// its seed sealed under
|
|
25
|
+
// $NERVUR_SECRET, and its places
|
|
26
|
+
// nervur edge dev <file> <name> [--port P] the harbor packed so, and the pack
|
|
27
27
|
// run by the worker's own wrangler
|
|
28
28
|
// dev, from this folder, at
|
|
29
|
-
// http://127.0.0.1:P
|
|
29
|
+
// http://127.0.0.1:P/<name>/quo
|
|
30
30
|
// nervur open <key> <kind> a faculty stands in the box ward
|
|
31
31
|
// nervur host <ward> [--seed S] [--store K] a hosted ward stands, kept by the
|
|
32
32
|
// root memory or the store K
|
|
@@ -197,11 +197,11 @@ export const main = async (argv: string[], env: Record<string, string | undefine
|
|
|
197
197
|
return out({ landed: await land(dir, JSON.parse(text)) });
|
|
198
198
|
}
|
|
199
199
|
if (command === 'edge') {
|
|
200
|
-
if ((words[0] !== 'pack' && words[0] !== 'dev') || words.length !==
|
|
200
|
+
if ((words[0] !== 'pack' && words[0] !== 'dev') || words.length !== 3) throw new Error('edge takes pack or dev, a file, and the harbor name');
|
|
201
201
|
if (flags.via !== undefined) throw new Error(`edge ${words[0]} reads this folder, so it takes no --via`);
|
|
202
202
|
if (await isServed(dir)) throw new Error(`a harbor is served from ${dir}: stop it first`);
|
|
203
|
-
if (words[0] === 'pack') return out(await pack(dir, words[1]
|
|
204
|
-
const dev = await edgeDev(dir, words[1]!, process.cwd(), flags.port === undefined ? EDGE_PORT : Number(flags.port));
|
|
203
|
+
if (words[0] === 'pack') return out(await pack(dir, words[1]!, words[2]!, env.NERVUR_SECRET));
|
|
204
|
+
const dev = await edgeDev(dir, words[1]!, words[2]!, process.cwd(), flags.port === undefined ? EDGE_PORT : Number(flags.port), env.NERVUR_SECRET);
|
|
205
205
|
const stop = (): void => void dev.close().then(() => process.exit(0));
|
|
206
206
|
process.once('SIGINT', stop);
|
|
207
207
|
process.once('SIGTERM', stop);
|
|
@@ -221,8 +221,9 @@ export const main = async (argv: string[], env: Record<string, string | undefine
|
|
|
221
221
|
if (words.length !== 2 && words.length !== 3) throw new Error('pilot takes a name, an invitation, and an address where the invitation carries none');
|
|
222
222
|
const [name, invitation, at] = words as [string, string, string | undefined];
|
|
223
223
|
const held = JSON.parse(invitation) as JsonObject;
|
|
224
|
-
const census = (await request(dir, {})) as { answer?: { beings?: JsonObject } };
|
|
225
|
-
const
|
|
224
|
+
const census = (await request(dir, {})) as { answer?: { notes?: { beings?: JsonObject } } };
|
|
225
|
+
const beings = census.answer?.notes?.beings;
|
|
226
|
+
const web = beings !== undefined && Object.hasOwn(beings, WEB_OPENING.key);
|
|
226
227
|
for (const req of pilotRequests(name, held, at, web)) {
|
|
227
228
|
const answer = await request(dir, req);
|
|
228
229
|
if (!failed(answer)) continue;
|
package/src/cli/edge.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// `nervur edge dev`: an edge harbor on this machine, as it will stand on
|
|
3
|
-
// Cloudflare. The harbor of a folder is packed
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
3
|
+
// Cloudflare. The harbor of a folder is packed under its name into the
|
|
4
|
+
// pack the worker imports, beside the harbors the pack holds already, and
|
|
5
|
+
// the worker's own `wrangler dev` runs them all, in the folder the command
|
|
6
|
+
// runs from, with the secret handed as `NERVUR_SECRET` through the
|
|
7
|
+
// environment so no listing of processes shows it. The secret is the one
|
|
8
|
+
// the command's environment names, or one drawn once and kept under
|
|
9
|
+
// `.nervur-edge`, beside the objects' storage, so a harbor landed once
|
|
10
|
+
// stands again across restarts, as it does on Cloudflare.
|
|
9
11
|
//
|
|
10
12
|
// wrangler is the worker's tool and never the kit's: the one who deploys
|
|
11
13
|
// an edge has it already, and `edge dev` finds it where npm put it.
|
|
12
14
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
15
|
+
import { existsSync } from 'node:fs';
|
|
16
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
13
17
|
import { join } from 'node:path';
|
|
18
|
+
import { hex } from '../crypto/index.ts';
|
|
19
|
+
import { CryptoEntropy } from '../pointer/index.ts';
|
|
14
20
|
import { pack } from './harbor.ts';
|
|
15
21
|
|
|
16
22
|
export const EDGE_STATE = '.nervur-edge';
|
|
@@ -38,17 +44,30 @@ const ready = async (child: ChildProcess, url: string, said: () => string): Prom
|
|
|
38
44
|
throw new Error(`wrangler dev never answered at ${url}: ${said()}`);
|
|
39
45
|
};
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
// The secret `edge dev` runs under: the one named, or the one kept here.
|
|
48
|
+
const devSecret = async (state: string, named: string | undefined): Promise<string> => {
|
|
49
|
+
if (named) return named;
|
|
50
|
+
const file = join(state, 'secret');
|
|
51
|
+
if (!existsSync(file)) {
|
|
52
|
+
await mkdir(state, { recursive: true, mode: 0o700 });
|
|
53
|
+
await writeFile(file, `${hex(new CryptoEntropy().draw(32))}\n`, { mode: 0o600, flag: 'wx' });
|
|
54
|
+
}
|
|
55
|
+
return (await readFile(file, 'utf8')).trim();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const edgeDev = async (dir: string, file: string, name: string, cwd: string, port: number, named?: string): Promise<EdgeDev> => {
|
|
59
|
+
const state = join(cwd, EDGE_STATE);
|
|
60
|
+
const secret = await devSecret(state, named);
|
|
61
|
+
const { places } = (await pack(dir, file, name, secret)) as { places: number };
|
|
62
|
+
const child = spawn('npx', ['--no-install', 'wrangler', 'dev', '--ip', '127.0.0.1', '--port', String(port), '--persist-to', state, '--log-level', 'warn'], {
|
|
44
63
|
cwd,
|
|
45
|
-
env: { ...process.env,
|
|
64
|
+
env: { ...process.env, NERVUR_SECRET: secret, CLOUDFLARE_INCLUDE_PROCESS_ENV: 'true' },
|
|
46
65
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
47
66
|
detached: true,
|
|
48
67
|
});
|
|
49
68
|
let err = '';
|
|
50
69
|
child.stderr!.on('data', (chunk: Buffer) => (err = `${err}${chunk.toString('utf8')}`.slice(-4000)));
|
|
51
|
-
const at = `http://127.0.0.1:${String(port)}/quo`;
|
|
70
|
+
const at = `http://127.0.0.1:${String(port)}/${name}/quo`;
|
|
52
71
|
const close = (): Promise<void> =>
|
|
53
72
|
new Promise((done) => {
|
|
54
73
|
if (child.exitCode !== null) return done();
|
package/src/cli/harbor.ts
CHANGED
|
@@ -10,7 +10,8 @@ import { existsSync } from 'node:fs';
|
|
|
10
10
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import type { JsonObject } from '../being/index.ts';
|
|
13
|
-
import { hex } from '../crypto/index.ts';
|
|
13
|
+
import { hex, unhex } from '../crypto/index.ts';
|
|
14
|
+
import { isHarborName, readPack, sealSeed, type Pack } from '../edge/index.ts';
|
|
14
15
|
import { FolderCustody, FolderMemory } from '../folder/index.ts';
|
|
15
16
|
import { carryPlaces, Harbor, landPlaces, readCarried, rootLine, type CarrierFaculty, type Carried, type RootRequest } from '../harbor/index.ts';
|
|
16
17
|
import { askServed, MODULES, NodeTerrain, socketOf } from '../node/index.ts';
|
|
@@ -49,13 +50,21 @@ export const carry = async (dir: string, seed: boolean): Promise<Carried> => {
|
|
|
49
50
|
};
|
|
50
51
|
};
|
|
51
52
|
|
|
52
|
-
// What `nervur edge pack` hands a deploy: the
|
|
53
|
-
//
|
|
54
|
-
|
|
53
|
+
// What `nervur edge pack` hands a deploy: the harbor of a folder, under
|
|
54
|
+
// its name, in the pack the worker bundles, its seed sealed under the
|
|
55
|
+
// worker's `NERVUR_SECRET` beside its sealed places. Every other harbor
|
|
56
|
+
// the pack holds stays, so one worker carries them all, and nothing in the
|
|
57
|
+
// pack opens without the secret.
|
|
58
|
+
export const pack = async (dir: string, file: string, name: string, secret: string | undefined): Promise<JsonObject> => {
|
|
59
|
+
if (!isHarborName(name)) throw new Error(`${name} is no harbor name: lowercase letters, digits and -`);
|
|
60
|
+
if (!secret) throw new Error('edge pack seals the seed under NERVUR_SECRET, and none is set');
|
|
55
61
|
if (!existsSync(join(dir, 'seed'))) throw new Error(`no harbor stands at ${dir}`);
|
|
56
62
|
const { seed, places } = await carry(dir, true);
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
const held = existsSync(file) ? readPack(JSON.parse(await readFile(file, 'utf8'))) : {};
|
|
64
|
+
if (held === null) throw new Error(`${file} is no pack`);
|
|
65
|
+
const packed: Pack = { ...held, [name]: { sealed: await sealSeed(secret, unhex(seed!)), places } };
|
|
66
|
+
await writeFile(file, `${JSON.stringify(packed)}\n`, { mode: 0o600 });
|
|
67
|
+
return { packed: name, package: file, harbors: Object.keys(packed).sort(), places: Object.keys(places).length };
|
|
59
68
|
};
|
|
60
69
|
|
|
61
70
|
// A carried harbor into an empty folder. A folder holding a seed or a
|
package/src/contract/index.ts
CHANGED
|
@@ -67,6 +67,11 @@ export type Loaded = Module & { readonly from: string };
|
|
|
67
67
|
// one thing beside the seed a terrain hands in: a map, an import, a bundle.
|
|
68
68
|
export abstract class Loader {
|
|
69
69
|
abstract load(from: string): Promise<Loaded | null>;
|
|
70
|
+
// Why a name it found nothing under holds nothing, where it can say more
|
|
71
|
+
// than that: a bundle names that code comes from it alone.
|
|
72
|
+
missing(_from: string): string | null {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
// Bytes to a ward pk in; bytes, or null for nothing, out. A harbor hands a
|