nervur 0.21.0 → 0.21.1

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 CHANGED
@@ -218,7 +218,11 @@ stands a ward under a seed you name, or a drawn one.
218
218
  and every class between it and `Faculty`.
219
219
  - **Entropy, Clock, Custody, Memory, Carrier, Loader.** The contracts a
220
220
  terrain fulfils. Each has one suite, and every body that fulfils it
221
- passes it.
221
+ passes it. `nervur/proof` hands you those suites and the unpacking
222
+ law's as scenes, a title and a function each, so a body of your own,
223
+ a store in a cloud or a secret of your platform's, runs them under
224
+ your own runner: `for (const [title, scene] of memoryScenes('Mine',
225
+ make, expect)) test(title, scene)`. `@nervur-org/s3` is one such body.
222
226
  - **Harbor, Terrain, Dock, Catalogue, Registry, WardBeing.** The onion.
223
227
  - **World, PointerTerrain** and the pointer bodies, `HeldLoader` among
224
228
  them. A whole world with this package alone.
@@ -240,6 +244,8 @@ stands a ward under a seed you name, or a drawn one.
240
244
  - **WebDialer, webFaculty**: Quo over the web, a post to `https://` or a
241
245
  held line to `wss://`, in any engine with `fetch` and WebSocket.
242
246
  `webServe` and `webGround`, from `nervur/http`, listen for it on Node.
247
+ `answerPost(harbor, listen, request)` answers one post and nothing
248
+ more, for a function that opens a harbor per request and closes it.
243
249
 
244
250
  - **The relay**, `org.nervur.relay` and `org.nervur.relay-client`, in
245
251
  every harbor: a harbor nobody can dial is reached through one that
