nervur 0.20.1 → 0.20.3

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.
Files changed (131) hide show
  1. package/README.md +174 -12
  2. package/dist/browser/index.d.ts +35 -0
  3. package/dist/browser/index.js +188 -0
  4. package/dist/browser/worker.d.ts +12 -0
  5. package/dist/browser/worker.js +40 -0
  6. package/dist/browser.d.ts +1 -0
  7. package/dist/browser.js +7 -0
  8. package/dist/cli/command.d.ts +1 -1
  9. package/dist/cli/command.js +84 -20
  10. package/dist/cli/harbor.d.ts +7 -12
  11. package/dist/cli/harbor.js +73 -123
  12. package/dist/contract/index.d.ts +2 -1
  13. package/dist/contract/index.js +3 -7
  14. package/dist/edge/index.d.ts +71 -0
  15. package/dist/edge/index.js +334 -0
  16. package/dist/edge.d.ts +1 -0
  17. package/dist/edge.js +8 -0
  18. package/dist/folder/index.d.ts +4 -0
  19. package/dist/folder/index.js +57 -14
  20. package/dist/harbor/carrying.d.ts +17 -0
  21. package/dist/harbor/carrying.js +38 -0
  22. package/dist/harbor/catalogue.d.ts +2 -0
  23. package/dist/harbor/catalogue.js +28 -20
  24. package/dist/harbor/dock.d.ts +22 -3
  25. package/dist/harbor/dock.js +69 -13
  26. package/dist/harbor/harbor.d.ts +14 -4
  27. package/dist/harbor/harbor.js +144 -15
  28. package/dist/harbor/index.d.ts +6 -3
  29. package/dist/harbor/index.js +4 -1
  30. package/dist/harbor/package.d.ts +10 -0
  31. package/dist/harbor/package.js +52 -1
  32. package/dist/harbor/probe.d.ts +18 -0
  33. package/dist/harbor/probe.js +15 -0
  34. package/dist/harbor/registry.d.ts +1 -0
  35. package/dist/harbor/registry.js +11 -0
  36. package/dist/harbor/relay.d.ts +15 -0
  37. package/dist/harbor/relay.js +406 -0
  38. package/dist/harbor/root-line.js +4 -2
  39. package/dist/harbor/terrain.d.ts +12 -0
  40. package/dist/harbor/terrain.js +19 -0
  41. package/dist/http/index.d.ts +4 -0
  42. package/dist/http/index.js +156 -0
  43. package/dist/http/websocket.d.ts +29 -0
  44. package/dist/http/websocket.js +133 -0
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.js +8 -2
  47. package/dist/line/answer.d.ts +8 -0
  48. package/dist/line/answer.js +60 -0
  49. package/dist/line/frame.d.ts +34 -0
  50. package/dist/line/frame.js +81 -0
  51. package/dist/line/ground.d.ts +3 -0
  52. package/dist/line/ground.js +9 -0
  53. package/dist/line/index.d.ts +5 -0
  54. package/dist/line/index.js +9 -0
  55. package/dist/line/push.d.ts +7 -0
  56. package/dist/line/push.js +92 -0
  57. package/dist/line/web.d.ts +30 -0
  58. package/dist/line/web.js +181 -0
  59. package/dist/node/index.d.ts +36 -0
  60. package/dist/node/index.js +228 -0
  61. package/dist/node.d.ts +1 -0
  62. package/dist/node.js +7 -0
  63. package/dist/pointer/bodies.d.ts +2 -0
  64. package/dist/pointer/bodies.js +10 -0
  65. package/dist/pointer/world.d.ts +7 -1
  66. package/dist/pointer/world.js +6 -0
  67. package/dist/quo/address.d.ts +19 -0
  68. package/dist/quo/address.js +84 -0
  69. package/dist/quo/door.d.ts +1 -1
  70. package/dist/quo/door.js +4 -2
  71. package/dist/quo/index.d.ts +1 -0
  72. package/dist/quo/index.js +1 -0
  73. package/dist/quo/invitation.d.ts +1 -0
  74. package/dist/quo/invitation.js +9 -5
  75. package/dist/tcp/frame.d.ts +0 -4
  76. package/dist/tcp/frame.js +0 -10
  77. package/dist/tcp/index.d.ts +8 -2
  78. package/dist/tcp/index.js +123 -27
  79. package/dist/ward/index.d.ts +2 -2
  80. package/dist/ward/index.js +1 -1
  81. package/dist/ward/stance.d.ts +2 -0
  82. package/dist/ward/stance.js +3 -1
  83. package/dist/ward/ward-being.d.ts +2 -1
  84. package/dist/ward/ward-being.js +1 -1
  85. package/dist/ward/ward.d.ts +7 -0
  86. package/dist/ward/ward.js +6 -0
  87. package/package.json +21 -2
  88. package/src/browser/index.ts +207 -0
  89. package/src/browser/worker.ts +66 -0
  90. package/src/browser.ts +9 -0
  91. package/src/cli/command.ts +74 -20
  92. package/src/cli/harbor.ts +73 -116
  93. package/src/contract/index.ts +11 -8
  94. package/src/edge/index.ts +413 -0
  95. package/src/edge.ts +10 -0
  96. package/src/folder/index.ts +57 -12
  97. package/src/harbor/carrying.ts +56 -0
  98. package/src/harbor/catalogue.ts +32 -17
  99. package/src/harbor/dock.ts +88 -17
  100. package/src/harbor/harbor.ts +146 -17
  101. package/src/harbor/index.ts +6 -3
  102. package/src/harbor/package.ts +55 -1
  103. package/src/harbor/probe.ts +34 -0
  104. package/src/harbor/registry.ts +11 -0
  105. package/src/harbor/relay.ts +418 -0
  106. package/src/harbor/root-line.ts +4 -1
  107. package/src/harbor/terrain.ts +28 -2
  108. package/src/http/index.ts +150 -0
  109. package/src/http/websocket.ts +124 -0
  110. package/src/index.ts +35 -2
  111. package/src/line/answer.ts +59 -0
  112. package/src/line/frame.ts +88 -0
  113. package/src/line/ground.ts +19 -0
  114. package/src/line/index.ts +9 -0
  115. package/src/line/push.ts +112 -0
  116. package/src/line/web.ts +227 -0
  117. package/src/node/index.ts +234 -0
  118. package/src/node.ts +9 -0
  119. package/src/pointer/bodies.ts +11 -0
  120. package/src/pointer/world.ts +18 -2
  121. package/src/quo/address.ts +87 -0
  122. package/src/quo/door.ts +4 -2
  123. package/src/quo/index.ts +1 -0
  124. package/src/quo/invitation.ts +10 -6
  125. package/src/stand/stand.ts +60 -21
  126. package/src/tcp/index.ts +142 -31
  127. package/src/ward/index.ts +2 -2
  128. package/src/ward/stance.ts +6 -1
  129. package/src/ward/ward-being.ts +2 -2
  130. package/src/ward/ward.ts +16 -0
  131. package/src/tcp/frame.ts +0 -78
