nervur 0.22.0 → 0.22.2-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.
- package/AUTHORING.md +741 -0
- package/KIT-SPEC.md +286 -0
- package/LICENSE +1 -1
- package/NOTICE +6 -2
- package/README.md +97 -424
- package/dist/app/app-ground.d.ts +18 -0
- package/dist/app/app-ground.js +25 -0
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +5 -0
- package/dist/app/native.d.ts +46 -0
- package/dist/app/native.js +101 -0
- package/dist/being/being.d.ts +229 -0
- package/dist/being/being.js +20 -0
- package/dist/being/covers.d.ts +9 -0
- package/dist/being/covers.js +21 -0
- package/dist/being/index.d.ts +3 -0
- package/dist/being/index.js +5 -0
- package/dist/being/need.d.ts +49 -0
- package/dist/being/need.js +33 -0
- package/dist/being/schema.d.ts +100 -0
- package/dist/being/schema.js +132 -0
- package/dist/being/table.d.ts +42 -0
- package/dist/being/table.js +288 -0
- package/dist/bench/bench-ground.d.ts +55 -0
- package/dist/bench/bench-ground.js +101 -0
- package/dist/bench/bench.d.ts +87 -0
- package/dist/bench/bench.js +260 -0
- package/dist/bench/fake-carry.d.ts +32 -0
- package/dist/bench/fake-carry.js +56 -0
- package/dist/bench/fake-clock.d.ts +17 -0
- package/dist/bench/fake-clock.js +34 -0
- package/dist/bench/fake-custody.d.ts +9 -0
- package/dist/bench/fake-custody.js +10 -0
- package/dist/bench/fake-faculty.d.ts +25 -0
- package/dist/bench/fake-faculty.js +48 -0
- package/dist/bench/fake-keys.d.ts +5 -0
- package/dist/bench/fake-keys.js +9 -0
- package/dist/bench/fake-memory.d.ts +14 -0
- package/dist/bench/fake-memory.js +46 -0
- package/dist/bench/fake-network.d.ts +57 -0
- package/dist/bench/fake-network.js +169 -0
- package/dist/bench/index.d.ts +11 -0
- package/dist/bench/index.js +13 -0
- package/dist/bench/seeded.d.ts +11 -0
- package/dist/bench/seeded.js +37 -0
- package/dist/bench/settle.d.ts +2 -0
- package/dist/bench/settle.js +49 -0
- package/dist/bench/stewards.d.ts +17 -0
- package/dist/bench/stewards.js +20 -0
- package/dist/bodies/class-list.d.ts +14 -0
- package/dist/bodies/class-list.js +33 -0
- package/dist/bodies/joined-carry.d.ts +23 -0
- package/dist/bodies/joined-carry.js +42 -0
- package/dist/bodies/noble-crypto.d.ts +22 -0
- package/dist/bodies/noble-crypto.js +88 -0
- package/dist/bodies/seed-keys.d.ts +10 -0
- package/dist/bodies/seed-keys.js +42 -0
- package/dist/bodies/strict-tools.d.ts +10 -0
- package/dist/bodies/strict-tools.js +306 -0
- package/dist/bodies/web-carry.d.ts +65 -0
- package/dist/bodies/web-carry.js +224 -0
- package/dist/bodies/web-clock.d.ts +12 -0
- package/dist/bodies/web-clock.js +33 -0
- package/dist/browser/browser-ground.d.ts +69 -0
- package/dist/browser/browser-ground.js +219 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +7 -0
- package/dist/browser/indexeddb-memory.d.ts +17 -0
- package/dist/browser/indexeddb-memory.js +97 -0
- package/dist/browser/locked-custody.d.ts +25 -0
- package/dist/browser/locked-custody.js +89 -0
- package/dist/browser/origin-classes.d.ts +7 -0
- package/dist/browser/origin-classes.js +31 -0
- package/dist/foundation.d.ts +150 -0
- package/dist/foundation.js +4 -0
- package/dist/ground/ground.d.ts +177 -0
- package/dist/ground/ground.js +363 -0
- package/dist/house/crossing.d.ts +37 -0
- package/dist/house/crossing.js +131 -0
- package/dist/house/house.d.ts +80 -0
- package/dist/house/house.js +1753 -0
- package/dist/house/rows-shape.d.ts +103 -0
- package/dist/house/rows-shape.js +12 -0
- package/dist/house/rows.d.ts +27 -0
- package/dist/house/rows.js +168 -0
- package/dist/index.d.ts +29 -1
- package/dist/index.js +25 -14
- package/dist/node/bridge.d.ts +17 -0
- package/dist/node/bridge.js +188 -0
- package/dist/node/cli.js +170 -0
- package/dist/node/custody.d.ts +20 -0
- package/dist/node/custody.js +34 -0
- package/dist/node/file-keys.d.ts +7 -0
- package/dist/node/file-keys.js +30 -0
- package/dist/node/file-memory.d.ts +13 -0
- package/dist/node/file-memory.js +91 -0
- package/dist/node/folder-classes.d.ts +5 -0
- package/dist/node/folder-classes.js +40 -0
- package/dist/node/gone.d.ts +2 -0
- package/dist/node/gone.js +7 -0
- package/dist/node/hand.d.ts +29 -0
- package/dist/node/hand.js +85 -0
- package/dist/node/held-keys.d.ts +14 -0
- package/dist/node/held-keys.js +36 -0
- package/dist/node/http.d.ts +11 -0
- package/dist/node/http.js +90 -0
- package/dist/node/index.d.ts +13 -0
- package/dist/node/index.js +15 -0
- package/dist/node/keychain-keys.d.ts +10 -0
- package/dist/node/keychain-keys.js +51 -0
- package/dist/node/ledger-memory.d.ts +17 -0
- package/dist/node/ledger-memory.js +151 -0
- package/dist/node/lock.d.ts +10 -0
- package/dist/node/lock.js +78 -0
- package/dist/node/node-ground.d.ts +33 -0
- package/dist/node/node-ground.js +147 -0
- package/dist/node/notify.d.ts +1 -0
- package/dist/node/notify.js +18 -0
- package/dist/node/tcp-carry.d.ts +39 -0
- package/dist/node/tcp-carry.js +216 -0
- package/dist/node/websocket.d.ts +6 -0
- package/dist/node/websocket.js +118 -0
- package/dist/quo/frame.d.ts +20 -0
- package/dist/quo/frame.js +48 -0
- package/dist/quo/room.d.ts +154 -0
- package/dist/quo/room.js +362 -0
- package/dist/serve/index.d.ts +27 -0
- package/dist/serve/index.js +55 -0
- package/package.json +49 -47
- package/dist/browser.d.ts +0 -1
- package/dist/browser.js +0 -8
- package/dist/cli.js +0 -16
- package/dist/core/being/being.d.ts +0 -17
- package/dist/core/being/being.js +0 -72
- package/dist/core/being/digest.d.ts +0 -3
- package/dist/core/being/digest.js +0 -17
- package/dist/core/being/faculty.d.ts +0 -10
- package/dist/core/being/faculty.js +0 -77
- package/dist/core/being/index.d.ts +0 -6
- package/dist/core/being/index.js +0 -8
- package/dist/core/being/kind.d.ts +0 -6
- package/dist/core/being/kind.js +0 -34
- package/dist/core/being/types.d.ts +0 -61
- package/dist/core/being/types.js +0 -4
- package/dist/core/being/words.d.ts +0 -13
- package/dist/core/being/words.js +0 -14
- package/dist/core/browser/index.d.ts +0 -37
- package/dist/core/browser/index.js +0 -196
- package/dist/core/browser/worker.d.ts +0 -11
- package/dist/core/browser/worker.js +0 -37
- package/dist/core/cli/command.d.ts +0 -13
- package/dist/core/cli/command.js +0 -330
- package/dist/core/cli/edge.d.ts +0 -7
- package/dist/core/cli/edge.js +0 -79
- package/dist/core/cli/harbor.d.ts +0 -25
- package/dist/core/cli/harbor.js +0 -115
- package/dist/core/contract/index.d.ts +0 -42
- package/dist/core/contract/index.js +0 -34
- package/dist/core/contract/link.d.ts +0 -4
- package/dist/core/contract/link.js +0 -45
- package/dist/core/crypto/aes.d.ts +0 -3
- package/dist/core/crypto/aes.js +0 -24
- package/dist/core/crypto/bytes.d.ts +0 -6
- package/dist/core/crypto/bytes.js +0 -33
- package/dist/core/crypto/ed25519.d.ts +0 -3
- package/dist/core/crypto/ed25519.js +0 -93
- package/dist/core/crypto/hash.d.ts +0 -2
- package/dist/core/crypto/hash.js +0 -11
- package/dist/core/crypto/index.d.ts +0 -7
- package/dist/core/crypto/index.js +0 -10
- package/dist/core/crypto/json.d.ts +0 -8
- package/dist/core/crypto/json.js +0 -250
- package/dist/core/crypto/mlkem.d.ts +0 -12
- package/dist/core/crypto/mlkem.js +0 -36
- package/dist/core/crypto/subtle.d.ts +0 -3
- package/dist/core/crypto/subtle.js +0 -11
- package/dist/core/crypto/x25519.d.ts +0 -2
- package/dist/core/crypto/x25519.js +0 -24
- package/dist/core/edge/index.d.ts +0 -164
- package/dist/core/edge/index.js +0 -739
- package/dist/core/edge/kit.d.ts +0 -2
- package/dist/core/edge/kit.js +0 -2
- package/dist/core/folder/index.d.ts +0 -18
- package/dist/core/folder/index.js +0 -130
- package/dist/core/git/http.d.ts +0 -6
- package/dist/core/git/http.js +0 -102
- package/dist/core/git/index.d.ts +0 -5
- package/dist/core/git/index.js +0 -10
- package/dist/core/git/object.d.ts +0 -40
- package/dist/core/git/object.js +0 -103
- package/dist/core/git/pack.d.ts +0 -9
- package/dist/core/git/pack.js +0 -171
- package/dist/core/git/store.d.ts +0 -23
- package/dist/core/git/store.js +0 -120
- package/dist/core/git/zlib.d.ts +0 -6
- package/dist/core/git/zlib.js +0 -210
- package/dist/core/harbor/carrying.d.ts +0 -51
- package/dist/core/harbor/carrying.js +0 -69
- package/dist/core/harbor/catalogue.d.ts +0 -33
- package/dist/core/harbor/catalogue.js +0 -365
- package/dist/core/harbor/dna.d.ts +0 -13
- package/dist/core/harbor/dna.js +0 -120
- package/dist/core/harbor/dock.d.ts +0 -62
- package/dist/core/harbor/dock.js +0 -381
- package/dist/core/harbor/harbor.d.ts +0 -16
- package/dist/core/harbor/harbor.js +0 -438
- package/dist/core/harbor/index.d.ts +0 -11
- package/dist/core/harbor/index.js +0 -14
- package/dist/core/harbor/memory.d.ts +0 -10
- package/dist/core/harbor/memory.js +0 -25
- package/dist/core/harbor/package.d.ts +0 -20
- package/dist/core/harbor/package.js +0 -172
- package/dist/core/harbor/probe.d.ts +0 -22
- package/dist/core/harbor/probe.js +0 -15
- package/dist/core/harbor/registry.d.ts +0 -15
- package/dist/core/harbor/registry.js +0 -110
- package/dist/core/harbor/root-line.d.ts +0 -7
- package/dist/core/harbor/root-line.js +0 -9
- package/dist/core/harbor/terrain.d.ts +0 -25
- package/dist/core/harbor/terrain.js +0 -28
- package/dist/core/http/index.d.ts +0 -2
- package/dist/core/http/index.js +0 -154
- package/dist/core/http/websocket.d.ts +0 -29
- package/dist/core/http/websocket.js +0 -133
- package/dist/core/line/answer.d.ts +0 -8
- package/dist/core/line/answer.js +0 -60
- package/dist/core/line/frame.d.ts +0 -29
- package/dist/core/line/frame.js +0 -77
- package/dist/core/line/ground.d.ts +0 -3
- package/dist/core/line/ground.js +0 -7
- package/dist/core/line/index.d.ts +0 -4
- package/dist/core/line/index.js +0 -8
- package/dist/core/line/web.d.ts +0 -12
- package/dist/core/line/web.js +0 -117
- package/dist/core/node/index.d.ts +0 -23
- package/dist/core/node/index.js +0 -124
- package/dist/core/pointer/bodies.d.ts +0 -31
- package/dist/core/pointer/bodies.js +0 -118
- package/dist/core/pointer/index.d.ts +0 -2
- package/dist/core/pointer/index.js +0 -4
- package/dist/core/pointer/world.d.ts +0 -45
- package/dist/core/pointer/world.js +0 -106
- package/dist/core/proof/contracts.d.ts +0 -19
- package/dist/core/proof/contracts.js +0 -116
- package/dist/core/proof/expect.d.ts +0 -6
- package/dist/core/proof/expect.js +0 -18
- package/dist/core/proof/index.d.ts +0 -3
- package/dist/core/proof/index.js +0 -14
- package/dist/core/proof/law.d.ts +0 -25
- package/dist/core/proof/law.js +0 -715
- package/dist/core/quo/address.d.ts +0 -19
- package/dist/core/quo/address.js +0 -84
- package/dist/core/quo/door.d.ts +0 -34
- package/dist/core/quo/door.js +0 -174
- package/dist/core/quo/index.d.ts +0 -9
- package/dist/core/quo/index.js +0 -12
- package/dist/core/quo/invitation.d.ts +0 -8
- package/dist/core/quo/invitation.js +0 -19
- package/dist/core/quo/keys.d.ts +0 -40
- package/dist/core/quo/keys.js +0 -79
- package/dist/core/quo/payload.d.ts +0 -15
- package/dist/core/quo/payload.js +0 -55
- package/dist/core/quo/relations.d.ts +0 -40
- package/dist/core/quo/relations.js +0 -33
- package/dist/core/quo/reply.d.ts +0 -13
- package/dist/core/quo/reply.js +0 -35
- package/dist/core/quo/seal.d.ts +0 -42
- package/dist/core/quo/seal.js +0 -78
- package/dist/core/quo/standing.d.ts +0 -39
- package/dist/core/quo/standing.js +0 -90
- package/dist/core/tcp/index.d.ts +0 -27
- package/dist/core/tcp/index.js +0 -209
- package/dist/core/ward/allowance.d.ts +0 -14
- package/dist/core/ward/allowance.js +0 -32
- package/dist/core/ward/cells.d.ts +0 -7
- package/dist/core/ward/cells.js +0 -74
- package/dist/core/ward/house.d.ts +0 -67
- package/dist/core/ward/house.js +0 -473
- package/dist/core/ward/index.d.ts +0 -5
- package/dist/core/ward/index.js +0 -7
- package/dist/core/ward/partition.d.ts +0 -50
- package/dist/core/ward/partition.js +0 -66
- package/dist/core/ward/stance.d.ts +0 -29
- package/dist/core/ward/stance.js +0 -186
- package/dist/core/ward/ward.d.ts +0 -44
- package/dist/core/ward/ward.js +0 -140
- package/dist/core.d.ts +0 -10
- package/dist/core.js +0 -19
- package/dist/defaults/dock/index.d.ts +0 -4
- package/dist/defaults/dock/index.js +0 -11
- package/dist/defaults/index.d.ts +0 -5
- package/dist/defaults/index.js +0 -10
- package/dist/defaults/memory/index.d.ts +0 -12
- package/dist/defaults/memory/index.js +0 -39
- package/dist/defaults/tcp/index.d.ts +0 -16
- package/dist/defaults/tcp/index.js +0 -82
- package/dist/defaults/ward/index.d.ts +0 -4
- package/dist/defaults/ward/index.js +0 -11
- package/dist/defaults/web/index.d.ts +0 -15
- package/dist/defaults/web/index.js +0 -90
- package/dist/edge.d.ts +0 -1
- package/dist/edge.js +0 -9
- package/dist/kit.d.ts +0 -4
- package/dist/kit.js +0 -12
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -8
- package/src/browser.ts +0 -10
- package/src/cli.ts +0 -15
- package/src/core/being/being.ts +0 -84
- package/src/core/being/digest.ts +0 -18
- package/src/core/being/faculty.ts +0 -76
- package/src/core/being/index.ts +0 -8
- package/src/core/being/kind.ts +0 -37
- package/src/core/being/types.ts +0 -74
- package/src/core/being/words.ts +0 -31
- package/src/core/browser/index.ts +0 -219
- package/src/core/browser/worker.ts +0 -63
- package/src/core/cli/command.ts +0 -311
- package/src/core/cli/edge.ts +0 -86
- package/src/core/cli/harbor.ts +0 -120
- package/src/core/contract/index.ts +0 -82
- package/src/core/contract/link.ts +0 -58
- package/src/core/crypto/aes.ts +0 -26
- package/src/core/crypto/bytes.ts +0 -37
- package/src/core/crypto/ed25519.ts +0 -92
- package/src/core/crypto/hash.ts +0 -14
- package/src/core/crypto/index.ts +0 -10
- package/src/core/crypto/json.ts +0 -241
- package/src/core/crypto/mlkem.ts +0 -38
- package/src/core/crypto/subtle.ts +0 -13
- package/src/core/crypto/x25519.ts +0 -23
- package/src/core/edge/index.ts +0 -865
- package/src/core/folder/index.ts +0 -137
- package/src/core/git/http.ts +0 -103
- package/src/core/git/index.ts +0 -10
- package/src/core/git/object.ts +0 -116
- package/src/core/git/pack.ts +0 -147
- package/src/core/git/store.ts +0 -122
- package/src/core/git/zlib.ts +0 -197
- package/src/core/harbor/carrying.ts +0 -124
- package/src/core/harbor/catalogue.ts +0 -386
- package/src/core/harbor/dna.ts +0 -116
- package/src/core/harbor/dock.ts +0 -420
- package/src/core/harbor/harbor.ts +0 -433
- package/src/core/harbor/index.ts +0 -14
- package/src/core/harbor/memory.ts +0 -31
- package/src/core/harbor/package.ts +0 -195
- package/src/core/harbor/probe.ts +0 -39
- package/src/core/harbor/registry.ts +0 -107
- package/src/core/harbor/root-line.ts +0 -19
- package/src/core/harbor/terrain.ts +0 -54
- package/src/core/http/index.ts +0 -147
- package/src/core/http/websocket.ts +0 -124
- package/src/core/line/answer.ts +0 -60
- package/src/core/line/frame.ts +0 -83
- package/src/core/line/ground.ts +0 -18
- package/src/core/line/index.ts +0 -8
- package/src/core/line/web.ts +0 -123
- package/src/core/node/index.ts +0 -139
- package/src/core/pointer/bodies.ts +0 -122
- package/src/core/pointer/index.ts +0 -4
- package/src/core/pointer/world.ts +0 -134
- package/src/core/proof/contracts.ts +0 -134
- package/src/core/proof/expect.ts +0 -25
- package/src/core/proof/index.ts +0 -14
- package/src/core/proof/law.ts +0 -742
- package/src/core/quo/address.ts +0 -87
- package/src/core/quo/door.ts +0 -180
- package/src/core/quo/index.ts +0 -12
- package/src/core/quo/invitation.ts +0 -19
- package/src/core/quo/keys.ts +0 -91
- package/src/core/quo/payload.ts +0 -61
- package/src/core/quo/relations.ts +0 -62
- package/src/core/quo/reply.ts +0 -38
- package/src/core/quo/seal.ts +0 -101
- package/src/core/quo/standing.ts +0 -111
- package/src/core/stand/main.ts +0 -20
- package/src/core/stand/stand.ts +0 -259
- package/src/core/tcp/index.ts +0 -227
- package/src/core/ward/allowance.ts +0 -47
- package/src/core/ward/cells.ts +0 -77
- package/src/core/ward/house.ts +0 -524
- package/src/core/ward/index.ts +0 -7
- package/src/core/ward/partition.ts +0 -117
- package/src/core/ward/stance.ts +0 -207
- package/src/core/ward/ward.ts +0 -166
- package/src/core.ts +0 -55
- package/src/defaults/dock/index.ts +0 -12
- package/src/defaults/index.ts +0 -10
- package/src/defaults/memory/index.ts +0 -50
- package/src/defaults/tcp/index.ts +0 -89
- package/src/defaults/ward/index.ts +0 -12
- package/src/defaults/web/index.ts +0 -97
- package/src/edge.ts +0 -11
- package/src/index.ts +0 -18
- package/src/kit.ts +0 -15
- package/src/node.ts +0 -10
- /package/dist/{cli.d.ts → node/cli.d.ts} +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Json } from '../being/being.ts';
|
|
2
|
+
import type { Memory } from '../foundation.ts';
|
|
3
|
+
import { Ground, type Bodies, type Custody, type Faculty } from '../ground/ground.ts';
|
|
4
|
+
import type { Answer } from '../house/rows-shape.ts';
|
|
5
|
+
import { type Load } from './origin-classes.ts';
|
|
6
|
+
/** What a page hands its ground: the faculties it makes, by name, and any custom body. */
|
|
7
|
+
export interface BrowserRecipe {
|
|
8
|
+
readonly faculties?: () => Readonly<Record<string, Faculty | Promise<Faculty>>>;
|
|
9
|
+
readonly bodies?: Partial<Bodies>;
|
|
10
|
+
}
|
|
11
|
+
/** What the ground takes of its engine. Each is the page's own where omitted. */
|
|
12
|
+
export interface BrowserPlatform {
|
|
13
|
+
readonly locks: Pick<LockManager, 'request'>;
|
|
14
|
+
readonly channel: (name: string) => Pick<BroadcastChannel, 'postMessage' | 'close'> & {
|
|
15
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
16
|
+
};
|
|
17
|
+
readonly custody: (name: string) => Promise<Custody>;
|
|
18
|
+
readonly memory: (name: string) => Promise<Memory>;
|
|
19
|
+
/** Where its code stands; its classes load from under it alone. */
|
|
20
|
+
readonly origin: string;
|
|
21
|
+
/** Asks the browser to keep its storage, and answers whether it will. */
|
|
22
|
+
readonly persist: () => Promise<boolean>;
|
|
23
|
+
/** Loads a module of classes by URL: `import()`, or in a service worker a map of the worker's own imports. */
|
|
24
|
+
readonly load: Load;
|
|
25
|
+
}
|
|
26
|
+
export interface BrowserGroundOptions {
|
|
27
|
+
/** The ground's name on its origin, which names its lock, its channel and its storage. `nervur` where omitted. */
|
|
28
|
+
readonly name?: string;
|
|
29
|
+
readonly recipe?: BrowserRecipe;
|
|
30
|
+
readonly platform?: Partial<BrowserPlatform>;
|
|
31
|
+
/** The ground's bound on every ask, in milliseconds. */
|
|
32
|
+
readonly wait?: number;
|
|
33
|
+
/** Whether it dials private and loopback addresses, as a page on localhost does. */
|
|
34
|
+
readonly allowPrivate?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** One request of the hand, as every terrain serves it. */
|
|
37
|
+
export interface BrowserHandRequest {
|
|
38
|
+
readonly describe?: true;
|
|
39
|
+
readonly faculty?: string;
|
|
40
|
+
readonly house?: string;
|
|
41
|
+
readonly id?: string;
|
|
42
|
+
readonly method?: string;
|
|
43
|
+
readonly args?: Json;
|
|
44
|
+
readonly after?: Answer;
|
|
45
|
+
}
|
|
46
|
+
export declare class BrowserGround {
|
|
47
|
+
#private;
|
|
48
|
+
private constructor();
|
|
49
|
+
/**
|
|
50
|
+
* The origin's ground, joined: this page runs it where it takes the lock,
|
|
51
|
+
* and reaches the page that runs it where another holds the lock.
|
|
52
|
+
*/
|
|
53
|
+
static open(options?: BrowserGroundOptions): Promise<BrowserGround>;
|
|
54
|
+
/** Whether this page runs the ground now. */
|
|
55
|
+
get leading(): boolean;
|
|
56
|
+
/** The ground, where this page runs it. */
|
|
57
|
+
get ground(): Ground | undefined;
|
|
58
|
+
/** Resolves once this page runs the ground, and rejects where its boot failed. */
|
|
59
|
+
led(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* The hand: `describe`, a faculty's method, or an ask of a being in a
|
|
62
|
+
* named house, answered by whichever page runs the ground.
|
|
63
|
+
*/
|
|
64
|
+
hand(request: BrowserHandRequest): Promise<Answer | {
|
|
65
|
+
readonly describe: Json;
|
|
66
|
+
}>;
|
|
67
|
+
/** This page leaves: its ground closes where it ran one, and the lock passes to the next page. */
|
|
68
|
+
close(): Promise<void>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
import { JoinedCarry } from '../bodies/joined-carry.js';
|
|
10
|
+
import { WebCarry } from '../bodies/web-carry.js';
|
|
11
|
+
import { Ground } from '../ground/ground.js';
|
|
12
|
+
import { IndexedDbMemory } from './indexeddb-memory.js';
|
|
13
|
+
import { LockedCustody } from './locked-custody.js';
|
|
14
|
+
import { OriginClasses } from './origin-classes.js';
|
|
15
|
+
const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
16
|
+
const random = () => Array.from(crypto.getRandomValues(new Uint8Array(8)), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
17
|
+
// Bodies a recipe adds beside the ground's own, never in their place.
|
|
18
|
+
const joined = (own, added = {}) => {
|
|
19
|
+
for (const name of Object.keys(added))
|
|
20
|
+
if (name in own)
|
|
21
|
+
throw new TypeError(`the recipe names a body ${name}, which is the ground's own`);
|
|
22
|
+
return { ...own, ...added };
|
|
23
|
+
};
|
|
24
|
+
const defaults = (name) => ({
|
|
25
|
+
locks: navigator.locks,
|
|
26
|
+
channel: (channel) => new BroadcastChannel(channel),
|
|
27
|
+
custody: (custody) => LockedCustody.open(custody),
|
|
28
|
+
memory: (memory) => IndexedDbMemory.open(memory),
|
|
29
|
+
origin: globalThis.location?.origin ?? `nervur:${name}`,
|
|
30
|
+
persist: async () => (await navigator.storage?.persist?.()) ?? false,
|
|
31
|
+
load: (href) => import(__rewriteRelativeImportExtension(href)),
|
|
32
|
+
});
|
|
33
|
+
export class BrowserGround {
|
|
34
|
+
#name;
|
|
35
|
+
#platform;
|
|
36
|
+
#options;
|
|
37
|
+
#self = random();
|
|
38
|
+
#channel;
|
|
39
|
+
#abort = new AbortController();
|
|
40
|
+
// Asks sent to another page's ground, by id, with the page they went to.
|
|
41
|
+
#pending = new Map();
|
|
42
|
+
#ground;
|
|
43
|
+
#persisted = false;
|
|
44
|
+
#leader;
|
|
45
|
+
#waiting = [];
|
|
46
|
+
#release;
|
|
47
|
+
#leading;
|
|
48
|
+
#led;
|
|
49
|
+
#failed;
|
|
50
|
+
#closed = false;
|
|
51
|
+
constructor(name, platform, options) {
|
|
52
|
+
this.#name = name;
|
|
53
|
+
this.#platform = platform;
|
|
54
|
+
this.#options = options;
|
|
55
|
+
this.#channel = platform.channel(`${name}-hand`);
|
|
56
|
+
this.#leading = new Promise((resolve, reject) => {
|
|
57
|
+
this.#led = resolve;
|
|
58
|
+
this.#failed = reject;
|
|
59
|
+
});
|
|
60
|
+
this.#leading.catch(() => undefined);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The origin's ground, joined: this page runs it where it takes the lock,
|
|
64
|
+
* and reaches the page that runs it where another holds the lock.
|
|
65
|
+
*/
|
|
66
|
+
static async open(options = {}) {
|
|
67
|
+
const name = options.name ?? 'nervur';
|
|
68
|
+
if (!NAME.test(name))
|
|
69
|
+
throw new TypeError(`no ground is named ${name}`);
|
|
70
|
+
const platform = { ...defaults(name), ...options.platform };
|
|
71
|
+
const joining = new BrowserGround(name, platform, options);
|
|
72
|
+
joining.#listen();
|
|
73
|
+
joining.#queue();
|
|
74
|
+
joining.#say({ kind: 'who' });
|
|
75
|
+
return joining;
|
|
76
|
+
}
|
|
77
|
+
/** Whether this page runs the ground now. */
|
|
78
|
+
get leading() {
|
|
79
|
+
return this.#ground !== undefined;
|
|
80
|
+
}
|
|
81
|
+
/** The ground, where this page runs it. */
|
|
82
|
+
get ground() {
|
|
83
|
+
return this.#ground;
|
|
84
|
+
}
|
|
85
|
+
/** Resolves once this page runs the ground, and rejects where its boot failed. */
|
|
86
|
+
led() {
|
|
87
|
+
return this.#leading;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The hand: `describe`, a faculty's method, or an ask of a being in a
|
|
91
|
+
* named house, answered by whichever page runs the ground.
|
|
92
|
+
*/
|
|
93
|
+
async hand(request) {
|
|
94
|
+
if (this.#closed)
|
|
95
|
+
return { error: { message: 'the ground is closed here' } };
|
|
96
|
+
if (this.#ground !== undefined)
|
|
97
|
+
return this.#local(request);
|
|
98
|
+
if (this.#leader === undefined)
|
|
99
|
+
await new Promise((resolve) => this.#waiting.push(resolve));
|
|
100
|
+
if (this.#ground !== undefined)
|
|
101
|
+
return this.#local(request);
|
|
102
|
+
const id = random();
|
|
103
|
+
const to = this.#leader;
|
|
104
|
+
return new Promise((settle) => {
|
|
105
|
+
this.#pending.set(id, { to, settle });
|
|
106
|
+
this.#say({ kind: 'ask', id, to, request });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async #local(request) {
|
|
110
|
+
const answer = await this.#ground.hand(request);
|
|
111
|
+
if (request.describe === true && 'result' in answer)
|
|
112
|
+
return { result: { ...answer.result, persisted: this.#persisted } };
|
|
113
|
+
return answer;
|
|
114
|
+
}
|
|
115
|
+
#say(said) {
|
|
116
|
+
this.#channel.postMessage(said);
|
|
117
|
+
}
|
|
118
|
+
#listen() {
|
|
119
|
+
this.#channel.onmessage = (event) => {
|
|
120
|
+
const said = event.data;
|
|
121
|
+
if (said.kind === 'who' && this.#ground !== undefined)
|
|
122
|
+
this.#say({ kind: 'up', leader: this.#self });
|
|
123
|
+
else if (said.kind === 'up')
|
|
124
|
+
this.#heard(said.leader);
|
|
125
|
+
else if (said.kind === 'ask' && said.to === this.#self)
|
|
126
|
+
void this.#answer(said.id, said.request);
|
|
127
|
+
else if (said.kind === 'answer') {
|
|
128
|
+
const pending = this.#pending.get(said.id);
|
|
129
|
+
this.#pending.delete(said.id);
|
|
130
|
+
pending?.settle(said.answer);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async #answer(id, request) {
|
|
135
|
+
const answer = this.#ground === undefined ? { error: { message: 'the ground moved to another page; ask again' } } : await this.#local(request);
|
|
136
|
+
this.#say({ kind: 'answer', id, answer });
|
|
137
|
+
}
|
|
138
|
+
// A new page runs the ground: asks sent to the one before may never be answered, so they fail, and whoever waited goes on.
|
|
139
|
+
#heard(leader) {
|
|
140
|
+
if (leader !== this.#leader)
|
|
141
|
+
this.#orphan((to) => to !== leader);
|
|
142
|
+
this.#leader = leader;
|
|
143
|
+
const waiting = this.#waiting;
|
|
144
|
+
this.#waiting = [];
|
|
145
|
+
for (const resolve of waiting)
|
|
146
|
+
resolve();
|
|
147
|
+
}
|
|
148
|
+
#orphan(lost) {
|
|
149
|
+
for (const [id, pending] of this.#pending) {
|
|
150
|
+
if (!lost(pending.to))
|
|
151
|
+
continue;
|
|
152
|
+
this.#pending.delete(id);
|
|
153
|
+
pending.settle({ error: { message: 'the page that ran the ground closed; ask again' } });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// The lock, asked once and held until this page closes.
|
|
157
|
+
#queue() {
|
|
158
|
+
this.#platform.locks
|
|
159
|
+
.request(`${this.#name}-ground`, { signal: this.#abort.signal }, async () => {
|
|
160
|
+
try {
|
|
161
|
+
this.#ground = await this.#boot();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
this.#failed(error);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.#heard(this.#self);
|
|
168
|
+
this.#say({ kind: 'up', leader: this.#self });
|
|
169
|
+
this.#led();
|
|
170
|
+
await new Promise((resolve) => {
|
|
171
|
+
this.#release = resolve;
|
|
172
|
+
});
|
|
173
|
+
})
|
|
174
|
+
.catch(() => undefined);
|
|
175
|
+
}
|
|
176
|
+
// The boot: its own custody and memory, the recipe's faculties, and the record's houses.
|
|
177
|
+
async #boot() {
|
|
178
|
+
const name = this.#name;
|
|
179
|
+
const platform = this.#platform;
|
|
180
|
+
const recipe = this.#options.recipe ?? {};
|
|
181
|
+
const custody = await platform.custody(`${name}-custody`);
|
|
182
|
+
const memory = await platform.memory(`${name}-ground`);
|
|
183
|
+
this.#persisted = await platform.persist().catch(() => false);
|
|
184
|
+
// It only dials, so it writes no address into an invitation.
|
|
185
|
+
const web = new WebCarry({ allowPrivate: this.#options.allowPrivate === true });
|
|
186
|
+
const carry = new JoinedCarry({ https: web, http: web });
|
|
187
|
+
const faculties = {};
|
|
188
|
+
for (const [faculty, made] of Object.entries(recipe.faculties?.() ?? {}))
|
|
189
|
+
faculties[faculty] = await made;
|
|
190
|
+
const bodies = {
|
|
191
|
+
memory: joined({ indexeddb: ({ house }) => platform.memory(`${name}-house-${house}`) }, recipe.bodies?.memory),
|
|
192
|
+
classes: joined({
|
|
193
|
+
origin: ({ args }) => {
|
|
194
|
+
if (typeof args.at !== 'string')
|
|
195
|
+
throw new Error('the origin body names its module in at');
|
|
196
|
+
return OriginClasses.open(args.at, platform.origin, platform.load);
|
|
197
|
+
},
|
|
198
|
+
}, recipe.bodies?.classes),
|
|
199
|
+
};
|
|
200
|
+
const wait = this.#options.wait;
|
|
201
|
+
return Ground.open({ custody, memory, carry, bodies, faculties, ...(wait === undefined ? {} : { wait }) });
|
|
202
|
+
}
|
|
203
|
+
/** This page leaves: its ground closes where it ran one, and the lock passes to the next page. */
|
|
204
|
+
async close() {
|
|
205
|
+
if (this.#closed)
|
|
206
|
+
return;
|
|
207
|
+
this.#closed = true;
|
|
208
|
+
this.#abort.abort();
|
|
209
|
+
const ground = this.#ground;
|
|
210
|
+
this.#ground = undefined;
|
|
211
|
+
await ground?.close();
|
|
212
|
+
this.#release?.();
|
|
213
|
+
this.#orphan(() => true);
|
|
214
|
+
for (const resolve of this.#waiting)
|
|
215
|
+
resolve();
|
|
216
|
+
this.#waiting = [];
|
|
217
|
+
this.#channel.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { BrowserGround, type BrowserGroundOptions, type BrowserHandRequest, type BrowserPlatform, type BrowserRecipe } from './browser-ground.ts';
|
|
2
|
+
export { IndexedDbMemory } from './indexeddb-memory.ts';
|
|
3
|
+
export { IndexedDbShelf, LockedCustody, type Shelf } from './locked-custody.ts';
|
|
4
|
+
export { OriginClasses, type Load } from './origin-classes.ts';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// `nervur/browser`: the ground of a page and its service worker, and the
|
|
3
|
+
// bodies it passes, for whoever writes a ground of their own.
|
|
4
|
+
export { BrowserGround } from './browser-ground.js';
|
|
5
|
+
export { IndexedDbMemory } from './indexeddb-memory.js';
|
|
6
|
+
export { IndexedDbShelf, LockedCustody } from './locked-custody.js';
|
|
7
|
+
export { OriginClasses } from './origin-classes.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Memory, PlaceRead } from '../foundation.ts';
|
|
2
|
+
export declare class IndexedDbMemory implements Memory {
|
|
3
|
+
#private;
|
|
4
|
+
private constructor();
|
|
5
|
+
/** The memory named `name`, its database made on first use. `factory` is the engine's `indexedDB` where omitted. */
|
|
6
|
+
static open(name: string, factory?: IDBFactory): Promise<IndexedDbMemory>;
|
|
7
|
+
read({ place }: {
|
|
8
|
+
place: string;
|
|
9
|
+
}): Promise<PlaceRead>;
|
|
10
|
+
list(): Promise<readonly string[]>;
|
|
11
|
+
write({ writes, expect, }: {
|
|
12
|
+
writes: Readonly<Record<string, Readonly<Record<string, Uint8Array | null>>>>;
|
|
13
|
+
expect: Readonly<Record<string, string | null>>;
|
|
14
|
+
}): Promise<Readonly<Record<string, string | null>> | null>;
|
|
15
|
+
/** The database let go. */
|
|
16
|
+
close(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const PLACES = 'places';
|
|
2
|
+
const COUNT = 'count';
|
|
3
|
+
const done = (request) => new Promise((resolve, reject) => {
|
|
4
|
+
request.onsuccess = () => resolve(request.result);
|
|
5
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB refused the request'));
|
|
6
|
+
});
|
|
7
|
+
const copied = (entries) => Object.fromEntries(Object.entries(entries).map(([name, bytes]) => [name, bytes.slice()]));
|
|
8
|
+
export class IndexedDbMemory {
|
|
9
|
+
#db;
|
|
10
|
+
constructor(db) {
|
|
11
|
+
this.#db = db;
|
|
12
|
+
}
|
|
13
|
+
/** The memory named `name`, its database made on first use. `factory` is the engine's `indexedDB` where omitted. */
|
|
14
|
+
static async open(name, factory = indexedDB) {
|
|
15
|
+
const opening = factory.open(name, 1);
|
|
16
|
+
opening.onupgradeneeded = () => {
|
|
17
|
+
opening.result.createObjectStore(PLACES);
|
|
18
|
+
opening.result.createObjectStore(COUNT);
|
|
19
|
+
};
|
|
20
|
+
return new IndexedDbMemory(await done(opening));
|
|
21
|
+
}
|
|
22
|
+
async read({ place }) {
|
|
23
|
+
const held = (await done(this.#db.transaction(PLACES, 'readonly').objectStore(PLACES).get(place)));
|
|
24
|
+
if (held === undefined)
|
|
25
|
+
return { entries: {}, version: null };
|
|
26
|
+
return { entries: copied(held.entries), version: String(held.version) };
|
|
27
|
+
}
|
|
28
|
+
async list() {
|
|
29
|
+
const keys = await done(this.#db.transaction(PLACES, 'readonly').objectStore(PLACES).getAllKeys());
|
|
30
|
+
return keys.map(String);
|
|
31
|
+
}
|
|
32
|
+
write({ writes, expect, }) {
|
|
33
|
+
const places = [...new Set([...Object.keys(writes), ...Object.keys(expect)])];
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const transaction = this.#db.transaction([PLACES, COUNT], 'readwrite');
|
|
36
|
+
const store = transaction.objectStore(PLACES);
|
|
37
|
+
const count = transaction.objectStore(COUNT);
|
|
38
|
+
const held = new Map();
|
|
39
|
+
let versions = null;
|
|
40
|
+
let refused = false;
|
|
41
|
+
transaction.oncomplete = () => resolve(versions);
|
|
42
|
+
transaction.onabort = () => (refused ? resolve(null) : reject(transaction.error ?? new Error('IndexedDB aborted the write')));
|
|
43
|
+
// Every place and the counter are read inside the transaction, and the writes follow in the same one.
|
|
44
|
+
const counted = count.get(COUNT);
|
|
45
|
+
const land = () => {
|
|
46
|
+
if (held.size < places.length || counted.readyState !== 'done')
|
|
47
|
+
return;
|
|
48
|
+
for (const name of places) {
|
|
49
|
+
const version = held.get(name) === undefined ? null : String(held.get(name).version);
|
|
50
|
+
if (!(name in expect) || expect[name] !== version) {
|
|
51
|
+
refused = true;
|
|
52
|
+
transaction.abort();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let next = counted.result ?? 0;
|
|
57
|
+
const landed = {};
|
|
58
|
+
for (const [name, entries] of Object.entries(writes)) {
|
|
59
|
+
const kept = { ...(held.get(name)?.entries ?? {}) };
|
|
60
|
+
for (const [entry, bytes] of Object.entries(entries)) {
|
|
61
|
+
if (bytes === null)
|
|
62
|
+
delete kept[entry];
|
|
63
|
+
else
|
|
64
|
+
kept[entry] = bytes.slice();
|
|
65
|
+
}
|
|
66
|
+
next += 1;
|
|
67
|
+
if (Object.keys(kept).length === 0) {
|
|
68
|
+
store.delete(name);
|
|
69
|
+
landed[name] = null;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
store.put({ entries: kept, version: next }, name);
|
|
73
|
+
landed[name] = String(next);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
count.put(next, COUNT);
|
|
77
|
+
versions = landed;
|
|
78
|
+
};
|
|
79
|
+
counted.onsuccess = land;
|
|
80
|
+
for (const place of places) {
|
|
81
|
+
const request = store.get(place);
|
|
82
|
+
request.onsuccess = () => {
|
|
83
|
+
held.set(place, request.result);
|
|
84
|
+
land();
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (places.length === 0)
|
|
88
|
+
counted.onsuccess = () => {
|
|
89
|
+
versions = {};
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** The database let go. */
|
|
94
|
+
close() {
|
|
95
|
+
this.#db.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Keys } from '../foundation.ts';
|
|
2
|
+
import type { Custody } from '../ground/ground.ts';
|
|
3
|
+
/** Where custody keeps its key and its sealed seeds: values kept as they are. */
|
|
4
|
+
export interface Shelf {
|
|
5
|
+
get(key: string): Promise<unknown>;
|
|
6
|
+
set(key: string, value: unknown): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/** A shelf in IndexedDB, which keeps a `CryptoKey` whole. */
|
|
9
|
+
export declare class IndexedDbShelf implements Shelf {
|
|
10
|
+
#private;
|
|
11
|
+
private constructor();
|
|
12
|
+
static open(name: string, factory?: IDBFactory): Promise<IndexedDbShelf>;
|
|
13
|
+
get(key: string): Promise<unknown>;
|
|
14
|
+
set(key: string, value: unknown): Promise<void>;
|
|
15
|
+
close(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare class LockedCustody implements Custody {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(shelf: Shelf, subtle?: SubtleCrypto);
|
|
20
|
+
/** The custody of the origin, on a shelf in IndexedDB named `name`. */
|
|
21
|
+
static open(name?: string, factory?: IDBFactory): Promise<LockedCustody>;
|
|
22
|
+
keys({ house }: {
|
|
23
|
+
house: string;
|
|
24
|
+
}): Promise<Keys>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A BrowserGround's custody: one seed for each house, sealed with AES-GCM
|
|
3
|
+
// under a key the browser keeps and never hands out. The key is made once,
|
|
4
|
+
// unextractable, and kept on a shelf beside the sealed seeds, since
|
|
5
|
+
// IndexedDB keeps a key as it is and never as bytes. A script on the
|
|
6
|
+
// origin may use the key and can never copy it: a lock, not a wall.
|
|
7
|
+
import { SeedKeys } from '../bodies/seed-keys.js';
|
|
8
|
+
const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
9
|
+
const KEY = 'key';
|
|
10
|
+
const STORE = 'shelf';
|
|
11
|
+
const done = (request) => new Promise((resolve, reject) => {
|
|
12
|
+
request.onsuccess = () => resolve(request.result);
|
|
13
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB refused the request'));
|
|
14
|
+
});
|
|
15
|
+
/** A shelf in IndexedDB, which keeps a `CryptoKey` whole. */
|
|
16
|
+
export class IndexedDbShelf {
|
|
17
|
+
#db;
|
|
18
|
+
constructor(db) {
|
|
19
|
+
this.#db = db;
|
|
20
|
+
}
|
|
21
|
+
static async open(name, factory = indexedDB) {
|
|
22
|
+
const opening = factory.open(name, 1);
|
|
23
|
+
opening.onupgradeneeded = () => {
|
|
24
|
+
opening.result.createObjectStore(STORE);
|
|
25
|
+
};
|
|
26
|
+
return new IndexedDbShelf(await done(opening));
|
|
27
|
+
}
|
|
28
|
+
get(key) {
|
|
29
|
+
return done(this.#db.transaction(STORE, 'readonly').objectStore(STORE).get(key));
|
|
30
|
+
}
|
|
31
|
+
async set(key, value) {
|
|
32
|
+
const transaction = this.#db.transaction(STORE, 'readwrite');
|
|
33
|
+
transaction.objectStore(STORE).put(value, key);
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
transaction.oncomplete = () => resolve();
|
|
36
|
+
transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB aborted the write'));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
close() {
|
|
40
|
+
this.#db.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
44
|
+
export class LockedCustody {
|
|
45
|
+
#shelf;
|
|
46
|
+
#subtle;
|
|
47
|
+
// One seed is drawn at a time, so two first uses of a house never draw two.
|
|
48
|
+
#turn = Promise.resolve();
|
|
49
|
+
constructor(shelf, subtle = crypto.subtle) {
|
|
50
|
+
this.#shelf = shelf;
|
|
51
|
+
this.#subtle = subtle;
|
|
52
|
+
}
|
|
53
|
+
/** The custody of the origin, on a shelf in IndexedDB named `name`. */
|
|
54
|
+
static async open(name = 'nervur-custody', factory = indexedDB) {
|
|
55
|
+
return new LockedCustody(await IndexedDbShelf.open(name, factory));
|
|
56
|
+
}
|
|
57
|
+
keys({ house }) {
|
|
58
|
+
if (!NAME.test(house))
|
|
59
|
+
return Promise.reject(new TypeError(`no house is named ${house}`));
|
|
60
|
+
const turn = this.#turn.then(() => this.#keys(house));
|
|
61
|
+
this.#turn = turn.catch(() => undefined);
|
|
62
|
+
return turn;
|
|
63
|
+
}
|
|
64
|
+
async #keys(house) {
|
|
65
|
+
const key = await this.#key();
|
|
66
|
+
// The house's name is sealed in, so one house's seed never opens as another's.
|
|
67
|
+
const additionalData = new TextEncoder().encode(house);
|
|
68
|
+
let sealed = (await this.#shelf.get(`seed:${house}`));
|
|
69
|
+
if (sealed === undefined) {
|
|
70
|
+
const seed = crypto.getRandomValues(new Uint8Array(32));
|
|
71
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
72
|
+
sealed = { iv, data: new Uint8Array(await this.#subtle.encrypt({ name: 'AES-GCM', iv, additionalData }, key, seed)) };
|
|
73
|
+
seed.fill(0);
|
|
74
|
+
await this.#shelf.set(`seed:${house}`, sealed);
|
|
75
|
+
}
|
|
76
|
+
const seed = new Uint8Array(await this.#subtle.decrypt({ name: 'AES-GCM', iv: sealed.iv, additionalData }, key, sealed.data));
|
|
77
|
+
const keys = new SeedKeys(hex(seed));
|
|
78
|
+
seed.fill(0);
|
|
79
|
+
return keys;
|
|
80
|
+
}
|
|
81
|
+
async #key() {
|
|
82
|
+
const kept = (await this.#shelf.get(KEY));
|
|
83
|
+
if (kept !== undefined)
|
|
84
|
+
return kept;
|
|
85
|
+
const made = await this.#subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
86
|
+
await this.#shelf.set(KEY, made);
|
|
87
|
+
return made;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Classes } from '../foundation.ts';
|
|
2
|
+
/** What loads a module by its URL: the engine's `import()` where none is handed. */
|
|
3
|
+
export type Load = (href: string) => Promise<unknown>;
|
|
4
|
+
export declare const OriginClasses: Readonly<{
|
|
5
|
+
/** The classes of the module at `at`, resolved against `origin`, loaded once. */
|
|
6
|
+
open(at: string, origin: string, load?: Load): Promise<Classes>;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
10
|
+
// Classes from the ground's own origin. A module at a path under the
|
|
11
|
+
// origin exports `steward`, and `public` and `beings` where it has them, as
|
|
12
|
+
// `ClassList` takes them. A path that resolves off the origin is refused,
|
|
13
|
+
// as a folder of code on a NodeGround stays inside its folder. A service
|
|
14
|
+
// worker may not `import()`, so there the worker's own build hands a
|
|
15
|
+
// loader that answers the same paths from modules it imported itself.
|
|
16
|
+
import { ClassList } from '../bodies/class-list.js';
|
|
17
|
+
export const OriginClasses = Object.freeze({
|
|
18
|
+
/** The classes of the module at `at`, resolved against `origin`, loaded once. */
|
|
19
|
+
async open(at, origin, load = (href) => import(__rewriteRelativeImportExtension(href))) {
|
|
20
|
+
const base = new URL(origin.endsWith('/') ? origin : `${origin}/`);
|
|
21
|
+
const url = new URL(at, base);
|
|
22
|
+
if (url.origin !== base.origin || !url.href.startsWith(base.href))
|
|
23
|
+
throw new Error(`the code at ${at} stands off ${base.href}`);
|
|
24
|
+
const module = (await load(url.href));
|
|
25
|
+
if (module.steward === undefined)
|
|
26
|
+
throw new Error(`${url.href} exports no steward`);
|
|
27
|
+
if (module.beings !== undefined && !Array.isArray(module.beings))
|
|
28
|
+
throw new Error(`${url.href} exports beings that are not a list`);
|
|
29
|
+
return new ClassList({ steward: module.steward, ...(module.public === undefined ? {} : { public: module.public }), ...(module.beings === undefined ? {} : { beings: module.beings }) });
|
|
30
|
+
},
|
|
31
|
+
});
|