@@ -18,7 +18,8 @@ export class Memory {
18
18
  export class Custody {
19
19
  }
20
20
  // Code, by the name a harbor keeps for it, or null where this loader has
21
- // none. Code never crosses a door and never lives in a cell, so it is the
21
+ // none. Code it has that does not load throws, saying why. Code never
22
+ // crosses a door and never lives in a cell, so it is the
22
23
  // one thing beside the seed a terrain hands in: a map, an import, a bundle.
23
24
  export class Loader {
24
25
  }
@@ -42,8 +42,14 @@ export class Catalogue extends Faculty {
42
42
  // it. The rest stay out, each with its reason.
43
43
  async wake() {
44
44
  for (const [module, row] of Object.entries(this.#rows)) {
45
- const loaded = await this.#harbor.load(row.from).catch(() => null);
46
- const refused = loaded === null ? `not found at ${row.from}` : loaded.module !== module ? `${row.from} is module ${loaded.module}` : this.#harbor.check([...this.#running.values(), loaded]);
45
+ const loaded = await this.#harbor.load(row.from).catch((e) => e);
46
+ const refused = loaded instanceof Error
47
+ ? loaded.message
48
+ : loaded === null
49
+ ? `not found at ${row.from}`
50
+ : loaded.module !== module
51
+ ? `${row.from} is module ${loaded.module}`
52
+ : this.#harbor.check([...this.#running.values(), loaded]);
47
53
  if (refused === null)
48
54
  this.#running.set(module, loaded);
49
55
  else
@@ -54,7 +60,9 @@ export class Catalogue extends Faculty {
54
60
  async add(args) {
55
61
  if (!text(args.from))
56
62
  return { error: 'from is text' };
57
- const loaded = await this.#harbor.load(args.from);
63
+ const loaded = await this.#harbor.load(args.from).catch((e) => e);
64
+ if (loaded instanceof Error)
65
+ return { error: loaded.message };
58
66
  if (loaded === null)
59
67
  return { error: 'no such module' };
60
68
  const running = new Map(this.#running);
@@ -126,8 +126,12 @@ export class Dock extends WardBeing {
126
126
  }
127
127
  async host(args) {
128
128
  const name = args.ward;
129
- if (!text(name) || name === BOX || this.hosted.some((h) => h.ward === name))
130
- return { error: 'no such ward to host' };
129
+ if (!text(name))
130
+ return { error: 'ward is text' };
131
+ if (name === BOX)
132
+ return { error: 'box is the box ward' };
133
+ if (this.hosted.some((h) => h.ward === name))
134
+ return { error: `${name} is hosted already` };
131
135
  if (args.seed !== undefined && !text(args.seed))
132
136
  return { error: 'seed is text' };
133
137
  const invitation = await this.stance.occupants.invite(name, { hosted: true });
@@ -53,8 +53,10 @@ export const isFile = (from) => /^(\.{1,2}[\\/]|[\\/]|[A-Za-z]:[\\/])/.test(from
53
53
  // Code from a file or an installed package: a module that exports
54
54
  // `module`, `version` and `classes`, found again by where it was found, a
55
55
  // file by its absolute path and a package by its name, resolved from each
56
- // folder of `bases` in order. What cannot be imported, or exports no
57
- // module, is none.
56
+ // folder of `bases` in order. What is not there, or exports no module, is
57
+ // none. Code that is there and does not import throws Node's reason, so
58
+ // whoever added it learns why; an ES module read as CommonJS says which
59
+ // field its package.json lacks.
58
60
  export class ImportLoader extends Loader {
59
61
  #bases;
60
62
  constructor(bases = [process.cwd()]) {
@@ -69,8 +71,14 @@ export class ImportLoader extends Loader {
69
71
  try {
70
72
  exported = (await import(__rewriteRelativeImportExtension(pathToFileURL(found).href)));
71
73
  }
72
- catch {
73
- return null;
74
+ catch (e) {
75
+ const said = e.message;
76
+ // The name itself is nowhere, as against code there that needs
77
+ // something that is not.
78
+ if (e.code === 'ERR_MODULE_NOT_FOUND' && said.includes(`'${found}'`))
79
+ return null;
80
+ const commonjs = e instanceof SyntaxError && /\b(export|import)\b/.test(said);
81
+ throw new Error(`${found} did not load: ${said}${commonjs ? '; it is an ES module read as CommonJS, so set "type": "module" in its package.json' : ''}`, { cause: e });
74
82
  }
75
83
  const { module, version, classes } = exported;
76
84
  if (typeof module !== 'string' || typeof version !== 'string' || !Array.isArray(classes))
package/dist/proof/law.js CHANGED
@@ -210,7 +210,7 @@ export const lawScenes = (t, expect) => [
210
210
  expect.same(again.names, ['good']);
211
211
  expect.same((await census(again)).failed, { wards: { bad: 'an entry the package did not seal' }, routes: {} });
212
212
  expect.same(await rootLine(again, { ward: 'bad' }), { error: 'the ward did not stand', reason: 'an entry the package did not seal' });
213
- expect.same(await again.root('host', { ward: 'bad' }), { error: 'no such ward to host' }, 'its name and seed stay kept');
213
+ expect.same(await again.root('host', { ward: 'bad' }), { error: 'bad is hosted already' }, 'its name and seed stay kept');
214
214
  await spoil(places[1]);
215
215
  let refused = '';
216
216
  await world.restart('h', t.again(bodies)).catch((e) => (refused = e.message));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nervur",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "description": "Nervur's kit of Quo: an object asks another object and gets an answer, without knowing where it is.",
5
5
  "keywords": [
6
6
  "quo",
@@ -62,7 +62,8 @@ export type Module = { readonly module: string; readonly version: string; readon
62
62
  export type Loaded = Module & { readonly from: string };
63
63
 
64
64
  // Code, by the name a harbor keeps for it, or null where this loader has
65
- // none. Code never crosses a door and never lives in a cell, so it is the
65
+ // none. Code it has that does not load throws, saying why. Code never
66
+ // crosses a door and never lives in a cell, so it is the
66
67
  // one thing beside the seed a terrain hands in: a map, an import, a bundle.
67
68
  export abstract class Loader {
68
69
  abstract load(from: string): Promise<Loaded | null>;
@@ -67,10 +67,16 @@ export class Catalogue extends Faculty {
67
67
  // it. The rest stay out, each with its reason.
68
68
  async wake(): Promise<void> {
69
69
  for (const [module, row] of Object.entries(this.#rows)) {
70
- const loaded = await this.#harbor.load(row.from).catch(() => null);
70
+ const loaded = await this.#harbor.load(row.from).catch((e: Error) => e);
71
71
  const refused =
72
- loaded === null ? `not found at ${row.from}` : loaded.module !== module ? `${row.from} is module ${loaded.module}` : this.#harbor.check([...this.#running.values(), loaded]);
73
- if (refused === null) this.#running.set(module, loaded!);
72
+ loaded instanceof Error
73
+ ? loaded.message
74
+ : loaded === null
75
+ ? `not found at ${row.from}`
76
+ : loaded.module !== module
77
+ ? `${row.from} is module ${loaded.module}`
78
+ : this.#harbor.check([...this.#running.values(), loaded]);
79
+ if (refused === null) this.#running.set(module, loaded as Loaded);
74
80
  else this.#failed.set(module, refused);
75
81
  }
76
82
  this.#harbor.run([...this.#running.values()]);
@@ -78,7 +84,8 @@ export class Catalogue extends Faculty {
78
84
 
79
85
  async add(args: JsonObject): Promise<JsonObject> {
80
86
  if (!text(args.from)) return { error: 'from is text' };
81
- const loaded = await this.#harbor.load(args.from);
87
+ const loaded = await this.#harbor.load(args.from).catch((e: Error) => e);
88
+ if (loaded instanceof Error) return { error: loaded.message };
82
89
  if (loaded === null) return { error: 'no such module' };
83
90
  const running = new Map(this.#running);
84
91
  running.set(loaded.module, loaded);
@@ -158,7 +158,9 @@ export class Dock extends WardBeing implements Lender {
158
158
 
159
159
  async host(args: JsonObject): Promise<JsonObject> {
160
160
  const name = args.ward;
161
- if (!text(name) || name === BOX || this.hosted.some((h) => h.ward === name)) return { error: 'no such ward to host' };
161
+ if (!text(name)) return { error: 'ward is text' };
162
+ if (name === BOX) return { error: 'box is the box ward' };
163
+ if (this.hosted.some((h) => h.ward === name)) return { error: `${name} is hosted already` };
162
164
  if (args.seed !== undefined && !text(args.seed)) return { error: 'seed is text' };
163
165
  const invitation = await this.stance.occupants.invite(name, { hosted: true });
164
166
  if (invitation === null) return { error: 'not hosted' };
package/src/node/index.ts CHANGED
@@ -50,8 +50,10 @@ export const isFile = (from: string): boolean => /^(\.{1,2}[\\/]|[\\/]|[A-Za-z]:
50
50
  // Code from a file or an installed package: a module that exports
51
51
  // `module`, `version` and `classes`, found again by where it was found, a
52
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.
53
+ // folder of `bases` in order. What is not there, or exports no module, is
54
+ // none. Code that is there and does not import throws Node's reason, so
55
+ // whoever added it learns why; an ES module read as CommonJS says which
56
+ // field its package.json lacks.
55
57
  export class ImportLoader extends Loader {
56
58
  readonly #bases: readonly string[];
57
59
  constructor(bases: readonly string[] = [process.cwd()]) {
@@ -65,8 +67,13 @@ export class ImportLoader extends Loader {
65
67
  let exported: { module?: unknown; version?: unknown; classes?: unknown };
66
68
  try {
67
69
  exported = (await import(pathToFileURL(found).href)) as typeof exported;
68
- } catch {
69
- return null;
70
+ } catch (e) {
71
+ const said = (e as Error).message;
72
+ // The name itself is nowhere, as against code there that needs
73
+ // something that is not.
74
+ if ((e as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' && said.includes(`'${found}'`)) return null;
75
+ const commonjs = e instanceof SyntaxError && /\b(export|import)\b/.test(said);
76
+ throw new Error(`${found} did not load: ${said}${commonjs ? '; it is an ES module read as CommonJS, so set "type": "module" in its package.json' : ''}`, { cause: e });
70
77
  }
71
78
  const { module, version, classes } = exported;
72
79
  if (typeof module !== 'string' || typeof version !== 'string' || !Array.isArray(classes)) return null;
package/src/proof/law.ts CHANGED
@@ -220,7 +220,7 @@ export const lawScenes = (t: Terrain, expect: Expect): [string, () => Promise<vo
220
220
  expect.same(again.names, ['good']);
221
221
  expect.same(((await census(again)) as { failed: unknown }).failed, { wards: { bad: 'an entry the package did not seal' }, routes: {} });
222
222
  expect.same(await rootLine(again, { ward: 'bad' }), { error: 'the ward did not stand', reason: 'an entry the package did not seal' });
223
- expect.same(await again.root('host', { ward: 'bad' }), { error: 'no such ward to host' }, 'its name and seed stay kept');
223
+ expect.same(await again.root('host', { ward: 'bad' }), { error: 'bad is hosted already' }, 'its name and seed stay kept');
224
224
  await spoil(places[1]!);
225
225
  let refused = '';
226
226
  await world.restart('h', t.again(bodies)).catch((e: Error) => (refused = e.message));