@@ -7,13 +7,27 @@
7
7
  // invitation for whoever made it
8
8
  // nervur serve [--dir D] [--host H] [--port P] the harbor served: Quo over TCP,
9
9
  // and the root line on its socket
10
- // nervur module add <file> a module the catalogue runs
10
+ // nervur module add <file | package | id> a module the catalogue runs, kept
11
+ // by the id it declares
11
12
  // nervur module remove <module> a module it runs no more
12
13
  // nervur modules what the catalogue runs
13
14
  // nervur census [--ward W] the empty ask
15
+ // nervur sweep every place of the folder this
16
+ // harbor does not name, gone
17
+ // nervur export [--with-seed] the harbor as one JSON line, to
18
+ // carry it to another ground; with
19
+ // --with-seed the seed goes too
20
+ // nervur import <file> a carried harbor into an empty
21
+ // folder, `-` being this input
22
+ // nervur edge pack <file> the harbor for an edge: its seed
23
+ // printed, for the worker's
24
+ // NERVUR_SEED, and its sealed
25
+ // package written to the file
14
26
  // nervur open <key> <kind> a faculty stands in the box ward
15
27
  // nervur host <ward> [--seed S] a hosted ward stands
16
- // nervur route <ward pk> <host:port | none> where a ward is reached
28
+ // nervur route <ward pk> <address... | none> where a ward is reached, in order
29
+ // nervur reach <address...> where this harbor is reached, as
30
+ // every invitation it gives says
17
31
  // nervur boot [--ward W] <key> <kind> a being stands
18
32
  // nervur unboot [--ward W] <being> a being goes
19
33
  // nervur public [--ward W] <key | none> the being a stranger reaches
@@ -21,8 +35,9 @@
21
35
  // nervur ask [--ward W] [being] [method] [args] a being, asked as the owner
22
36
  // nervur own [--ward W] <id> an invitation for an owner
23
37
  // nervur disown [--ward W] <id> an owner no more
