nervur 0.21.3 → 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.
@@ -29,6 +29,7 @@ export declare class BrowserTerrain extends PointerTerrain {
29
29
  #private;
30
30
  readonly where: string;
31
31
  constructor(parts?: BrowserParts);
32
+ get foreground(): boolean;
32
33
  claim(): Promise<void>;
33
34
  release(): Promise<void>;
34
35
  }
@@ -156,6 +156,14 @@ export class BrowserTerrain extends PointerTerrain {
156
156
  super({ entropy, loader: new HeldLoader(parts.modules ?? []), custody: new IdbCustody(where, entropy), memory: new IdbMemory(where), faculties: [webFaculty(), webPush], opens: [WEB_OPENING] });
157
157
  this.where = where;
158
158
  }
159
+ // A shared or a service worker outlives any one tab, and a service
160
+ // worker is woken for a push with none open. Anywhere else, a page or a
161
+ // worker a tab made, the harbor lives with that tab.
162
+ get foreground() {
163
+ const scope = globalThis;
164
+ const is = (name) => typeof scope[name] === 'function' && globalThis instanceof scope[name];
165
+ return !is('SharedWorkerGlobalScope') && !is('ServiceWorkerGlobalScope');
166
+ }
159
167
  async claim() {
160
168
  const locks = globalThis.navigator?.locks;
161
169
  if (!locks)
@@ -34,6 +34,7 @@ export type Loaded = Module & {
34
34
  };
35
35
  export declare abstract class Loader {
36
36
  abstract load(from: string): Promise<Loaded | null>;
37
+ missing(_from: string): string | null;
37
38
  }
38
39
  export declare abstract class Carrier {
39
40
  abstract carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
@@ -22,6 +22,11 @@ export class Custody {
22
22
  // crosses a door and never lives in a cell, so it is the
23
23
  // one thing beside the seed a terrain hands in: a map, an import, a bundle.
24
24
  export class Loader {
25
+ // Why a name it found nothing under holds nothing, where it can say more
26
+ // than that: a bundle names that code comes from it alone.
27
+ missing(_from) {
28
+ return null;
29
+ }
25
30
  }
26
31
  // Bytes to a ward pk in; bytes, or null for nothing, out. A harbor hands a
27
32
  // terrain's carrier what it holds no route for; what it routes goes to its
@@ -8,6 +8,7 @@ export type ModuleRow = {
8
8
  };
9
9
  export interface Cataloguing {
10
10
  load(from: string): Promise<Loaded | null>;
11
+ missing(from: string): string | null;
11
12
  check(modules: readonly Loaded[]): string | null;
12
13
  run(modules: readonly Loaded[]): string | null;
13
14
  stands(kinds: readonly string[]): boolean;
@@ -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()]);
@@ -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>;
@@ -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.notes.failed = this.#hosting.failures();
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() {
@@ -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;
@@ -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
  }
@@ -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>;
@@ -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.
@@ -18,6 +18,7 @@ export declare class HeldLoader extends Loader {
18
18
  #private;
19
19
  constructor(modules?: readonly Module[]);
20
20
  load(from: string): Promise<Loaded | null>;
21
+ missing(from: string): string;
21
22
  }
22
23
  export declare class VolatileMemory extends Memory {
23
24
  #private;
@@ -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: `not found at ${law.module}` } },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nervur",
3
- "version": "0.21.3",
3
+ "version": "0.21.4",
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",
@@ -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;
@@ -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
@@ -29,6 +29,8 @@ export type ModuleRow = { version: string; from: string; kinds: string[] };
29
29
  // What the catalogue asks of her harbor.
30
30
  export interface Cataloguing {
31
31
  load(from: string): Promise<Loaded | null>;
32
+ // Why nothing loads from a name, where the loader says more.
33
+ missing(from: string): string | null;
32
34
  // Why these modules cannot run together, or null.
33
35
  check(modules: readonly Loaded[]): string | null;
34
36
  // The modules run from now on, or why they cannot be.
@@ -72,7 +74,7 @@ export class Catalogue extends Faculty {
72
74
  loaded instanceof Error
73
75
  ? loaded.message
74
76
  : loaded === null
75
- ? `not found at ${row.from}`
77
+ ? (this.#harbor.missing(row.from) ?? `not found at ${row.from}`)
76
78
  : loaded.module !== module
77
79
  ? `${row.from} is module ${loaded.module}`
78
80
  : this.#harbor.check([...this.#running.values(), loaded]);
@@ -86,7 +88,7 @@ export class Catalogue extends Faculty {
86
88
  if (!text(args.from)) return { error: 'from is text' };
87
89
  const loaded = await this.#harbor.load(args.from).catch((e: Error) => e);
88
90
  if (loaded instanceof Error) return { error: loaded.message };
89
- if (loaded === null) return { error: 'no such module' };
91
+ if (loaded === null) return { error: this.#harbor.missing(args.from) ?? 'no such module' };
90
92
  const running = new Map(this.#running);
91
93
  running.set(loaded.module, loaded);
92
94
  const refused = this.#harbor.run([...running.values()]);
@@ -46,6 +46,8 @@ export interface Hosting {
46
46
  keep(): Promise<boolean>;
47
47
  // What did not stand when the harbor opened.
48
48
  failures(): Failures;
49
+ // Whether the harbor lives only as long as a screen.
50
+ readonly foreground: boolean;
49
51
  // Every place of the memory the dock does not name, gone.
50
52
  sweep(): Promise<string[]>;
51
53
  // Whether a kind is a carrier this harbor's ground ships.
@@ -92,10 +94,14 @@ export class Dock extends WardBeing implements Lender {
92
94
  this.#hosting = hosting;
93
95
  }
94
96
 
95
- // The census adds what did not stand, for whoever pilots.
97
+ // The census adds what did not stand, for whoever pilots, and whether
98
+ // the harbor lives only as long as a screen.
96
99
  override describe(asker: Asker): Blueprint {
97
100
  const blueprint = super.describe(asker);
98
- if (pilots(asker, this.notes(asker))) (blueprint.notes as JsonObject).failed = this.#hosting.failures();
101
+ if (!pilots(asker, this.notes(asker))) return blueprint;
102
+ const notes = blueprint.notes as JsonObject;
103
+ notes.failed = this.#hosting.failures();
104
+ if (this.#hosting.foreground) notes.foreground = true;
99
105
  return blueprint;
100
106
  }
101
107
 
@@ -175,6 +175,10 @@ export class Harbor extends Carrier implements Hosting, Cataloguing, Resolving {
175
175
  });
176
176
  }
177
177
 
178
+ get foreground(): boolean {
179
+ return this.terrain.foreground;
180
+ }
181
+
178
182
  // Hosting: what did not stand when the harbor opened, each alone.
179
183
  failures(): Failures {
180
184
  return { wards: { ...this.#failed.wards }, routes: { ...this.#failed.routes } };
@@ -246,6 +250,10 @@ export class Harbor extends Carrier implements Hosting, Cataloguing, Resolving {
246
250
  return this.terrain.loader.load(from);
247
251
  }
248
252
 
253
+ missing(from: string): string | null {
254
+ return this.terrain.loader.missing(from);
255
+ }
256
+
249
257
  check(modules: readonly Loaded[]): string | null {
250
258
  return Registry.refusal(this.#kit, modules);
251
259
  }
@@ -25,6 +25,12 @@ export abstract class Terrain {
25
25
  // Whether its carriers wake and listen where their cells say. A harbor
26
26
  // opened for one ask and closed again dials and hears nothing.
27
27
  readonly wakes: boolean = true;
28
+ // Whether the harbor lives only as long as a screen: a tab's, or a worker
29
+ // one tab owns. It is reached only while that screen is open, and hears
30
+ // no push, so its census says so.
31
+ get foreground(): boolean {
32
+ return false;
33
+ }
28
34
 
29
35
  // Before the harbor opens: a terrain whose ground another run holds
30
36
  // refuses here. Once it stands: whatever the ground serves beside it.
@@ -32,7 +32,9 @@ export class HeldCustody extends Custody {
32
32
  }
33
33
  }
34
34
 
35
- // Modules handed to this object, found by the name each declares.
35
+ // Modules handed to this object, found by the name each declares: the
36
+ // bundle a ground with no disk to load from carries, an edge's or a
37
+ // browser's. A path, a package or any other name is no module of it.
36
38
  export class HeldLoader extends Loader {
37
39
  readonly #modules = new Map<string, Module>();
38
40
  constructor(modules: readonly Module[] = []) {
@@ -43,6 +45,10 @@ export class HeldLoader extends Loader {
43
45
  const m = this.#modules.get(from);
44
46
  return Promise.resolve(m ? { module: m.module, version: m.version, classes: m.classes, from } : null);
45
47
  }
48
+ override missing(from: string): string {
49
+ const carried = [...this.#modules.keys()];
50
+ return `${from} is no module this bundle carries${carried.length === 0 ? ', and it carries none' : `: it carries ${carried.join(', ')}`}`;
51
+ }
46
52
  }
47
53
 
48
54
  // Entries that live as long as this object, copied in and out, which is
package/src/proof/law.ts CHANGED
@@ -203,7 +203,7 @@ export const lawScenes = (t: Terrain, expect: Expect): [string, () => Promise<vo
203
203
  expect.same(lost.names, ['w'], 'the rest of the harbor stands');
204
204
  expect.same(((await census(lost)) as { beings: Record<string, { absent: boolean }> }).beings.echo?.absent, true);
205
205
  expect.same(await lost.root('ask', { being: 'catalogue', method: 'modules' }), {
206
- answer: { [law.module]: { version: '1', running: null, from: law.module, failed: `not found at ${law.module}` } },
206
+ answer: { [law.module]: { version: '1', running: null, from: law.module, failed: `${law.module} is no module this bundle carries, and it carries none` } },
207
207
  });
208
208
  const again = await world.restart('h', { ...t.again(bodies), loader: new HeldLoader([law]) });
209
209
  expect.same(((await census(again)) as { beings: Record<string, { absent: boolean }> }).beings.echo?.absent, false);