nervur 0.20.2 → 0.20.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 +60 -15
- package/dist/cli/command.d.ts +1 -1
- package/dist/cli/command.js +30 -9
- package/dist/cli/harbor.d.ts +2 -5
- package/dist/cli/harbor.js +28 -36
- package/dist/cli/main.js +0 -0
- package/dist/edge/index.d.ts +17 -3
- package/dist/edge/index.js +40 -12
- package/dist/harbor/index.d.ts +1 -1
- package/dist/harbor/index.js +1 -1
- package/dist/harbor/package.d.ts +8 -0
- package/dist/harbor/package.js +37 -1
- package/dist/harbor/relay.js +20 -1
- package/dist/node/index.d.ts +5 -1
- package/dist/node/index.js +77 -13
- package/package.json +3 -2
- package/src/cli/command.ts +27 -9
- package/src/cli/harbor.ts +26 -35
- package/src/edge/index.ts +76 -22
- package/src/harbor/index.ts +1 -1
- package/src/harbor/package.ts +39 -1
- package/src/harbor/relay.ts +20 -1
- package/src/node/index.ts +77 -13
package/src/node/index.ts
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
//
|
|
14
14
|
// One run holds a folder: a terrain refuses a folder another run serves.
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
|
-
import { chmod, rm } from 'node:fs/promises';
|
|
16
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
17
18
|
import { connect, createServer, type Server } from 'node:net';
|
|
18
19
|
import { homedir, tmpdir } from 'node:os';
|
|
19
|
-
import { join, resolve } from 'node:path';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
20
21
|
import { pathToFileURL } from 'node:url';
|
|
21
22
|
import type { JsonObject } from '../being/index.ts';
|
|
22
23
|
import { Loader, type Loaded, type Module } from '../contract/index.ts';
|
|
@@ -43,36 +44,99 @@ export const socketOf = (dir: string): string => {
|
|
|
43
44
|
return join(tmpdir(), `nervur-${createHash('sha256').update(resolve(dir)).digest('hex').slice(0, 32)}.sock`);
|
|
44
45
|
};
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// A specifier that names a file rather than a package.
|
|
48
|
+
export const isFile = (from: string): boolean => /^(\.{1,2}[\\/]|[\\/]|[A-Za-z]:[\\/])/.test(from) || /\.(m?[jt]s|c[jt]s)$/.test(from);
|
|
49
|
+
|
|
50
|
+
// Code from a file or an installed package: a module that exports
|
|
51
|
+
// `module`, `version` and `classes`, found again by where it was found, a
|
|
52
|
+
// file by its absolute path and a package by its name, resolved from each
|
|
53
|
+
// folder of `bases` in order. What cannot be imported, or exports no
|
|
54
|
+
// module, is none.
|
|
49
55
|
export class ImportLoader extends Loader {
|
|
56
|
+
readonly #bases: readonly string[];
|
|
57
|
+
constructor(bases: readonly string[] = [process.cwd()]) {
|
|
58
|
+
super();
|
|
59
|
+
this.#bases = bases;
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
async load(from: string): Promise<Loaded | null> {
|
|
51
|
-
const
|
|
63
|
+
const found = isFile(from) ? resolve(from) : this.#package(from);
|
|
64
|
+
if (found === null) return null;
|
|
52
65
|
let exported: { module?: unknown; version?: unknown; classes?: unknown };
|
|
53
66
|
try {
|
|
54
|
-
exported = (await import(pathToFileURL(
|
|
67
|
+
exported = (await import(pathToFileURL(found).href)) as typeof exported;
|
|
55
68
|
} catch {
|
|
56
69
|
return null;
|
|
57
70
|
}
|
|
58
71
|
const { module, version, classes } = exported;
|
|
59
72
|
if (typeof module !== 'string' || typeof version !== 'string' || !Array.isArray(classes)) return null;
|
|
60
|
-
return { module, version, classes, from:
|
|
73
|
+
return { module, version, classes, from: isFile(from) ? found : from };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#package(name: string): string | null {
|
|
77
|
+
for (const base of this.#bases) {
|
|
78
|
+
try {
|
|
79
|
+
return createRequire(join(resolve(base), 'noop.js')).resolve(name);
|
|
80
|
+
} catch {
|
|
81
|
+
// Not installed from here; the next base may hold it.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
61
85
|
}
|
|
62
86
|
}
|
|
63
87
|
|
|
64
|
-
//
|
|
88
|
+
// A module by its id, the name it declares, so the catalogue's cells name
|
|
89
|
+
// code the same way on every ground. `add` may name a module by its id, a
|
|
90
|
+
// file, or an installed package, and the ground works out which. The
|
|
91
|
+
// modules a harbor is handed are found by their id first. Where else an id
|
|
92
|
+
// is found, a file's path or a package's name, is this ground's own
|
|
93
|
+
// knowledge, kept beside the harbor in `<dir>/modules.json` and never in
|
|
94
|
+
// the sealed package, since another ground finds the same id in its own
|
|
95
|
+
// bundle. Packages resolve from where the command runs, then from the
|
|
96
|
+
// harbor's folder.
|
|
65
97
|
export class NodeLoader extends ImportLoader {
|
|
66
98
|
readonly #held: HeldLoader;
|
|
67
|
-
|
|
68
|
-
|
|
99
|
+
readonly #map: string | undefined;
|
|
100
|
+
constructor(modules: readonly Module[] = [], dir?: string) {
|
|
101
|
+
super(dir === undefined ? [process.cwd()] : [process.cwd(), dir]);
|
|
69
102
|
this.#held = new HeldLoader(modules);
|
|
103
|
+
this.#map = dir === undefined ? undefined : join(dir, MODULES);
|
|
70
104
|
}
|
|
105
|
+
|
|
71
106
|
override async load(from: string): Promise<Loaded | null> {
|
|
72
|
-
|
|
107
|
+
const held = await this.#held.load(from);
|
|
108
|
+
if (held) return held;
|
|
109
|
+
const known = await this.#known();
|
|
110
|
+
const where = known[from];
|
|
111
|
+
if (where !== undefined) {
|
|
112
|
+
const loaded = await super.load(where);
|
|
113
|
+
return loaded?.module === from ? { ...loaded, from } : null;
|
|
114
|
+
}
|
|
115
|
+
const loaded = await super.load(from);
|
|
116
|
+
if (!loaded) return null;
|
|
117
|
+
if (this.#map && known[loaded.module] !== loaded.from) await this.#keep({ ...known, [loaded.module]: loaded.from });
|
|
118
|
+
return { ...loaded, from: loaded.module };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async #known(): Promise<Record<string, string>> {
|
|
122
|
+
if (!this.#map) return {};
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(await readFile(this.#map, 'utf8')) as Record<string, string>;
|
|
125
|
+
} catch {
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async #keep(files: Record<string, string>): Promise<void> {
|
|
131
|
+
await mkdir(dirname(this.#map!), { recursive: true, mode: 0o700 });
|
|
132
|
+
await writeFile(`${this.#map!}.new`, `${JSON.stringify(files, null, 2)}\n`, { mode: 0o600 });
|
|
133
|
+
await rename(`${this.#map!}.new`, this.#map!);
|
|
73
134
|
}
|
|
74
135
|
}
|
|
75
136
|
|
|
137
|
+
// Where a Node harbor's module ids find their files.
|
|
138
|
+
export const MODULES = 'modules.json';
|
|
139
|
+
|
|
76
140
|
// One root request over a served harbor's socket, or undefined where none
|
|
77
141
|
// is served.
|
|
78
142
|
export const askServed = (dir: string, request: RootRequest): Promise<JsonObject | undefined> =>
|
|
@@ -107,7 +171,7 @@ export class NodeTerrain extends PointerTerrain {
|
|
|
107
171
|
const entropy = new CryptoEntropy();
|
|
108
172
|
super({
|
|
109
173
|
entropy,
|
|
110
|
-
loader: new NodeLoader(parts.modules),
|
|
174
|
+
loader: new NodeLoader(parts.modules, parts.dir),
|
|
111
175
|
custody: new FolderCustody(parts.dir, entropy),
|
|
112
176
|
memory: new FolderMemory(parts.dir),
|
|
113
177
|
faculties: [tcpFaculty, webGround, webPush],
|