24
- // nervur pilot <name> <invitation> <host:port> a far ward held as its owner, by a
25
- // pilot being of the box ward
38
+ // nervur pilot <name> <invitation> [address] a far ward held as its owner, by a
39
+ // pilot being of the box ward, at
40
+ // the address or the invitation's at
26
41
  // nervur <command> --via <name> ... the command asked of the far ward
27
42
  // that pilot holds, as its owner
28
43
  //
@@ -31,11 +46,16 @@
31
46
  // ward, and a pilot holds one ward, so --via takes no --ward. The exit is 1
32
47
  // when the answer is an error.
33
48
  import { existsSync } from 'node:fs';
34
- import { homedir } from 'node:os';
49
+ import { readFile } from 'node:fs/promises';
35
50
  import { join, resolve } from 'node:path';
36
- import { open, request, serve } from './harbor.js';
37
- const USAGE = 'usage: nervur <init|serve|module|modules|census|open|host|route|boot|unboot|public|invite|ask|own|disown|pilot> [args] [--dir D] [--ward W] [--via V] [--seed S] [--host H] [--port P]';
51
+ import { WEB_OPENING } from '../line/index.js';
52
+ import { harborDir, isFile } from '../node/index.js';
53
+ import { readAt, webAddress } from '../quo/index.js';
54
+ import { carry, isServed, land, open, pack, request, serve } from './harbor.js';
55
+ const USAGE = 'usage: nervur <init|serve|module|modules|census|sweep|open|host|route|reach|boot|unboot|public|invite|ask|own|disown|pilot|export|import|edge> [args] [--dir D] [--ward W] [--via V] [--seed S] [--with-seed] [--host H] [--port P]';
38
56
  const FLAGS = new Set(['dir', 'ward', 'via', 'seed', 'host', 'port']);
