nervur 0.18.0 → 0.19.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 +7 -7
- package/dist/being/index.d.ts +1 -0
- package/dist/being/index.js +1 -0
- package/dist/being/lent.d.ts +32 -0
- package/dist/being/lent.js +63 -0
- package/dist/being/types.js +1 -1
- package/dist/conformance/beings.d.ts +2 -1
- package/dist/conformance/beings.js +7 -2
- package/dist/conformance/index.d.ts +0 -1
- package/dist/conformance/index.js +8 -5
- package/dist/harbor/box.d.ts +121 -0
- package/dist/harbor/box.js +121 -0
- package/dist/harbor/core.d.ts +5 -5
- package/dist/harbor/core.js +73 -72
- package/dist/harbor/index.d.ts +3 -1
- package/dist/harbor/index.js +3 -0
- package/dist/harbor/memory.d.ts +8 -4
- package/dist/harbor/memory.js +43 -11
- package/dist/harbor/reach.js +9 -1
- package/dist/harbor/store.js +1 -1
- package/dist/vector/cases.d.ts +4 -3
- package/dist/vector/cases.js +33 -5
- package/dist/vector/stand.js +2 -1
- package/dist/vector/world.d.ts +1 -0
- package/dist/vector/world.js +17 -6
- package/dist/ward/arithmetic.js +6 -2
- package/dist/ward/ground.d.ts +13 -7
- package/dist/ward/ground.js +29 -11
- package/dist/ward/heirs.js +4 -2
- package/dist/ward/index.d.ts +2 -1
- package/dist/ward/index.js +1 -0
- package/dist/ward/json.d.ts +2 -0
- package/dist/ward/json.js +163 -0
- package/dist/ward/partition.js +2 -2
- package/dist/ward/seal.js +14 -9
- package/dist/ward/stance.d.ts +0 -5
- package/dist/ward/ward.d.ts +1 -1
- package/dist/ward/ward.js +62 -27
- package/package.json +7 -5
- package/src/being/index.ts +1 -0
- package/src/being/lent.ts +66 -0
- package/src/being/types.ts +1 -1
- package/src/conformance/beings.ts +8 -3
- package/src/conformance/index.ts +18 -15
- package/src/harbor/box.ts +131 -0
- package/src/harbor/core.ts +23 -22
- package/src/harbor/index.ts +4 -1
- package/src/harbor/memory.ts +52 -12
- package/src/harbor/reach.ts +7 -1
- package/src/harbor/store.ts +1 -1
- package/src/vector/cases.ts +37 -9
- package/src/vector/stand.ts +2 -1
- package/src/vector/world.ts +18 -6
- package/src/ward/arithmetic.ts +6 -2
- package/src/ward/ground.ts +85 -103
- package/src/ward/heirs.ts +4 -2
- package/src/ward/index.ts +2 -1
- package/src/ward/json.ts +144 -0
- package/src/ward/partition.ts +2 -2
- package/src/ward/seal.ts +12 -8
- package/src/ward/stance.ts +1 -1
- package/src/ward/ward.ts +59 -27
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Nervur
|
|
2
2
|
|
|
3
3
|
Nervur is the first open source kit of Quo, the library itself: harbor, ward
|
|
4
|
-
and being, with
|
|
4
|
+
and being, with no dependencies.
|
|
5
5
|
|
|
6
6
|
Quo is a protocol that lets an object ask another object and get an answer,
|
|
7
7
|
without knowing whether that other object is in the same process, on the same
|
|
@@ -38,8 +38,9 @@ source it was emitted from, because Node strips types nowhere under
|
|
|
38
38
|
```
|
|
39
39
|
src/being/ the Being side: what a being author imports, if anything
|
|
40
40
|
src/ward/ the ward: the Ground contract, door, seal, arithmetic, heirs, stance, allowance
|
|
41
|
-
src/harbor/ MemoryHarbor, and the harbor core with its store, reach and dialer
|
|
41
|
+
src/harbor/ MemoryHarbor, the box's being, and the harbor core with its store, reach and dialer
|
|
42
42
|
src/conformance/ behaviours any ward must show, written against the truth
|
|
43
|
+
src/vector/ vector mode, and the world the door's cases are taken in
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
The suites live beside the source in the tree this is developed in and are
|
|
@@ -107,11 +108,10 @@ is no migration to write because there is nothing yet to migrate from. This
|
|
|
107
108
|
package carries the version of its own work and is bound to no other's, so a
|
|
108
109
|
number equal to `@nervur-org/dock`'s or `@nervur-org/ui`'s is a coincidence.
|
|
109
110
|
|
|
110
|
-
What ships is the emitted `dist/`, the source it came from, the
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
with no truth beside it is a pile of names.
|
|
111
|
+
What ships is the emitted `dist/`, the source it came from, this file, the
|
|
112
|
+
licence and the notice. No tests and no configs. The spec and the vectors
|
|
113
|
+
are not in the tarball: they are Quo's and not this package's to carry, and
|
|
114
|
+
they stand at <https://quo.systems>, which is where this file points.
|
|
115
115
|
|
|
116
116
|
## License
|
|
117
117
|
|
package/dist/being/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Being, type AskSpec } from './being.ts';
|
|
2
|
+
export { Lent, BOX, LENT_ASKS } from './lent.ts';
|
|
2
3
|
export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.ts';
|
|
3
4
|
export { digest, canonical } from './digest.ts';
|
|
4
5
|
export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs, isInvitation } from './types.ts';
|
package/dist/being/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// nervur — the Being side. What a being author imports, if anything.
|
|
3
3
|
export { Being } from './being.js';
|
|
4
|
+
export { Lent, BOX, LENT_ASKS } from './lent.js';
|
|
4
5
|
export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.js';
|
|
5
6
|
export { digest, canonical } from './digest.js';
|
|
6
7
|
export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs, isInvitation } from './types.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Being } from './being.ts';
|
|
2
|
+
import type { Asker, Blueprint, JsonObject, Reply } from './types.ts';
|
|
3
|
+
export declare const BOX = "box";
|
|
4
|
+
export declare const LENT_ASKS: {
|
|
5
|
+
offer: {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
input: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
retract: {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
input: {
|
|
16
|
+
type: string;
|
|
17
|
+
properties: {
|
|
18
|
+
heir: {
|
|
19
|
+
type: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
required: string[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export declare class Lent extends Being {
|
|
27
|
+
#private;
|
|
28
|
+
offer(): Promise<JsonObject>;
|
|
29
|
+
retract(args: JsonObject): JsonObject;
|
|
30
|
+
describe(asker: Asker): Blueprint;
|
|
31
|
+
answer(asker: Asker, method?: string, args?: JsonObject): Promise<Reply>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A lent being: a being of the box's own ward, standing for one thing the
|
|
3
|
+
// device can do. What makes her one is two asks the box's being alone may
|
|
4
|
+
// make, whom she knows as the occupant `box`. `offer`: she mints an
|
|
5
|
+
// invitation on herself and answers it, so that a ward the box vouches for
|
|
6
|
+
// may knock and take a standing at her. `retract`: an offer that was not
|
|
7
|
+
// taken, named by its heir, is removed, so a refused lend leaves nothing.
|
|
8
|
+
//
|
|
9
|
+
// The two are the base's and never a subclass's to declare: a class says
|
|
10
|
+
// her own asks and nothing of the seam she is lent through, so they are
|
|
11
|
+
// answered here before her asks are read, and shown to the box alone. No
|
|
12
|
+
// root mints for her, and nothing else about her is decided here.
|
|
13
|
+
//
|
|
14
|
+
// A convenience, not a contract: a being that answers the two the same way
|
|
15
|
+
// to `box` is lendable, whatever she extends.
|
|
16
|
+
import { Being } from './being.js';
|
|
17
|
+
// The one word for the box's being: her key in the box's ward, the id every
|
|
18
|
+
// lent being knows her by, and the standing every hosted ward takes at
|
|
19
|
+
// birth.
|
|
20
|
+
export const BOX = 'box';
|
|
21
|
+
export const LENT_ASKS = {
|
|
22
|
+
offer: { name: 'offer', description: 'an invitation on me, for a ward the box vouches for', input: { type: 'object' } },
|
|
23
|
+
retract: { name: 'retract', description: 'an offer that was not taken, by its heir', input: { type: 'object', properties: { heir: { type: 'string' } }, required: ['heir'] } },
|
|
24
|
+
};
|
|
25
|
+
export class Lent extends Being {
|
|
26
|
+
// The offers she has open, heir to the id she minted under.
|
|
27
|
+
#open() {
|
|
28
|
+
return (this.cells.open ??= {});
|
|
29
|
+
}
|
|
30
|
+
async offer() {
|
|
31
|
+
const n = (this.cells.lent ?? 0) + 1;
|
|
32
|
+
this.cells.lent = n;
|
|
33
|
+
const id = `lent:${n}`;
|
|
34
|
+
const inv = await this.invite(id);
|
|
35
|
+
if (inv === null)
|
|
36
|
+
return { error: 'could not mint' };
|
|
37
|
+
this.#open()[inv.heir] = id;
|
|
38
|
+
return inv;
|
|
39
|
+
}
|
|
40
|
+
retract(args) {
|
|
41
|
+
const open = this.#open();
|
|
42
|
+
const heir = typeof args.heir === 'string' ? args.heir : '';
|
|
43
|
+
const id = Object.hasOwn(open, heir) ? open[heir] : undefined;
|
|
44
|
+
if (id === undefined)
|
|
45
|
+
return { retracted: null };
|
|
46
|
+
this.occupants.remove(id);
|
|
47
|
+
delete open[heir];
|
|
48
|
+
return { retracted: id };
|
|
49
|
+
}
|
|
50
|
+
describe(asker) {
|
|
51
|
+
const bp = super.describe(asker);
|
|
52
|
+
if (asker.id !== BOX)
|
|
53
|
+
return bp;
|
|
54
|
+
return { ...bp, asks: [...bp.asks, LENT_ASKS.offer, LENT_ASKS.retract] };
|
|
55
|
+
}
|
|
56
|
+
async answer(asker, method, args = {}) {
|
|
57
|
+
if (asker.id === BOX && method === 'offer')
|
|
58
|
+
return this.offer();
|
|
59
|
+
if (asker.id === BOX && method === 'retract')
|
|
60
|
+
return this.retract(args);
|
|
61
|
+
return super.answer(asker, method, args);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/being/types.js
CHANGED
|
@@ -25,7 +25,7 @@ export const invitationArgs = (inv) => ({
|
|
|
25
25
|
});
|
|
26
26
|
// Whether a value that arrived is an invitation. The shape is the spec's, so
|
|
27
27
|
// the reading of it is too, and it is one reading: a form that
|
|
28
|
-
// asks a guest for one, a
|
|
28
|
+
// asks a guest for one, a lent being handed one to wake her maker by, and a
|
|
29
29
|
// shell that finds one in a link all ask the same question, and a kinder
|
|
30
30
|
// answer in one of them is a value that fails at a door instead of at the
|
|
31
31
|
// edge it came in by. A ward pk is a hundred and twenty-eight lowercase hex,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Being } from '../being/being.ts';
|
|
2
|
+
import { Lent } from '../being/lent.ts';
|
|
2
3
|
import type { Asker, Blueprint, Invitation, Json, JsonObject, OccupantRecord, Reply, Stance } from '../being/types.ts';
|
|
3
4
|
export declare class Printer extends Being {
|
|
4
5
|
static cells: {
|
|
@@ -157,7 +158,7 @@ export declare class Maker extends Being {
|
|
|
157
158
|
lent: string | null;
|
|
158
159
|
}>;
|
|
159
160
|
}
|
|
160
|
-
export declare class Member extends
|
|
161
|
+
export declare class Member extends Lent {
|
|
161
162
|
static cells: {
|
|
162
163
|
heard: JsonObject[];
|
|
163
164
|
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// shop, a customer on the base class, a maker, a member, and one raw being
|
|
4
4
|
// with no base at all.
|
|
5
5
|
import { Being } from '../being/being.js';
|
|
6
|
+
import { Lent } from '../being/lent.js';
|
|
6
7
|
import { answered, isSilence, isUnreached, isWord, wordOf } from '../being/silence.js';
|
|
7
8
|
import { invitationArgs } from '../being/types.js';
|
|
8
9
|
// A printer. Invites whom she is told to, prints for her occupants. Her
|
|
@@ -153,9 +154,13 @@ export class Maker extends Being {
|
|
|
153
154
|
return { lent: await this.lend(name, id) };
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
// A member is also lendable: lent by a box that stands her in its own ward,
|
|
158
|
+
// she offers an invitation on herself when the box's being asks, and is an
|
|
159
|
+
// ordinary being to everyone else.
|
|
160
|
+
export class Member extends Lent {
|
|
161
|
+
static cells = { ...Lent.cells, heard: [] };
|
|
158
162
|
static asks = {
|
|
163
|
+
...Lent.asks,
|
|
159
164
|
ping: { input: { type: 'object' } },
|
|
160
165
|
relay: { input: { type: 'object', properties: { id: { type: 'string' }, method: { type: 'string' }, time: { type: 'number' } }, required: ['id'] } },
|
|
161
166
|
};
|
|
@@ -63,7 +63,6 @@ export type Refusing = {
|
|
|
63
63
|
};
|
|
64
64
|
export { Printer, Shop, Customer, Echo, Maker, Member };
|
|
65
65
|
export declare const HEX64: RegExp;
|
|
66
|
-
export declare const HEX128: RegExp;
|
|
67
66
|
export type Runner = (name: string, opts: {
|
|
68
67
|
skip?: string | false;
|
|
69
68
|
}, fn: () => Promise<void> | void) => void;
|
|
@@ -33,7 +33,7 @@ export { Printer, Shop, Customer, Echo, Maker, Member };
|
|
|
33
33
|
// expects instead of importing ours, and nothing under `src/conformance`
|
|
34
34
|
// reaches into `src/ward` for anything.
|
|
35
35
|
export const HEX64 = /^[0-9a-f]{64}$/; // one key, one secret, one digest
|
|
36
|
-
|
|
36
|
+
const HEX128 = /^[0-9a-f]{128}$/; // a ward pk: the signing pk then the padlock
|
|
37
37
|
// Her ward's word, as she receives it.
|
|
38
38
|
const said = (x, w) => told(x) === w;
|
|
39
39
|
// A world whose harness stands a ward up again. The two members are optional
|
|
@@ -412,10 +412,12 @@ export function conform(label, make, { canDown = true, canLend = true, canRestar
|
|
|
412
412
|
assert.equal(after.said, null, 'and she asks again on the same standing');
|
|
413
413
|
});
|
|
414
414
|
// Lend is boot's other half: what the device already has, reached the one
|
|
415
|
-
// way anything is reached. The
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
415
|
+
// way anything is reached. The device is one standing the ward holds at
|
|
416
|
+
// the box's being, and a lend is an ask on it; the being lent stands in
|
|
417
|
+
// a ward the harbor roots, so nothing here can look at her from the
|
|
418
|
+
// outside, and what the ward owes is what is asserted. She is handed an
|
|
419
|
+
// id, the standing is an ordinary one, and the invitation is nowhere she
|
|
420
|
+
// can read.
|
|
419
421
|
lends('what the device lends is a being: she is handed an id, asks on an ordinary standing, and never sees the invitation', async () => {
|
|
420
422
|
const w = await make({ lends: { pen: 'Member' } });
|
|
421
423
|
const maker = await w.boot('MK', Maker);
|
|
@@ -574,6 +576,7 @@ export function conform(label, make, { canDown = true, canLend = true, canRestar
|
|
|
574
576
|
const names = (x) => bp(x).asks.map((a) => a.name);
|
|
575
577
|
assert.deepEqual(names(await alice.being.learn()), ['hello', 'buy', 'refund']);
|
|
576
578
|
assert.deepEqual(names(await bob.being.learn()), ['hello', 'buy']);
|
|
579
|
+
assert.deepEqual(await alice.being.standings.shop.ask('refund'), { ok: true }, 'and what she is shown she can ask');
|
|
577
580
|
});
|
|
578
581
|
t('the digest is per relation: two occupants, two digests of one being, both true', async () => {
|
|
579
582
|
const { shop, alice, bob } = await world();
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Being } from '../being/index.ts';
|
|
2
|
+
import type { Asker, BeingClass, Invitation, JsonObject, OccupantRecord } from '../being/index.ts';
|
|
3
|
+
import { type WardPointers } from '../ward/ground.ts';
|
|
4
|
+
export type Root = Pick<WardPointers, 'ask'>;
|
|
5
|
+
export { BOX } from '../being/lent.ts';
|
|
6
|
+
export declare const BOX_SEED = "box";
|
|
7
|
+
declare const ward: (occ: OccupantRecord | undefined) => boolean;
|
|
8
|
+
declare const root: (_occ: OccupantRecord | undefined, asker: Asker) => boolean;
|
|
9
|
+
export declare const BOX_ASKS: {
|
|
10
|
+
offer: {
|
|
11
|
+
description: string;
|
|
12
|
+
input: {
|
|
13
|
+
type: string;
|
|
14
|
+
properties: {
|
|
15
|
+
name: {
|
|
16
|
+
type: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
required: string[];
|
|
20
|
+
};
|
|
21
|
+
for: typeof ward;
|
|
22
|
+
};
|
|
23
|
+
retract: {
|
|
24
|
+
description: string;
|
|
25
|
+
input: {
|
|
26
|
+
type: string;
|
|
27
|
+
properties: {
|
|
28
|
+
name: {
|
|
29
|
+
type: string;
|
|
30
|
+
};
|
|
31
|
+
heir: {
|
|
32
|
+
type: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
required: string[];
|
|
36
|
+
};
|
|
37
|
+
for: typeof ward;
|
|
38
|
+
};
|
|
39
|
+
host: {
|
|
40
|
+
description: string;
|
|
41
|
+
input: {
|
|
42
|
+
type: string;
|
|
43
|
+
properties: {
|
|
44
|
+
seed: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
required: string[];
|
|
49
|
+
};
|
|
50
|
+
for: typeof root;
|
|
51
|
+
};
|
|
52
|
+
roll: {
|
|
53
|
+
description: string;
|
|
54
|
+
input: {
|
|
55
|
+
type: string;
|
|
56
|
+
};
|
|
57
|
+
for: typeof root;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export declare class Box extends Being {
|
|
61
|
+
static cells: JsonObject;
|
|
62
|
+
static asks: {
|
|
63
|
+
offer: {
|
|
64
|
+
description: string;
|
|
65
|
+
input: {
|
|
66
|
+
type: string;
|
|
67
|
+
properties: {
|
|
68
|
+
name: {
|
|
69
|
+
type: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
required: string[];
|
|
73
|
+
};
|
|
74
|
+
for: typeof ward;
|
|
75
|
+
};
|
|
76
|
+
retract: {
|
|
77
|
+
description: string;
|
|
78
|
+
input: {
|
|
79
|
+
type: string;
|
|
80
|
+
properties: {
|
|
81
|
+
name: {
|
|
82
|
+
type: string;
|
|
83
|
+
};
|
|
84
|
+
heir: {
|
|
85
|
+
type: string;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
required: string[];
|
|
89
|
+
};
|
|
90
|
+
for: typeof ward;
|
|
91
|
+
};
|
|
92
|
+
host: {
|
|
93
|
+
description: string;
|
|
94
|
+
input: {
|
|
95
|
+
type: string;
|
|
96
|
+
properties: {
|
|
97
|
+
seed: {
|
|
98
|
+
type: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
required: string[];
|
|
102
|
+
};
|
|
103
|
+
for: typeof root;
|
|
104
|
+
};
|
|
105
|
+
roll: {
|
|
106
|
+
description: string;
|
|
107
|
+
input: {
|
|
108
|
+
type: string;
|
|
109
|
+
};
|
|
110
|
+
for: typeof root;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
offer(args: JsonObject): Promise<JsonObject>;
|
|
114
|
+
retract(args: JsonObject): Promise<JsonObject>;
|
|
115
|
+
host(args: JsonObject): Promise<JsonObject>;
|
|
116
|
+
roll(): JsonObject;
|
|
117
|
+
}
|
|
118
|
+
export declare function stand(box: Root, lent: Record<string, BeingClass>, own?: string): Promise<void>;
|
|
119
|
+
export declare function hold(box: Root, key: string): Promise<boolean>;
|
|
120
|
+
export declare function offerTo(box: Root, seed: string): Promise<Invitation | undefined>;
|
|
121
|
+
export declare function roll(box: Root): Promise<string[]>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The box as one being. A harbor boots a ward of its own and one being in
|
|
3
|
+
// it, the box's own, who holds a standing at every lent being the box
|
|
4
|
+
// offers, under the name each is lent as, and keeps the roll of the wards
|
|
5
|
+
// the box hosts. Every hosted ward is handed one invitation on her at boot
|
|
6
|
+
// and takes it as its own standing; from then on the device is one
|
|
7
|
+
// standing, what it offers is her describe, and a lend is the ask `offer`
|
|
8
|
+
// on her.
|
|
9
|
+
//
|
|
10
|
+
// The harbor works in acts and holds no memory of its own. Act one boots the
|
|
11
|
+
// box's ward on the plain ground, no box, because it is the box; her lent
|
|
12
|
+
// beings come up, and the root places her standings and nothing else. Act
|
|
13
|
+
// two asks her who is hosted and boots each with one invitation. Everything
|
|
14
|
+
// the device is lives in her ward as a lent being's cells, placed through
|
|
15
|
+
// asks and read from no file.
|
|
16
|
+
//
|
|
17
|
+
// A runtime that needs her to be more, a resolver of names or a carrier of
|
|
18
|
+
// her lent beings' asks for a screen, extends her and overrides the asks.
|
|
19
|
+
import { Being, isInvitation, OWNER } from '../being/index.js';
|
|
20
|
+
import { BOX } from '../being/lent.js';
|
|
21
|
+
import { OFFER, RETRACT } from '../ward/ground.js';
|
|
22
|
+
export { BOX } from '../being/lent.js';
|
|
23
|
+
// The name of the box's own ward, one per harbor: the seed a memory harbor
|
|
24
|
+
// boots it under, and the name a store keeps it by.
|
|
25
|
+
export const BOX_SEED = 'box';
|
|
26
|
+
// A ward the box vouches for: invited by the root at act two, with the one
|
|
27
|
+
// note that says so. A screen or a model the root lets in has no such note
|
|
28
|
+
// and is shown nothing of the lend seam.
|
|
29
|
+
const ward = (occ) => occ?.notes.ward === true;
|
|
30
|
+
const root = (_occ, asker) => asker.id === OWNER;
|
|
31
|
+
export const BOX_ASKS = {
|
|
32
|
+
[OFFER]: { description: 'an invitation on the being lent under this name', input: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] }, for: ward },
|
|
33
|
+
[RETRACT]: { description: 'an offer under this name that was not taken, by its heir', input: { type: 'object', properties: { name: { type: 'string' }, heir: { type: 'string' } }, required: ['name', 'heir'] }, for: ward },
|
|
34
|
+
host: { description: 'a ward this box hosts from now', input: { type: 'object', properties: { seed: { type: 'string' } }, required: ['seed'] }, for: root },
|
|
35
|
+
roll: { description: 'the wards this box hosts', input: { type: 'object' }, for: root },
|
|
36
|
+
};
|
|
37
|
+
export class Box extends Being {
|
|
38
|
+
static cells = { hosted: [], offers: {} };
|
|
39
|
+
static asks = BOX_ASKS;
|
|
40
|
+
// Offer: the being under the name, asked to offer, answers her own
|
|
41
|
+
// invitation. A subclass that lends under more names than it holds
|
|
42
|
+
// standings, a contract's digest for one, resolves the name first and
|
|
43
|
+
// calls this with the standing's own.
|
|
44
|
+
async offer(args) {
|
|
45
|
+
const at = this.standings[args.name];
|
|
46
|
+
if (!at)
|
|
47
|
+
return { error: 'lends none' };
|
|
48
|
+
const out = await at.ask(OFFER);
|
|
49
|
+
return isInvitation(out) ? out : { error: 'not offered' };
|
|
50
|
+
}
|
|
51
|
+
async retract(args) {
|
|
52
|
+
const at = this.standings[args.name];
|
|
53
|
+
if (!at)
|
|
54
|
+
return { error: 'lends none' };
|
|
55
|
+
const out = await at.ask(RETRACT, { heir: args.heir });
|
|
56
|
+
return out !== null && typeof out === 'object' && !Array.isArray(out) ? out : { retracted: null };
|
|
57
|
+
}
|
|
58
|
+
// A ward on the roll, and its one invitation on her. She mints it the first
|
|
59
|
+
// time and keeps it, and hands the same one at every later boot until it
|
|
60
|
+
// is spent: a ward that could not take the box at birth, the box down or
|
|
61
|
+
// the knock lost, takes it at the next, as the spec promises. A ward that
|
|
62
|
+
// holds the standing already ignores it, and a spent heir opens no door,
|
|
63
|
+
// so handing it again costs nothing. The value never leaves her ward: it
|
|
64
|
+
// is read by the root alone and rides in the ground and nowhere else.
|
|
65
|
+
async host(args) {
|
|
66
|
+
const hosted = (this.cells.hosted ??= []);
|
|
67
|
+
const offers = (this.cells.offers ??= {});
|
|
68
|
+
if (typeof args.seed !== 'string')
|
|
69
|
+
return { error: 'a seed is a word' };
|
|
70
|
+
if (!hosted.includes(args.seed))
|
|
71
|
+
hosted.push(args.seed);
|
|
72
|
+
if (!Object.hasOwn(offers, args.seed)) {
|
|
73
|
+
const inv = await this.invite(args.seed, { ward: true });
|
|
74
|
+
if (inv !== null)
|
|
75
|
+
offers[args.seed] = inv;
|
|
76
|
+
}
|
|
77
|
+
return { hosted: [...hosted], invitation: offers[args.seed] ?? null };
|
|
78
|
+
}
|
|
79
|
+
roll() {
|
|
80
|
+
return { hosted: [...(this.cells.hosted ?? [])] };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Act one's second half, on a box ward already booted: the box's being and
|
|
84
|
+
// each lent being booted by key, and her standing placed at each under its
|
|
85
|
+
// key by the root. A ward woken from its memory already holds all of it,
|
|
86
|
+
// and a key already booted is refused and costs nothing, so this is run at
|
|
87
|
+
// every boot and does the right thing at each.
|
|
88
|
+
export async function stand(box, lent, own = 'Box') {
|
|
89
|
+
const up = async (key, cls) => {
|
|
90
|
+
const out = (await box.ask('boot', { key, class: cls }));
|
|
91
|
+
return out.booted === key;
|
|
92
|
+
};
|
|
93
|
+
await up(BOX, own);
|
|
94
|
+
for (const name of Object.keys(lent)) {
|
|
95
|
+
const fresh = await up(name, lent[name].name);
|
|
96
|
+
if (!fresh)
|
|
97
|
+
continue;
|
|
98
|
+
await hold(box, name);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// The box's standing at one lent being of her ward, under that being's key,
|
|
102
|
+
// placed by the root: the lent being invites the box's being as `box`, and
|
|
103
|
+
// she knocks. Refused where either already holds the relation.
|
|
104
|
+
export async function hold(box, key) {
|
|
105
|
+
const inv = await box.ask('invite', { being: key, id: BOX });
|
|
106
|
+
if (!isInvitation(inv))
|
|
107
|
+
return false;
|
|
108
|
+
const out = (await box.ask('knock', { being: BOX, id: key, invitation: inv }));
|
|
109
|
+
return out?.taken === key;
|
|
110
|
+
}
|
|
111
|
+
// One invitation on the box's being for the ward named, and the ward on the
|
|
112
|
+
// roll: the same one at every boot, minted once by her and kept by her.
|
|
113
|
+
export async function offerTo(box, seed) {
|
|
114
|
+
const out = (await box.ask('ask', { being: BOX, method: 'host', args: { seed } }));
|
|
115
|
+
return isInvitation(out?.invitation) ? out.invitation : undefined;
|
|
116
|
+
}
|
|
117
|
+
// Who the box hosts, asked of the box's being.
|
|
118
|
+
export async function roll(box) {
|
|
119
|
+
const out = (await box.ask('ask', { being: BOX, method: 'roll' }));
|
|
120
|
+
return out.hosted ?? [];
|
|
121
|
+
}
|
package/dist/harbor/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type WardPointers
|
|
2
|
-
import type { BeingClass, BeingLike } from '../being/types.ts';
|
|
1
|
+
import { type WardPointers } from '../ward/ground.ts';
|
|
2
|
+
import type { BeingClass, BeingLike, Invitation } from '../being/types.ts';
|
|
3
3
|
import { type Reach } from './reach.ts';
|
|
4
4
|
import type { Kept, Store, WardRecord } from './store.ts';
|
|
5
5
|
export type Hosted = WardPointers & {
|
|
@@ -34,9 +34,9 @@ export declare class Harbor {
|
|
|
34
34
|
readonly classes: Record<string, BeingClass>;
|
|
35
35
|
readonly fallbacks: Reach[];
|
|
36
36
|
readonly announcers: Set<() => void>;
|
|
37
|
-
|
|
38
|
-
constructor(store: Store, loader?: Loader
|
|
39
|
-
protected
|
|
37
|
+
offering: ((name: string) => Promise<Invitation | undefined>) | undefined;
|
|
38
|
+
constructor(store: Store, loader?: Loader);
|
|
39
|
+
protected boxFor(name: string, _record: WardRecord): Promise<Invitation | undefined>;
|
|
40
40
|
boot(): Promise<void>;
|
|
41
41
|
partitionOf(name: string): Record<string, unknown> | undefined;
|
|
42
42
|
carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | undefined>;
|