nervur 0.20.3 → 0.21.0
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 +61 -20
- package/dist/cli/command.js +18 -4
- package/dist/cli/edge.d.ts +7 -0
- package/dist/cli/edge.js +58 -0
- package/dist/cli/main.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/proof/contracts.d.ts +15 -0
- package/dist/proof/contracts.js +115 -0
- package/dist/proof/expect.d.ts +6 -0
- package/dist/proof/expect.js +18 -0
- package/dist/proof/index.d.ts +3 -0
- package/dist/proof/index.js +12 -0
- package/dist/proof/law.d.ts +52 -0
- package/dist/proof/law.js +345 -0
- package/package.json +11 -3
- package/src/cli/command.ts +17 -3
- package/src/cli/edge.ts +65 -0
- package/src/index.ts +1 -1
- package/src/proof/contracts.ts +129 -0
- package/src/proof/expect.ts +25 -0
- package/src/proof/index.ts +12 -0
- package/src/proof/law.ts +352 -0
package/README.md
CHANGED
|
@@ -43,38 +43,74 @@ terrain, as below, and that one is used.
|
|
|
43
43
|
## A harbor on the edge
|
|
44
44
|
|
|
45
45
|
An edge harbor is born on your machine and carried there, so you pilot it
|
|
46
|
-
from your own harbor, as you pilot one on a server.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```sh
|
|
50
|
-
nervur init --dir ./edge
|
|
51
|
-
nervur reach https://harbor.example.workers.dev/quo --dir ./edge
|
|
52
|
-
nervur own pilot --dir ./edge
|
|
53
|
-
nervur edge pack ./package.json --dir ./edge
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
`own` prints the owner invitation, which never leaves your machine.
|
|
57
|
-
`edge pack` prints the harbor's seed and writes its sealed package. Keep
|
|
58
|
-
the seed as the worker's secret, `wrangler secret put NERVUR_SEED`, and
|
|
59
|
-
bundle the package with the worker:
|
|
46
|
+
from your own harbor, as you pilot one on a server. The worker is a
|
|
47
|
+
folder of its own, an ES module project (`"type": "module"` in its
|
|
48
|
+
`package.json`), holding the worker and its `wrangler.toml`:
|
|
60
49
|
|
|
61
50
|
```js
|
|
51
|
+
// worker.js
|
|
62
52
|
import { harborObject } from 'nervur/edge';
|
|
63
|
-
import carried from './package.json';
|
|
53
|
+
import carried from './package.edge.json';
|
|
64
54
|
import * as greetings from './greetings.js';
|
|
65
55
|
|
|
66
56
|
export const Harbor = harborObject({ modules: [greetings], package: carried });
|
|
57
|
+
|
|
58
|
+
export default {
|
|
59
|
+
fetch(request, env) {
|
|
60
|
+
return env.HARBOR.get(env.HARBOR.idFromName('main')).fetch(request);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
# wrangler.toml
|
|
67
|
+
name = "harbor"
|
|
68
|
+
main = "worker.js"
|
|
69
|
+
compatibility_date = "2026-09-01"
|
|
70
|
+
|
|
71
|
+
[[durable_objects.bindings]]
|
|
72
|
+
name = "HARBOR"
|
|
73
|
+
class_name = "Harbor"
|
|
74
|
+
|
|
75
|
+
[[migrations]]
|
|
76
|
+
tag = "v1"
|
|
77
|
+
new_sqlite_classes = ["Harbor"]
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
Make the harbor and set it up with the command, from that folder, naming
|
|
81
|
+
where the edge will reach it:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
nervur init --dir ./harbor
|
|
85
|
+
nervur module add ./greetings.js --dir ./harbor
|
|
86
|
+
nervur reach http://127.0.0.1:8787/quo --dir ./harbor
|
|
87
|
+
nervur own me --dir ./harbor
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`own` prints the owner invitation, which never leaves your machine. Run
|
|
91
|
+
it on this machine first:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
nervur edge dev ./package.edge.json --dir ./harbor
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`edge dev` packs the harbor into `package.edge.json` and runs your
|
|
98
|
+
`wrangler dev` with its seed, keeping the object's storage in
|
|
99
|
+
`.nervur-edge`, so the harbor stands again each time you run it. The
|
|
100
|
+
first time the object starts, the package lands in its storage and the
|
|
101
|
+
harbor opens under that seed; after that the storage is the harbor, and
|
|
102
|
+
a new deploy leaves it be. Then, from your own harbor:
|
|
72
103
|
|
|
73
104
|
```sh
|
|
74
105
|
nervur pilot edge '<the owner invitation>' --dir ./mine
|
|
75
106
|
nervur host shop --via edge --dir ./mine
|
|
76
107
|
```
|
|
77
108
|
|
|
109
|
+
For Cloudflare, name the address it will answer at with `reach`, then
|
|
110
|
+
`nervur edge pack ./package.edge.json --dir ./harbor` prints the seed and
|
|
111
|
+
writes the package. Keep the seed as the worker's secret,
|
|
112
|
+
`wrangler secret put NERVUR_SEED`, and `wrangler deploy`.
|
|
113
|
+
|
|
78
114
|
`pilot` opens the web carrier where the invitation names a web address.
|
|
79
115
|
An object with no package draws its own seed, sealed under the worker's
|
|
80
116
|
`NERVUR_SECRET`, and `stood` runs the worker's own code on it as root:
|
|
@@ -211,8 +247,13 @@ stands a ward under a seed you name, or a drawn one.
|
|
|
211
247
|
- **Push, webPush**: a ring with no content, as Web Push sends it,
|
|
212
248
|
signed with VAPID, on fetch and Web Crypto.
|
|
213
249
|
|
|
214
|
-
|
|
215
|
-
|
|
250
|
+
Quo crosses two carriers, TCP and the web, and no third. MCP, an HTTP API
|
|
251
|
+
and every other outside protocol are gateway faculties: classes that
|
|
252
|
+
serve their clients, a UI in a browser, an MCP client, a caller holding
|
|
253
|
+
a bearer token, on one side, and stand as an ordinary being on the
|
|
254
|
+
other, offering standings, holding occupants and speaking Quo inbound
|
|
255
|
+
and outbound. They and every screen
|
|
256
|
+
are `@nervur-org/*`'s.
|
|
216
257
|
|
|
217
258
|
## The command
|
|
218
259
|
|
package/dist/cli/command.js
CHANGED
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
// printed, for the worker's
|
|
24
24
|
// NERVUR_SEED, and its sealed
|
|
25
25
|
// package written to the file
|
|
26
|
+
// nervur edge dev <file> [--port P] the harbor packed to the file and
|
|
27
|
+
// run by the worker's own wrangler
|
|
28
|
+
// dev, from this folder, at
|
|
29
|
+
// http://127.0.0.1:P/quo
|
|
26
30
|
// nervur open <key> <kind> a faculty stands in the box ward
|
|
27
31
|
// nervur host <ward> [--seed S] a hosted ward stands
|
|
28
32
|
// nervur route <ward pk> <address... | none> where a ward is reached, in order
|
|
@@ -51,7 +55,10 @@ import { join, resolve } from 'node:path';
|
|
|
51
55
|
import { WEB_OPENING } from '../line/index.js';
|
|
52
56
|
import { harborDir, isFile } from '../node/index.js';
|
|
53
57
|
import { readAt, webAddress } from '../quo/index.js';
|
|
58
|
+
import { edgeDev } from './edge.js';
|
|
54
59
|
import { carry, isServed, land, open, pack, request, serve } from './harbor.js';
|
|
60
|
+
// Where `edge dev` answers unless --port names another, wrangler's own.
|
|
61
|
+
const EDGE_PORT = 8787;
|
|
55
62
|
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]';
|
|
56
63
|
const FLAGS = new Set(['dir', 'ward', 'via', 'seed', 'host', 'port']);
|
|
57
64
|
// The one flag that stands alone, since what it names is a yes.
|
|
@@ -193,13 +200,20 @@ export const main = async (argv, env, print) => {
|
|
|
193
200
|
return out({ landed: await land(dir, JSON.parse(text)) });
|
|
194
201
|
}
|
|
195
202
|
if (command === 'edge') {
|
|
196
|
-
if (words[0] !== 'pack' || words.length !== 2)
|
|
197
|
-
throw new Error('edge takes pack and a file');
|
|
203
|
+
if ((words[0] !== 'pack' && words[0] !== 'dev') || words.length !== 2)
|
|
204
|
+
throw new Error('edge takes pack or dev, and a file');
|
|
198
205
|
if (flags.via !== undefined)
|
|
199
|
-
throw new Error(
|
|
206
|
+
throw new Error(`edge ${words[0]} reads this folder, so it takes no --via`);
|
|
200
207
|
if (await isServed(dir))
|
|
201
208
|
throw new Error(`a harbor is served from ${dir}: stop it first`);
|
|
202
|
-
|
|
209
|
+
if (words[0] === 'pack')
|
|
210
|
+
return out(await pack(dir, words[1]));
|
|
211
|
+
const dev = await edgeDev(dir, words[1], process.cwd(), flags.port === undefined ? EDGE_PORT : Number(flags.port));
|
|
212
|
+
const stop = () => void dev.close().then(() => process.exit(0));
|
|
213
|
+
process.once('SIGINT', stop);
|
|
214
|
+
process.once('SIGTERM', stop);
|
|
215
|
+
print(JSON.stringify({ at: dev.at, places: dev.places }));
|
|
216
|
+
return SERVING;
|
|
203
217
|
}
|
|
204
218
|
if (command === 'serve') {
|
|
205
219
|
if (words.length > 0)
|
package/dist/cli/edge.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// `nervur edge dev`: an edge harbor on this machine, as it will stand on
|
|
3
|
+
// Cloudflare. The harbor of a folder is packed into the file the worker
|
|
4
|
+
// imports, and the worker's own `wrangler dev` runs it, in the folder the
|
|
5
|
+
// command runs from, with the seed handed as `NERVUR_SEED` through the
|
|
6
|
+
// environment so no listing of processes shows it. The object's storage is
|
|
7
|
+
// kept under `.nervur-edge` there, so a harbor landed once stands again
|
|
8
|
+
// across restarts, as it does on Cloudflare.
|
|
9
|
+
//
|
|
10
|
+
// wrangler is the worker's tool and never the kit's: the one who deploys
|
|
11
|
+
// an edge has it already, and `edge dev` finds it where npm put it.
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { pack } from './harbor.js';
|
|
15
|
+
export const EDGE_STATE = '.nervur-edge';
|
|
16
|
+
const READY_TRIES = 240;
|
|
17
|
+
const READY_EVERY = 250;
|
|
18
|
+
const answers = (url) => fetch(url).then(() => true, () => false);
|
|
19
|
+
// Resolved once wrangler answers at `url`, rejected once it exits or never
|
|
20
|
+
// answers.
|
|
21
|
+
const ready = async (child, url, said) => {
|
|
22
|
+
let gone = false;
|
|
23
|
+
child.once('exit', () => (gone = true));
|
|
24
|
+
for (let i = 0; i < READY_TRIES; i += 1) {
|
|
25
|
+
if (gone)
|
|
26
|
+
throw new Error(`wrangler dev exited: ${said()}`);
|
|
27
|
+
if (await answers(url))
|
|
28
|
+
return;
|
|
29
|
+
await new Promise((r) => setTimeout(r, READY_EVERY));
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`wrangler dev never answered at ${url}: ${said()}`);
|
|
32
|
+
};
|
|
33
|
+
export const edgeDev = async (dir, file, cwd, port) => {
|
|
34
|
+
const { seed, places } = (await pack(dir, file));
|
|
35
|
+
const child = spawn('npx', ['--no-install', 'wrangler', 'dev', '--ip', '127.0.0.1', '--port', String(port), '--persist-to', join(cwd, EDGE_STATE), '--log-level', 'warn'], {
|
|
36
|
+
cwd,
|
|
37
|
+
env: { ...process.env, NERVUR_SEED: seed, CLOUDFLARE_INCLUDE_PROCESS_ENV: 'true' },
|
|
38
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
39
|
+
detached: true,
|
|
40
|
+
});
|
|
41
|
+
let err = '';
|
|
42
|
+
child.stderr.on('data', (chunk) => (err = `${err}${chunk.toString('utf8')}`.slice(-4000)));
|
|
43
|
+
const at = `http://127.0.0.1:${String(port)}/quo`;
|
|
44
|
+
const close = () => new Promise((done) => {
|
|
45
|
+
if (child.exitCode !== null)
|
|
46
|
+
return done();
|
|
47
|
+
child.once('exit', () => done());
|
|
48
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
await ready(child, at, () => err);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
await close();
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
return { at, places, close };
|
|
58
|
+
};
|
package/dist/cli/main.js
CHANGED
|
File without changes
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { Carrier, Clock, Custody, Entropy, Loader, Memory, type Loaded, type Mod
|
|
|
5
5
|
export { CEILING, DEFAULT, DEPTH, ownerAsk, Pilot, pilots, WardBeing, type Steward, type WardBeingStance } from './ward/index.ts';
|
|
6
6
|
export { BOX, CarrierFaculty, Catalogue, copyPackage, Dock, Harbor, addGround, grounds, readRootRequest, Registry, Push, PUSH, RELAY, RELAY_CLIENT, rootLine, Terrain, UNDIALED, type Dialed, type Failures, type Ground, type GroundFaculty, type GroundProbe, type Opening, type Probing, type RootRequest, } from './harbor/index.ts';
|
|
7
7
|
export { CryptoEntropy, HeldCustody, HeldLoader, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World, type PointerParts } from './pointer/index.ts';
|
|
8
|
-
export { NEUTRAL, WEB, WEB_OPENING, WEB_PUSH, WebDialer, webFaculty, webPush, type WebListen, type WebListener, type WebServe } from './line/index.ts';
|
|
8
|
+
export { answerPost, NEUTRAL, WEB, WEB_OPENING, WEB_PUSH, WebDialer, webFaculty, webPush, type WebListen, type WebListener, type WebServe } from './line/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,6 @@ export { CEILING, DEFAULT, DEPTH, ownerAsk, Pilot, pilots, WardBeing } from './w
|
|
|
15
15
|
export { BOX, CarrierFaculty, Catalogue, copyPackage, Dock, Harbor, addGround, grounds, readRootRequest, Registry, Push, PUSH, RELAY, RELAY_CLIENT, rootLine, Terrain, UNDIALED, } from './harbor/index.js';
|
|
16
16
|
// The pointer dock: a whole world in one process.
|
|
17
17
|
export { CryptoEntropy, HeldCustody, HeldLoader, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World } from './pointer/index.js';
|
|
18
|
-
export { NEUTRAL, WEB, WEB_OPENING, WEB_PUSH, WebDialer, webFaculty, webPush } from './line/index.js';
|
|
18
|
+
export { answerPost, NEUTRAL, WEB, WEB_OPENING, WEB_PUSH, WebDialer, webFaculty, webPush } from './line/index.js';
|
|
19
19
|
// The ground that fits everywhere, tried after any a platform entry adds.
|
|
20
20
|
addGround(neutralGround);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Custody, Entropy, Loader, Memory } from '../contract/index.ts';
|
|
2
|
+
import type { Expect } from './expect.ts';
|
|
3
|
+
export type Scene = [string, () => Promise<void>];
|
|
4
|
+
export declare const custodyScenes: (name: string, make: () => [Custody, Custody], expect: Expect) => Scene[];
|
|
5
|
+
export type Cut = {
|
|
6
|
+
cut: Memory;
|
|
7
|
+
fresh: Memory;
|
|
8
|
+
};
|
|
9
|
+
export declare const memoryScenes: (name: string, make: () => [Memory, Memory], expect: Expect, half?: () => Cut) => Scene[];
|
|
10
|
+
export declare const entropyScenes: (name: string, make: () => Entropy, expect: Expect) => Scene[];
|
|
11
|
+
export declare const loaderScenes: (name: string, make: () => {
|
|
12
|
+
loader: Loader;
|
|
13
|
+
found: string;
|
|
14
|
+
missing: string;
|
|
15
|
+
}, expect: Expect) => Scene[];
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The suites of the bodies, Custody, Memory, Entropy and Loader, as scenes
|
|
3
|
+
// with no platform under them, so Node's runner, every engine and a
|
|
4
|
+
// stranger's own runner hold a body to the same assertions.
|
|
5
|
+
import { Custody, Entropy, Loader, Memory } from '../contract/index.js';
|
|
6
|
+
const bytes = (...values) => Uint8Array.from(values);
|
|
7
|
+
// A place as plain values, in name order, so two can be compared as JSON.
|
|
8
|
+
const plain = (place) => [...place].map(([n, b]) => [n, [...b]]).sort(([a], [b]) => (a < b ? -1 : 1));
|
|
9
|
+
// `make` gives two bodies over one keeping, as two runs would build them,
|
|
10
|
+
// and a fresh keeping each call.
|
|
11
|
+
export const custodyScenes = (name, make, expect) => [
|
|
12
|
+
[
|
|
13
|
+
`[contract] ${name} fulfils Custody: one seed of thirty-two bytes, the same every time`,
|
|
14
|
+
async () => {
|
|
15
|
+
const [custody, again] = make();
|
|
16
|
+
expect.ok(custody instanceof Custody);
|
|
17
|
+
const seed = await custody.seed();
|
|
18
|
+
expect.equal(seed.length, 32);
|
|
19
|
+
const first = [...seed];
|
|
20
|
+
seed.fill(0);
|
|
21
|
+
expect.same([...(await custody.seed())], first, 'a seed handed out is a copy');
|
|
22
|
+
expect.same([...(await again.seed())], first, 'another body over the keeping holds the same');
|
|
23
|
+
expect.ok([...(await make()[0].seed())].join() !== first.join(), 'another keeping holds another seed');
|
|
24
|
+
const [fresh, rival] = make();
|
|
25
|
+
const [a, b] = await Promise.all([fresh.seed(), rival.seed()]);
|
|
26
|
+
expect.same([...a], [...b], 'two first asks at once keep one seed');
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
];
|
|
30
|
+
export const memoryScenes = (name, make, expect, half) => [
|
|
31
|
+
[
|
|
32
|
+
`[contract] ${name} fulfils Memory: it names its places, and forgets one whole`,
|
|
33
|
+
async () => {
|
|
34
|
+
const [memory, again] = make();
|
|
35
|
+
expect.same(await memory.places(), []);
|
|
36
|
+
await memory.write('aa', new Map([['01', bytes(1)]]));
|
|
37
|
+
await memory.write('bb', new Map([['01', bytes(2)], ['02', bytes(3)]]));
|
|
38
|
+
expect.same([...(await again.places())].sort(), ['aa', 'bb']);
|
|
39
|
+
await memory.forget('aa');
|
|
40
|
+
expect.same(await again.places(), ['bb']);
|
|
41
|
+
expect.same(plain(await again.read('aa')), []);
|
|
42
|
+
await memory.forget('aa');
|
|
43
|
+
await memory.write('bb', new Map([['01', null], ['02', null]]));
|
|
44
|
+
expect.same(await again.places(), [], 'a place holding nothing is no place');
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
...(half
|
|
48
|
+
? [
|
|
49
|
+
[
|
|
50
|
+
`[contract] ${name} fulfils Memory: a keep that stops partway is whole or is nothing`,
|
|
51
|
+
async () => {
|
|
52
|
+
const { cut, fresh } = half();
|
|
53
|
+
await cut.write('aa', new Map([['01', bytes(1)], ['02', bytes(2)]])).catch(() => undefined);
|
|
54
|
+
const before = plain(await fresh.read('aa'));
|
|
55
|
+
await cut.write('aa', new Map([['01', bytes(9)], ['02', null], ['03', bytes(3)]])).catch(() => undefined);
|
|
56
|
+
const after = JSON.stringify(plain(await fresh.read('aa')));
|
|
57
|
+
const whole = JSON.stringify(plain(new Map([['01', bytes(9)], ['03', bytes(3)]])));
|
|
58
|
+
expect.ok(after === JSON.stringify(before) || after === whole, `a keep cut short left ${after}`);
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
]
|
|
62
|
+
: []),
|
|
63
|
+
[
|
|
64
|
+
`[contract] ${name} fulfils Memory: a place read again holds what was kept, and nothing else`,
|
|
65
|
+
async () => {
|
|
66
|
+
const [memory, again] = make();
|
|
67
|
+
expect.ok(memory instanceof Memory);
|
|
68
|
+
expect.same(plain(await memory.read('aa')), []);
|
|
69
|
+
const written = bytes(1, 2, 3);
|
|
70
|
+
await memory.write('aa', new Map([['01', written], ['02', bytes(4)]]));
|
|
71
|
+
written[0] = 9;
|
|
72
|
+
await memory.write('bb', new Map([['01', bytes(5)]]));
|
|
73
|
+
await memory.write('aa', new Map([['02', null], ['03', bytes()], ['04', null]]));
|
|
74
|
+
const read = await again.read('aa');
|
|
75
|
+
expect.same(plain(read), [['01', [1, 2, 3]], ['03', []]]);
|
|
76
|
+
expect.ok(read.get('01') instanceof Uint8Array, 'an entry is bytes');
|
|
77
|
+
read.get('01')[0] = 7;
|
|
78
|
+
expect.same([...(await memory.read('aa')).get('01')], [1, 2, 3], 'an entry handed out is a copy');
|
|
79
|
+
expect.same(plain(await again.read('bb')), [['01', [5]]]);
|
|
80
|
+
expect.same(plain(await again.read('cc')), []);
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
];
|
|
84
|
+
export const entropyScenes = (name, make, expect) => [
|
|
85
|
+
[
|
|
86
|
+
`[contract] ${name} fulfils Entropy: bytes of the length asked, never the same twice`,
|
|
87
|
+
() => {
|
|
88
|
+
const entropy = make();
|
|
89
|
+
expect.ok(entropy instanceof Entropy);
|
|
90
|
+
expect.equal(entropy.draw(0).length, 0);
|
|
91
|
+
expect.equal(entropy.drawer(33).length, 33);
|
|
92
|
+
expect.ok([...entropy.draw(32)].join() !== [...entropy.draw(32)].join(), 'two draws differ');
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
];
|
|
97
|
+
// `make` gives a loader, a name it finds a module by, and one it finds
|
|
98
|
+
// nothing by.
|
|
99
|
+
export const loaderScenes = (name, make, expect) => [
|
|
100
|
+
[
|
|
101
|
+
`[contract] ${name} fulfils Loader: a module by its name, found again by the name it gives back, and nothing else`,
|
|
102
|
+
async () => {
|
|
103
|
+
const { loader, found, missing } = make();
|
|
104
|
+
expect.ok(loader instanceof Loader);
|
|
105
|
+
const loaded = await loader.load(found);
|
|
106
|
+
expect.ok(loaded !== null, `nothing found at ${found}`);
|
|
107
|
+
expect.equal(typeof loaded.module, 'string');
|
|
108
|
+
expect.equal(typeof loaded.version, 'string');
|
|
109
|
+
expect.ok(Array.isArray(loaded.classes) && loaded.classes.every((C) => typeof C === 'function'), 'the classes are classes');
|
|
110
|
+
const again = await loader.load(loaded.from);
|
|
111
|
+
expect.same(again && { module: again.module, version: again.version, from: again.from }, { module: loaded.module, version: loaded.version, from: loaded.from });
|
|
112
|
+
expect.equal(await loader.load(missing), null);
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const fail = (message, detail) => {
|
|
2
|
+
throw new Error(`${message ? `${message}: ` : ''}${detail}`);
|
|
3
|
+
};
|
|
4
|
+
export const expect = {
|
|
5
|
+
equal: (actual, expected, message) => {
|
|
6
|
+
if (!Object.is(actual, expected))
|
|
7
|
+
fail(message, `${String(actual)} is not ${String(expected)}`);
|
|
8
|
+
},
|
|
9
|
+
same: (actual, expected, message) => {
|
|
10
|
+
const [a, b] = [JSON.stringify(actual), JSON.stringify(expected)];
|
|
11
|
+
if (a !== b)
|
|
12
|
+
fail(message, `${a} is not ${b}`);
|
|
13
|
+
},
|
|
14
|
+
ok: (value, message) => {
|
|
15
|
+
if (!value)
|
|
16
|
+
fail(message, 'not true');
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// `nervur/proof`: the suites a body of a stranger's passes to be supported,
|
|
3
|
+
// the kit's own. Each is a list of scenes, a title and a function, run by
|
|
4
|
+
// whatever runner the stranger keeps, in the engine the body is for:
|
|
5
|
+
//
|
|
6
|
+
// for (const [title, scene] of memoryScenes('S3Memory', make, expect, half)) test(title, scene);
|
|
7
|
+
//
|
|
8
|
+
// `expect` throws on the first assertion that fails. The law's scenes stand
|
|
9
|
+
// harbors on a stranger's bodies and hold the unpacking law over them.
|
|
10
|
+
export { entropyScenes, custodyScenes, loaderScenes, memoryScenes } from './contracts.js';
|
|
11
|
+
export { expect } from './expect.js';
|
|
12
|
+
export { law, lawScenes } from './law.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Being, Faculty, type Asker, type JsonObject } from '../being/index.ts';
|
|
2
|
+
import { Memory, type Custody, type Module } from '../contract/index.ts';
|
|
3
|
+
import { type Harbor } from '../harbor/index.ts';
|
|
4
|
+
import { World } from '../pointer/index.ts';
|
|
5
|
+
import type { Expect } from './expect.ts';
|
|
6
|
+
export type Bodies = {
|
|
7
|
+
custody: Custody;
|
|
8
|
+
memory: Memory;
|
|
9
|
+
};
|
|
10
|
+
export type Terrain = {
|
|
11
|
+
name: string;
|
|
12
|
+
make(): Bodies;
|
|
13
|
+
again(bodies: Bodies): Bodies;
|
|
14
|
+
};
|
|
15
|
+
declare abstract class Echoes extends Faculty {
|
|
16
|
+
static kind: string;
|
|
17
|
+
}
|
|
18
|
+
declare class Echo extends Echoes {
|
|
19
|
+
static kind: string;
|
|
20
|
+
static asks: {
|
|
21
|
+
echo: {};
|
|
22
|
+
};
|
|
23
|
+
echo(args: JsonObject, asker: Asker): JsonObject;
|
|
24
|
+
}
|
|
25
|
+
declare class Shop extends Being {
|
|
26
|
+
static kind: string;
|
|
27
|
+
static cells: {
|
|
28
|
+
sold: string[];
|
|
29
|
+
};
|
|
30
|
+
static asks: {
|
|
31
|
+
buy: {};
|
|
32
|
+
};
|
|
33
|
+
buy(args: JsonObject, asker: Asker): JsonObject;
|
|
34
|
+
}
|
|
35
|
+
declare class Customer extends Being {
|
|
36
|
+
static kind: string;
|
|
37
|
+
static asks: {
|
|
38
|
+
join: {};
|
|
39
|
+
buy: {};
|
|
40
|
+
echo: {};
|
|
41
|
+
spoil: {};
|
|
42
|
+
};
|
|
43
|
+
spoil(): JsonObject;
|
|
44
|
+
join(args: JsonObject): Promise<JsonObject>;
|
|
45
|
+
buy(args: JsonObject): Promise<JsonObject>;
|
|
46
|
+
echo(args: JsonObject): Promise<JsonObject>;
|
|
47
|
+
}
|
|
48
|
+
export declare const classes: (typeof Echo | typeof Shop | typeof Customer)[];
|
|
49
|
+
export declare const law: Module;
|
|
50
|
+
export declare const standOne: (world: World, name: string, parts?: Partial<Bodies>) => Promise<Harbor>;
|
|
51
|
+
export declare const lawScenes: (t: Terrain, expect: Expect) => [string, () => Promise<void>][];
|
|
52
|
+
export {};
|