57
+ // The one flag that stands alone, since what it names is a yes.
58
+ const ALONE = 'with-seed';
39
59
  export const parse = (argv) => {
40
60
  const flags = {};
41
61
  const words = [];
@@ -44,6 +64,8 @@ export const parse = (argv) => {
44
64
  const name = arg.startsWith('--') ? arg.slice(2) : undefined;
45
65
  if (name === undefined)
46
66
  words.push(arg);
67
+ else if (name === ALONE)
68
+ flags[name] = 'yes';
47
69
  else if (!FLAGS.has(name) || argv[i + 1] === undefined)
48
70
  throw new Error(`--${name} is no flag with a value`);
49
71
  else
@@ -52,6 +74,13 @@ export const parse = (argv) => {
52
74
  const [command, ...rest] = words;
53
75
  return { ...(command === undefined ? {} : { command }), words: rest, flags };
54
76
  };
77
+ // What this command was handed on its input, for `import -`.
78
+ const standingIn = async () => {
79
+ let text = '';
80
+ for await (const chunk of process.stdin)
81
+ text += chunk.toString('utf8');
82
+ return text;
83
+ };
55
84
  const none = (word) => (word === 'none' ? null : word);
56
85
  const json = (word) => (word === undefined ? undefined : JSON.parse(word));
57
86
  const catalogue = (method, args = {}) => ({ method: 'ask', args: { being: 'catalogue', method, args } });
@@ -67,13 +96,15 @@ export const requestOf = (command, words, flags) => {
67
96
  case 'module': {
68
97
  const [verb, what] = need(2);
69
98
  if (verb === 'add')
70
- return catalogue('add', { from: resolve(what) });
99
+ return catalogue('add', { from: isFile(what) ? resolve(what) : what });
71
100
  if (verb === 'remove')
72
101
  return catalogue('remove', { module: what });
73
102
  throw new Error('module is add or remove');
74
103
  }
75
104
  case 'modules':
76
105
  return (need(0), catalogue('modules'));
106
+ case 'sweep':
107
+ return (need(0), { method: 'sweep', args: {} });
77
108
  case 'census':
78
109
  return (need(0), at);
79
110
  case 'open': {
@@ -85,9 +116,15 @@ export const requestOf = (command, words, flags) => {
85
116
  return { method: 'host', args: { ward: ward, ...(flags.seed === undefined ? {} : { seed: flags.seed }) } };
86
117
  }
87
118
  case 'route': {
88
- const [ward, where] = need(2);
89
- return { method: 'route', args: { ward: ward, at: none(where) } };
119
+ const [ward, ...where] = words;
120
+ if (ward === undefined || where.length === 0)
121
+ throw new Error('route takes a ward pk and its addresses, or none');
122
+ return { method: 'route', args: { ward, at: where.length === 1 && where[0] === 'none' ? null : where } };
90
123
  }
124
+ case 'reach':
125
+ if (words.length === 0)
126
+ throw new Error('reach takes the addresses this harbor is reached at');
127
+ return { method: 'reach', args: { at: words } };
91
128
  case 'boot': {
92
129
  const [key, kind] = need(2);
93
130
  return { ...at, method: 'boot', args: { key: key, class: kind } };
@@ -124,7 +161,7 @@ export const main = async (argv, env, print) => {
124
161
  const { command, words, flags } = parse(argv);
125
162
  if (command === undefined)
126
163
  throw new Error(USAGE);
127
- const dir = flags.dir ?? env.NERVUR_DIR ?? join(homedir(), '.nervur');
164
+ const dir = flags.dir ?? harborDir(env);
128
165
  const out = (answer) => {
129
166
  print(JSON.stringify(answer));
130
167
  return failed(answer) ? 1 : 0;
@@ -134,15 +171,36 @@ export const main = async (argv, env, print) => {
134
171
  throw new Error('init takes no argument');
135
172
  if (existsSync(join(dir, 'seed')))
136
173
  return out({ error: `a harbor stands at ${dir}` });
137
- const { harbor, carrier } = await open(dir);
174
+ const harbor = await open(dir);
138
175
  try {
139
176
  const owner = await harbor.root('own', { id: 'owner' });
140
177
  return out({ pk: harbor.box.pk, owner: owner });
141
178
  }
142
179
  finally {
143
- await carrier.close();
180
+ await harbor.close();
144
181
  }
145
182
  }
183
+ if (command === 'export' || command === 'import') {
184
+ if (words.length !== (command === 'import' ? 1 : 0))
185
+ throw new Error(`${command} takes ${command === 'import' ? 'one file' : 'no argument'}`);
186
+ if (flags.via !== undefined)
187
+ throw new Error(`${command} reads this folder, so it takes no --via`);
188
+ if (await isServed(dir))
189
+ throw new Error(`a harbor is served from ${dir}: stop it first`);
190
+ if (command === 'export')
191
+ return out((await carry(dir, flags[ALONE] !== undefined)));
192
+ const text = words[0] === '-' ? await standingIn() : await readFile(words[0], 'utf8');
193
+ return out({ landed: await land(dir, JSON.parse(text)) });
194
+ }
195
+ if (command === 'edge') {
196
+ if (words[0] !== 'pack' || words.length !== 2)
197
+ throw new Error('edge takes pack and a file');
198
+ if (flags.via !== undefined)
199
+ throw new Error('edge pack reads this folder, so it takes no --via');
200
+ if (await isServed(dir))
201
+ throw new Error(`a harbor is served from ${dir}: stop it first`);
202
+ return out(await pack(dir, words[1]));
203
+ }
146
204
  if (command === 'serve') {
147
205
  if (words.length > 0)
148
206
  throw new Error('serve takes no argument');
@@ -150,15 +208,17 @@ export const main = async (argv, env, print) => {
150
208
  const stop = () => void served.close().then(() => process.exit(0));
151
209
  process.once('SIGINT', stop);
152
210
  process.once('SIGTERM', stop);
153
- print(JSON.stringify({ pk: served.pk, at: served.at, root: served.root }));
211
+ print(JSON.stringify({ pk: served.pk, at: served.at, listening: served.listening, root: served.root }));
154
212
  return SERVING;
155
213
  }
156
214
  if (command === 'pilot') {
157
- if (words.length !== 3)
158
- throw new Error('pilot takes 3 arguments');
215
+ if (words.length !== 2 && words.length !== 3)
216
+ throw new Error('pilot takes a name, an invitation, and an address where the invitation carries none');
159
217
  const [name, invitation, at] = words;
160
218
  const held = JSON.parse(invitation);
161
- for (const req of pilotRequests(name, held, at)) {
219
+ const census = (await request(dir, {}));
220
+ const web = census.answer?.beings !== undefined && Object.hasOwn(census.answer.beings, WEB_OPENING.key);
221
+ for (const req of pilotRequests(name, held, at, web)) {
162
222
  const answer = await request(dir, req);
163
223
  if (!failed(answer))
164
224
  continue;
@@ -174,9 +234,13 @@ export const main = async (argv, env, print) => {
174
234
  throw new Error('--via takes no --ward');
175
235
  return out(farAnswer(await request(dir, via(flags.via, requestOf(command, words, flags)))));
176
236
  };
177
- // The root requests that hold a far ward: its route, the pilot, the take.
178
- export const pilotRequests = (name, invitation, at) => [
179
- { method: 'route', args: { ward: invitation.ward ?? null, at } },
237
+ // The root requests that hold a far ward: the web carrier opened, where the
238
+ // far ward is reached on the web and this harbor has none; its route where
239
+ // one is named, else the take learns it from the invitation's `at`; the
240
+ // pilot; the take.
241
+ export const pilotRequests = (name, invitation, at, web = false) => [
242
+ ...(!web && readAt([...(at === undefined ? [] : [at]), ...readAt(invitation.at)]).some((a) => webAddress(a) !== null) ? [{ method: 'open', args: { key: WEB_OPENING.key, class: WEB_OPENING.kind } }] : []),
243
+ ...(at === undefined ? [] : [{ method: 'route', args: { ward: invitation.ward ?? null, at: [at] } }]),
180
244
  { method: 'boot', args: { key: name, class: 'org.nervur.pilot' } },
181
245
  { method: 'ask', args: { being: name, method: 'hold', args: { invitation } } },
182
246
  ];
@@ -1,20 +1,15 @@
1
1
  import type { JsonObject } from '../being/index.ts';
2
- import { Loader, type Loaded } from '../contract/index.ts';
3
- import { Harbor, type RootRequest } from '../harbor/index.ts';
4
- import { TcpCarrier } from '../tcp/index.ts';
5
- export declare const socketOf: (dir: string) => string;
6
- export declare class ImportLoader extends Loader {
7
- load(from: string): Promise<Loaded | null>;
8
- }
9
- export type Opened = {
10
- harbor: Harbor;
11
- carrier: TcpCarrier;
12
- };
13
- export declare const open: (dir: string) => Promise<Opened>;
2
+ import { Harbor, type Carried, type RootRequest } from '../harbor/index.ts';
3
+ export declare const open: (dir: string) => Promise<Harbor>;
4
+ export declare const isServed: (dir: string) => Promise<boolean>;
14
5
  export declare const request: (dir: string, req: RootRequest) => Promise<JsonObject>;
6
+ export declare const carry: (dir: string, seed: boolean) => Promise<Carried>;
7
+ export declare const pack: (dir: string, file: string) => Promise<JsonObject>;
8
+ export declare const land: (dir: string, value: unknown) => Promise<string[]>;
15
9
  export type Served = {
16
10
  pk: string;
17
11
  at: string;
12
+ listening: string[];
18
13
  root: string;
19
14
  close(): Promise<void>;
20
15
  };
@@ -1,141 +1,91 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
1
  // SPDX-License-Identifier: Apache-2.0
10
- // A harbor on this machine, as the `nervur` command stands it: its seed and
11
- // sealed memory in a folder, its code imported from the files its
12
- // catalogue names, Quo over TCP as its carrier, and the root line on a
13
- // local socket beside them.
14
- //
15
- // <dir>/seed the harbor's seed
16
- // <dir>/<place>/ its sealed memory
17
- // <dir>/root.sock the root line while the harbor is served, open to
18
- // this user alone, or in the temporary folder for a
19
- // folder too deep for a socket path
2
+ // A harbor on this machine, as the `nervur` command stands it: a harbor of
3
+ // the Node ground in a folder, served while `serve` runs, and asked over
4
+ // its root line's socket.
20
5
  //
21
6
  // A root request goes to a served harbor over its socket. With none served,
22
7
  // the command unpacks the harbor from its folder, asks, and closes it, so
23
8
  // one process at a time writes the folder.
24
- import { createHash } from 'node:crypto';
25
- import { chmod, rm } from 'node:fs/promises';
26
- import { connect, createServer } from 'node:net';
27
- import { tmpdir } from 'node:os';
28
- import { join, resolve } from 'node:path';
29
- import { pathToFileURL } from 'node:url';
30
- import { Loader } from '../contract/index.js';
9
+ import { existsSync } from 'node:fs';
10
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
11
+ import { join } from 'node:path';
12
+ import { hex } from '../crypto/index.js';
31
13
  import { FolderCustody, FolderMemory } from '../folder/index.js';
32
- import { Harbor, rootLine } from '../harbor/index.js';
33
- import { CryptoEntropy, PointerTerrain } from '../pointer/index.js';
34
- import { TcpCarrier, TcpListener } from '../tcp/index.js';
35
- // A socket path has a hundred bytes or so on every system, so a folder too
36
- // deep for one puts its socket in this user's temporary folder, under a
37
- // name drawn from the folder's own path.
38
- const SOCKET_PATH = 100;
39
- export const socketOf = (dir) => {
40
- const beside = join(resolve(dir), 'root.sock');
41
- if (Buffer.byteLength(beside) <= SOCKET_PATH)
42
- return beside;
43
- return join(tmpdir(), `nervur-${createHash('sha256').update(resolve(dir)).digest('hex').slice(0, 32)}.sock`);
44
- };
45
- // Code from a file: a module that exports `module`, `version` and
46
- // `classes`, found again by its absolute path. A file that cannot be
47
- // imported, or exports no module, is none.
48
- export class ImportLoader extends Loader {
49
- async load(from) {
50
- const path = resolve(from);
51
- let exported;
52
- try {
53
- exported = (await import(__rewriteRelativeImportExtension(pathToFileURL(path).href)));
54
- }
55
- catch {
56
- return null;
57
- }
58
- const { module, version, classes } = exported;
59
- if (typeof module !== 'string' || typeof version !== 'string' || !Array.isArray(classes))
60
- return null;
61
- return { module, version, classes, from: path };
62
- }
63
- }
64
- export const open = async (dir) => {
65
- const entropy = new CryptoEntropy();
66
- const carrier = new TcpCarrier();
67
- const harbor = await Harbor.open(new PointerTerrain({ entropy, carrier, loader: new ImportLoader(), custody: new FolderCustody(dir, entropy), memory: new FolderMemory(dir) }));
68
- return { harbor, carrier };
69
- };
70
- // One root request over a served harbor's socket, or undefined where none
71
- // is served.
72
- const overSocket = (dir, request) => new Promise((done, fail) => {
73
- const socket = connect(socketOf(dir));
74
- let text = '';
75
- socket.once('connect', () => socket.write(`${JSON.stringify(request)}\n`));
76
- socket.on('data', (chunk) => {
77
- text += chunk.toString('utf8');
78
- const end = text.indexOf('\n');
79
- if (end < 0)
80
- return;
81
- socket.end();
82
- done(JSON.parse(text.slice(0, end)));
83
- });
84
- socket.once('error', (e) => (e.code === 'ENOENT' || e.code === 'ECONNREFUSED' ? done(undefined) : fail(e)));
85
- });
14
+ import { carryPlaces, Harbor, landPlaces, readCarried, rootLine } from '../harbor/index.js';
15
+ import { askServed, MODULES, NodeTerrain, socketOf } from '../node/index.js';
16
+ import { CryptoEntropy } from '../pointer/index.js';
17
+ import { TCP_OPENING } from '../tcp/index.js';
18
+ // A harbor of a folder, opened for one ask: it listens nowhere and serves
19
+ // nothing.
20
+ export const open = (dir) => Harbor.open(new NodeTerrain({ dir, wakes: false, serves: false }));
21
+ // Whether a harbor of this folder is served now.
22
+ export const isServed = async (dir) => (await askServed(dir, {})) !== undefined;
86
23
  export const request = async (dir, req) => {
87
- const served = await overSocket(dir, req);
24
+ const served = await askServed(dir, req);
88
25
  if (served !== undefined)
89
26
  return served;
90
- const { harbor, carrier } = await open(dir);
27
+ const harbor = await open(dir);
91
28
  try {
92
29
  return await rootLine(harbor, req);
93
30
  }
94
31
  finally {
95
- await carrier.close();
32
+ await harbor.close();
96
33
  }
97
34
  };
98
- // The harbor served: Quo over TCP on host and port, the root line on the
99
- // socket. A socket file nobody answers on is a stale one, and is replaced.
100
- export const serve = async (dir, host, port) => {
101
- if ((await overSocket(dir, {})) !== undefined)
102
- throw new Error(`a harbor is already served from ${dir}`);
103
- const { harbor, carrier } = await open(dir);
104
- const listener = await TcpListener.listen(harbor, host, port);
105
- const root = socketOf(dir);
106
- await rm(root, { force: true });
107
- const server = createServer((socket) => {
108
- let text = '';
109
- socket.on('data', (chunk) => {
110
- text += chunk.toString('utf8');
111
- for (let end = text.indexOf('\n'); end >= 0; end = text.indexOf('\n')) {
112
- const line = text.slice(0, end);
113
- text = text.slice(end + 1);
114
- let value;
115
- try {
116
- value = JSON.parse(line);
117
- }
118
- catch {
119
- value = undefined;
120
- }
121
- void rootLine(harbor, value)
122
- .catch(() => ({ error: 'the harbor threw' }))
123
- .then((answer) => socket.write(`${JSON.stringify(answer)}\n`));
124
- }
125
- });
126
- socket.on('error', () => socket.destroy());
127
- });
128
- await new Promise((done) => server.listen(root, done));
129
- await chmod(root, 0o600);
35
+ // The harbor of a folder, read as bytes alone, its seed only where the
36
+ // one who asks says so, since custody holds it and a package without it
37
+ // opens nowhere. No harbor opens, so nothing is asked and nothing is
38
+ // written.
39
+ export const carry = async (dir, seed) => {
40
+ const places = await carryPlaces(new FolderMemory(dir));
41
+ const modules = await readFile(join(dir, MODULES), 'utf8').then((text) => JSON.parse(text), () => undefined);
130
42
  return {
131
- pk: harbor.box.pk,
132
- at: listener.at,
133
- root,
134
- close: async () => {
135
- await new Promise((done) => server.close(done));
136
- await rm(root, { force: true });
137
- await listener.close();
138
- await carrier.close();
139
- },
43
+ ...(seed ? { seed: hex(await new FolderCustody(dir, new CryptoEntropy()).seed()) } : {}),
44
+ places,
45
+ ...(modules ? { modules } : {}),
140
46
  };
141
47
  };
48
+ // What `nervur edge pack` hands a deploy: the seed, for the worker's
49
+ // `NERVUR_SEED`, and the sealed package without it, for the bundle.
50
+ export const pack = async (dir, file) => {
51
+ if (!existsSync(join(dir, 'seed')))
52
+ throw new Error(`no harbor stands at ${dir}`);
53
+ const { seed, places } = await carry(dir, true);
54
+ await writeFile(file, `${JSON.stringify({ places })}\n`, { mode: 0o600 });
55
+ return { seed: seed, package: file, places: Object.keys(places).length };
56
+ };
57
+ // A carried harbor into an empty folder. A folder holding a seed or a
58
+ // place is left as it stands, and a package with no seed needs one there
59
+ // already, which a terrain that seals its custody writes its own way.
60
+ export const land = async (dir, value) => {
61
+ const held = readCarried(value);
62
+ if (held === null)
63
+ throw new Error('that is no carried harbor');
64
+ const memory = new FolderMemory(dir);
65
+ if ((await memory.places()).length > 0 || (held.seed !== undefined && existsSync(join(dir, 'seed'))))
66
+ throw new Error(`a harbor stands at ${dir}`);
67
+ await mkdir(dir, { recursive: true, mode: 0o700 });
68
+ if (held.seed !== undefined)
69
+ await writeFile(join(dir, 'seed'), `${held.seed}\n`, { mode: 0o600, flag: 'wx' });
70
+ else if (!existsSync(join(dir, 'seed')))
71
+ throw new Error('the package carries no seed, and none is held here');
72
+ if (held.modules && !existsSync(join(dir, MODULES)))
73
+ await writeFile(join(dir, MODULES), `${JSON.stringify(held.modules, null, 2)}\n`, { mode: 0o600 });
74
+ return landPlaces(memory, held);
75
+ };
76
+ // The harbor served: a harbor of the Node ground, with Quo over TCP on
77
+ // host and port.
78
+ export const serve = async (dir, host, port) => {
79
+ const harbor = await Harbor.open(new NodeTerrain({ dir }));
80
+ if (!harbor.box.being(TCP_OPENING.key))
81
+ await harbor.root('open', { key: TCP_OPENING.key, class: TCP_OPENING.kind });
82
+ const tcp = harbor.box.being(TCP_OPENING.key);
83
+ if (host !== undefined || port !== undefined || tcp.listening().length === 0) {
84
+ const heard = (await harbor.root('ask', { being: TCP_OPENING.key, method: 'listen', args: { ...(host === undefined ? {} : { host }), ...(port === undefined ? {} : { port }) } }));
85
+ if (typeof heard.answer?.at !== 'string') {
86
+ await harbor.close();
87
+ throw new Error(`the harbor does not listen: ${JSON.stringify(heard)}`);
88
+ }
89
+ }
90
+ return { pk: harbor.box.pk, at: tcp.listening()[0], listening: harbor.carriers.flatMap((c) => c.listening()), root: socketOf(dir), close: () => harbor.close() };
91
+ };
@@ -18,6 +18,8 @@ export interface Rows {
18
18
  export declare abstract class Memory {
19
19
  abstract read(place: string): Promise<Map<string, Uint8Array>>;
20
20
  abstract write(place: string, entries: ReadonlyMap<string, Uint8Array | null>): Promise<void>;
21
+ abstract places(): Promise<string[]>;
22
+ abstract forget(place: string): Promise<void>;
21
23
  }
22
24
  export declare abstract class Custody {
23
25
  abstract seed(): Promise<Uint8Array>;
@@ -35,5 +37,4 @@ export declare abstract class Loader {
35
37
  }
36
38
  export declare abstract class Carrier {
37
39
  abstract carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
38
- route(_pk: string, _at: string | null): boolean;
39
40
  }
@@ -22,12 +22,8 @@ export class Custody {
22
22
  // one thing beside the seed a terrain hands in: a map, an import, a bundle.
23
23
  export class Loader {
24
24
  }
25
- // Bytes to a ward pk in; bytes, or null for nothing, out.
25
+ // Bytes to a ward pk in; bytes, or null for nothing, out. A harbor hands a
26
+ // terrain's carrier what it holds no route for; what it routes goes to its
27
+ // carrier faculties.
26
28
  export class Carrier {
27
- // Where a ward pk is reached, or null to forget it: true when this
28
- // carrier dials by address and took it. The addresses live in the dock's
29
- // cells, and the harbor hands them here as it unpacks.
30
- route(_pk, _at) {
31
- return false;
32
- }
33
29
  }
@@ -0,0 +1,71 @@
1
+ import { Custody, Memory, type Entropy, type Module } from '../contract/index.ts';
2
+ import { Harbor, type Carried, type GroundFaculty, type GroundProbe } from '../harbor/index.ts';
3
+ import { type WebListen } from '../line/index.ts';
4
+ import { PointerTerrain } from '../pointer/index.ts';
5
+ export type { Carried } from '../harbor/index.ts';
6
+ export declare const EDGE = "edge";
7
+ export interface EdgeStorage {
8
+ get(key: string): Promise<unknown>;
9
+ put(entries: Record<string, unknown>): Promise<void>;
10
+ delete(keys: string[]): Promise<number>;
11
+ list(options: {
12
+ prefix: string;
13
+ }): Promise<Map<string, unknown>>;
14
+ transaction<T>(work: (txn: EdgeStorage) => Promise<T>): Promise<T>;
15
+ }
16
+ export interface EdgeState {
17
+ readonly storage: EdgeStorage;
18
+ blockConcurrencyWhile<T>(work: () => Promise<T>): Promise<T>;
19
+ }
20
+ export type EdgeEnv = {
21
+ readonly NERVUR_SEED?: string;
22
+ readonly NERVUR_SECRET?: string;
23
+ };
24
+ export type EdgeGiven = {
25
+ readonly state: EdgeState;
26
+ readonly env: EdgeEnv;
27
+ readonly package?: Carried;
28
+ };
29
+ export declare class EdgeMemory extends Memory {
30
+ #private;
31
+ constructor(storage: EdgeStorage, root?: string);
32
+ read(place: string): Promise<Map<string, Uint8Array>>;
33
+ write(place: string, entries: ReadonlyMap<string, Uint8Array | null>): Promise<void>;
34
+ places(): Promise<string[]>;
35
+ forget(place: string): Promise<void>;
36
+ }
37
+ export type EdgeKeys = {
38
+ readonly seed?: string | undefined;
39
+ readonly secret?: string | undefined;
40
+ };
41
+ export declare class EdgeCustody extends Custody {
42
+ #private;
43
+ constructor(storage: EdgeStorage, keys: EdgeKeys, entropy?: Entropy, root?: string);
44
+ seed(): Promise<Uint8Array>;
45
+ }
46
+ export declare const edgeTcp: GroundFaculty;
47
+ export type EdgeParts = {
48
+ readonly given: EdgeGiven;
49
+ readonly modules?: readonly Module[];
50
+ };
51
+ export declare class EdgeTerrain extends PointerTerrain {
52
+ #private;
53
+ readonly given: EdgeGiven;
54
+ listening: WebListen | undefined;
55
+ constructor(parts: EdgeParts);
56
+ claim(): Promise<void>;
57
+ stand(harbor: Harbor): Promise<void>;
58
+ answer(request: Request): Promise<Response>;
59
+ }
60
+ export declare const edgeGround: GroundProbe;
61
+ export type HarborObjectParts = {
62
+ readonly modules: readonly Module[];
63
+ readonly package?: Carried | ((env: EdgeEnv) => Carried | undefined);
64
+ readonly path?: string;
65
+ readonly stood?: (harbor: Harbor) => Promise<void>;
66
+ };
67
+ export type HarborObjectClass = new (state: EdgeState, env: EdgeEnv) => {
68
+ readonly harbor: Promise<Harbor>;
69
+ fetch(request: Request): Promise<Response>;
70
+ };
71
+ export declare const harborObject: ({ modules, package: carried, path, stood }: HarborObjectParts) => HarborObjectClass;