@lapxo/bound 0.1.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/CONTRIBUTING.md +44 -0
- package/LICENSE +22 -0
- package/README.md +97 -0
- package/TARGET.bound +361 -0
- package/dist/cli/accept.d.ts +23 -0
- package/dist/cli/accept.js +423 -0
- package/dist/cli/args.d.ts +10 -0
- package/dist/cli/args.js +34 -0
- package/dist/cli/host.d.ts +1 -0
- package/dist/cli/host.js +32 -0
- package/dist/cli/names.d.ts +25 -0
- package/dist/cli/names.js +12 -0
- package/dist/cli/observe.d.ts +1 -0
- package/dist/cli/observe.js +6 -0
- package/dist/cli/owner.d.ts +35 -0
- package/dist/cli/owner.js +119 -0
- package/dist/cli/pass.d.ts +6 -0
- package/dist/cli/pass.js +309 -0
- package/dist/cli/place.d.ts +74 -0
- package/dist/cli/place.js +290 -0
- package/dist/cli/takes.d.ts +30 -0
- package/dist/cli/takes.js +108 -0
- package/dist/cli/verb.d.ts +2 -0
- package/dist/cli/verb.js +478 -0
- package/dist/fold/ceilings.d.ts +24 -0
- package/dist/fold/ceilings.js +129 -0
- package/dist/fold/claims.d.ts +25 -0
- package/dist/fold/claims.js +107 -0
- package/dist/fold/closed.d.ts +15 -0
- package/dist/fold/closed.js +168 -0
- package/dist/fold/configures.d.ts +4 -0
- package/dist/fold/configures.js +22 -0
- package/dist/fold/digests.d.ts +29 -0
- package/dist/fold/digests.js +211 -0
- package/dist/fold/evidence.d.ts +32 -0
- package/dist/fold/evidence.js +22 -0
- package/dist/fold/fold-worker.d.ts +1 -0
- package/dist/fold/fold-worker.js +13 -0
- package/dist/fold/kept.d.ts +29 -0
- package/dist/fold/kept.js +142 -0
- package/dist/fold/keys.d.ts +17 -0
- package/dist/fold/keys.js +84 -0
- package/dist/fold/laid.d.ts +17 -0
- package/dist/fold/laid.js +90 -0
- package/dist/fold/nos.d.ts +8 -0
- package/dist/fold/nos.js +48 -0
- package/dist/fold/object.d.ts +17 -0
- package/dist/fold/object.js +38 -0
- package/dist/fold/observed.d.ts +33 -0
- package/dist/fold/observed.js +444 -0
- package/dist/fold/opened.d.ts +5 -0
- package/dist/fold/opened.js +33 -0
- package/dist/fold/own.d.ts +24 -0
- package/dist/fold/own.js +215 -0
- package/dist/fold/paid.d.ts +11 -0
- package/dist/fold/paid.js +92 -0
- package/dist/fold/place-inputs.d.ts +17 -0
- package/dist/fold/place-inputs.js +70 -0
- package/dist/fold/places.d.ts +20 -0
- package/dist/fold/places.js +84 -0
- package/dist/fold/rank.d.ts +3 -0
- package/dist/fold/rank.js +9 -0
- package/dist/fold/reach.d.ts +13 -0
- package/dist/fold/reach.js +65 -0
- package/dist/fold/readers.d.ts +44 -0
- package/dist/fold/readers.js +132 -0
- package/dist/fold/receipts.d.ts +8 -0
- package/dist/fold/receipts.js +47 -0
- package/dist/fold/region.d.ts +72 -0
- package/dist/fold/region.js +286 -0
- package/dist/fold/release-policy.d.ts +67 -0
- package/dist/fold/release-policy.js +103 -0
- package/dist/fold/resolved.d.ts +18 -0
- package/dist/fold/resolved.js +123 -0
- package/dist/fold/roles.d.ts +17 -0
- package/dist/fold/roles.js +59 -0
- package/dist/fold/second.d.ts +18 -0
- package/dist/fold/second.js +68 -0
- package/dist/fold/shards.d.ts +29 -0
- package/dist/fold/shards.js +100 -0
- package/dist/fold/signed.d.ts +18 -0
- package/dist/fold/signed.js +75 -0
- package/dist/fold/signers.d.ts +24 -0
- package/dist/fold/signers.js +58 -0
- package/dist/fold/stranger.d.ts +3 -0
- package/dist/fold/stranger.js +24 -0
- package/dist/fold/views.d.ts +27 -0
- package/dist/fold/views.js +66 -0
- package/dist/fold/wire.d.ts +19 -0
- package/dist/fold/wire.js +41 -0
- package/dist/fold/zoom.d.ts +16 -0
- package/dist/fold/zoom.js +80 -0
- package/dist/host/adapters/keys/ed25519.d.ts +7 -0
- package/dist/host/adapters/keys/ed25519.js +30 -0
- package/dist/host/archive.d.ts +11 -0
- package/dist/host/archive.js +152 -0
- package/dist/host/capsule-process.d.ts +20 -0
- package/dist/host/capsule-process.js +54 -0
- package/dist/host/capsule.d.ts +13 -0
- package/dist/host/capsule.js +149 -0
- package/dist/host/cell-receipts.d.ts +2 -0
- package/dist/host/cell-receipts.js +124 -0
- package/dist/host/digest.d.ts +3 -0
- package/dist/host/digest.js +15 -0
- package/dist/host/hash.d.ts +2 -0
- package/dist/host/hash.js +3 -0
- package/dist/host/io.d.ts +9 -0
- package/dist/host/io.js +9 -0
- package/dist/host/located.d.ts +4 -0
- package/dist/host/located.js +105 -0
- package/dist/host/objects.d.ts +5 -0
- package/dist/host/objects.js +18 -0
- package/dist/host/ports/store.d.ts +2 -0
- package/dist/host/ports/store.js +18 -0
- package/dist/host/provide.d.ts +23 -0
- package/dist/host/provide.js +81 -0
- package/dist/host/release.d.ts +12 -0
- package/dist/host/release.js +213 -0
- package/dist/host/selected-capsules.d.ts +8 -0
- package/dist/host/selected-capsules.js +19 -0
- package/dist/host/selected-topos.d.ts +17 -0
- package/dist/host/selected-topos.js +61 -0
- package/dist/host/source-content.d.ts +10 -0
- package/dist/host/source-content.js +94 -0
- package/dist/host/timing.d.ts +2 -0
- package/dist/host/timing.js +7 -0
- package/dist/judge/late.d.ts +7 -0
- package/dist/judge/late.js +33 -0
- package/dist/judge/take.d.ts +57 -0
- package/dist/judge/take.js +75 -0
- package/dist/judge/widening.d.ts +1 -0
- package/dist/judge/widening.js +25 -0
- package/dist/land/act.d.ts +4 -0
- package/dist/land/act.js +209 -0
- package/dist/land/alias.d.ts +10 -0
- package/dist/land/alias.js +41 -0
- package/dist/land/held.d.ts +7 -0
- package/dist/land/held.js +23 -0
- package/dist/land/inert.d.ts +26 -0
- package/dist/land/inert.js +33 -0
- package/dist/land/leaving.d.ts +14 -0
- package/dist/land/leaving.js +51 -0
- package/dist/land/ledger.d.ts +28 -0
- package/dist/land/ledger.js +148 -0
- package/dist/land/owned.d.ts +23 -0
- package/dist/land/owned.js +65 -0
- package/dist/land/sign.d.ts +16 -0
- package/dist/land/sign.js +30 -0
- package/dist/land/vouched.d.ts +21 -0
- package/dist/land/vouched.js +112 -0
- package/dist/land/withdraw.d.ts +14 -0
- package/dist/land/withdraw.js +24 -0
- package/dist/observe/files.d.ts +14 -0
- package/dist/observe/files.js +68 -0
- package/dist/observe/identity.d.ts +19 -0
- package/dist/observe/identity.js +61 -0
- package/dist/observe/match.d.ts +1 -0
- package/dist/observe/match.js +8 -0
- package/dist/observe/regions.d.ts +29 -0
- package/dist/observe/regions.js +200 -0
- package/dist/observe/run.d.ts +30 -0
- package/dist/observe/run.js +34 -0
- package/dist/observe/runner.d.ts +16 -0
- package/dist/observe/runner.js +83 -0
- package/dist/render/capsule.d.ts +24 -0
- package/dist/render/capsule.js +30 -0
- package/dist/render/field.d.ts +9 -0
- package/dist/render/field.js +46 -0
- package/dist/render/names.d.ts +3 -0
- package/dist/render/names.js +4 -0
- package/dist/render/open.d.ts +27 -0
- package/dist/render/open.js +61 -0
- package/dist/render/page.d.ts +3 -0
- package/dist/render/page.js +7 -0
- package/dist/render/receipts.d.ts +3 -0
- package/dist/render/receipts.js +46 -0
- package/dist/render/shape.d.ts +16 -0
- package/dist/render/shape.js +74 -0
- package/dist/render/target.d.ts +18 -0
- package/dist/render/target.js +52 -0
- package/dist/render/view.d.ts +22 -0
- package/dist/render/view.js +191 -0
- package/docs/readme-assets/atlas.svg +169 -0
- package/docs/usage.md +53 -0
- package/package.json +48 -0
- package/receipts.bound +59 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { PROTOCOL } from '@lapxo/topos/wire';
|
|
3
|
+
/** Operational process failure, distinct from a valid contract refusal or abstention. */
|
|
4
|
+
export class CapsuleProcessError extends Error {
|
|
5
|
+
constructor(reason, result) {
|
|
6
|
+
const stdout = result.stdout ?? '';
|
|
7
|
+
const stderr = result.stderr ?? '';
|
|
8
|
+
super(`REFUSE·capsule process ${reason}\n${stderr}${stdout}`);
|
|
9
|
+
this.name = 'CapsuleProcessError';
|
|
10
|
+
this.stdout = stdout;
|
|
11
|
+
this.stderr = stderr;
|
|
12
|
+
this.status = result.status;
|
|
13
|
+
this.signal = result.signal;
|
|
14
|
+
this.cause = result.error;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function validResponse(value, request) {
|
|
18
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
19
|
+
return false;
|
|
20
|
+
const one = value;
|
|
21
|
+
if (one['protocol'] !== PROTOCOL)
|
|
22
|
+
return false;
|
|
23
|
+
if (one['kind'] === 'refuse' || one['kind'] === 'abstain')
|
|
24
|
+
return typeof one['why'] === 'string';
|
|
25
|
+
if (one['kind'] !== 'fact')
|
|
26
|
+
return false;
|
|
27
|
+
if (request.verb === 'render')
|
|
28
|
+
return Array.isArray(one['lines']) && one['lines'].every((line) => typeof line === 'string');
|
|
29
|
+
if (request.verb === 'read')
|
|
30
|
+
return Array.isArray(one['claims']);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/** Last-line JSON is the wire framing. No partial batch, failed exit or synthetic missing response is accepted. */
|
|
34
|
+
export function executeCapsule(command, args, requests, limits = {}) {
|
|
35
|
+
const run = spawnSync(command, args, { input: JSON.stringify(requests), encoding: 'utf8', maxBuffer: 1 << 26, ...limits });
|
|
36
|
+
if (run.error)
|
|
37
|
+
throw new CapsuleProcessError(`failed: ${run.error.message}`, run);
|
|
38
|
+
if (run.signal !== null || run.status !== 0)
|
|
39
|
+
throw new CapsuleProcessError(`exited ${run.status ?? `with signal ${run.signal}`}`, run);
|
|
40
|
+
const last = /(?:^|\n)([^\n]*)$/.exec(run.stdout.trimEnd())?.[1] ?? '';
|
|
41
|
+
let values;
|
|
42
|
+
try {
|
|
43
|
+
values = JSON.parse(last);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new CapsuleProcessError('returned malformed or absent JSON', { ...run, error });
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(values) || values.length !== requests.length || !values.every((value, i) => validResponse(value, requests[i]))) {
|
|
49
|
+
throw new CapsuleProcessError(`returned an invalid batch; expected ${requests.length} responses`, run);
|
|
50
|
+
}
|
|
51
|
+
if (run.stderr)
|
|
52
|
+
process.stderr.write(run.stderr);
|
|
53
|
+
return values;
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Declaration } from '@lapxo/topos/capsule';
|
|
2
|
+
import type { Request, Response } from '@lapxo/topos/contract';
|
|
3
|
+
export interface Capsule {
|
|
4
|
+
readonly digest: string;
|
|
5
|
+
readonly declaration: Declaration;
|
|
6
|
+
readonly lines: readonly string[];
|
|
7
|
+
readonly ask: (requests: readonly Request[]) => readonly (Response | undefined)[];
|
|
8
|
+
}
|
|
9
|
+
/** A blob is loaded once in an act. A later ask for the same digest reads that capsule, and does not hash or unpack it again. */
|
|
10
|
+
export declare function capsuleAt(digest: string, entry: string, store?: string): Capsule | undefined;
|
|
11
|
+
export declare const childFlags: (argv?: readonly string[]) => readonly string[];
|
|
12
|
+
/** The host may supply its cache store independently of the located executable. */
|
|
13
|
+
export declare function locatedAt(tree: string, place: string, cacheStore?: string): Capsule | undefined;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { sha } from "./hash.js";
|
|
2
|
+
import { wireLinesOf as linesOf } from "../fold/claims.js";
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { mkdtempSync, rmSync, statSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { basename, join, resolve, relative, isAbsolute, sep } from 'node:path';
|
|
7
|
+
import { CAPSULE } from '@lapxo/topos/wire';
|
|
8
|
+
import { declarationOf } from '@lapxo/topos/capsule';
|
|
9
|
+
import { unpack } from "./archive.js";
|
|
10
|
+
import { executeCapsule, validResponse } from "./capsule-process.js";
|
|
11
|
+
import { blobAt, holdStore, replaceWhole, storeAt } from "../land/ledger.js";
|
|
12
|
+
import { coordinatesUnder, observeFile, observeText } from "../observe/files.js";
|
|
13
|
+
import { hostOf } from "../observe/run.js";
|
|
14
|
+
import { ownLock, ownRoot, ownStore, processOf } from "../observe/runner.js";
|
|
15
|
+
import { said } from "../fold/wire.js";
|
|
16
|
+
const declared = new Map();
|
|
17
|
+
const laid = new Map();
|
|
18
|
+
process.on('exit', () => { for (const at of laid.values())
|
|
19
|
+
rmSync(at, { recursive: true, force: true }); });
|
|
20
|
+
/** A blob is unpacked once in a process, where every run of it the process asks for reads it. */
|
|
21
|
+
function unpacked(digest, bytes) {
|
|
22
|
+
const held = laid.get(digest);
|
|
23
|
+
if (held !== undefined)
|
|
24
|
+
return held;
|
|
25
|
+
const at = mkdtempSync(join(tmpdir(), `${basename(ownRoot())}-capsule-`));
|
|
26
|
+
try {
|
|
27
|
+
unpack(bytes, at);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
rmSync(at, { recursive: true, force: true });
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
laid.set(digest, at);
|
|
34
|
+
return at;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A capsule is the bytes a digest names in the instrument's own store, and only when they hash to it: its own lock is
|
|
38
|
+
* read from them, and the coordinate a lock names runs as a process of its own, like a reader. Each answer is kept in one
|
|
39
|
+
* slot of the capsule, the place and the region; the body of the request is the hit, so a file that moved misses and
|
|
40
|
+
* overwrites the slot, and a request asked again for the same bytes is read back and never run twice. Operational failures never enter the cache; refusals and abstentions are asked again.
|
|
41
|
+
*/
|
|
42
|
+
const capsules = new Map();
|
|
43
|
+
/** A blob is loaded once in an act. A later ask for the same digest reads that capsule, and does not hash or unpack it again. */
|
|
44
|
+
export function capsuleAt(digest, entry, store) {
|
|
45
|
+
const identity = `${store ?? ownStore()}\0${digest}\0${entry}`;
|
|
46
|
+
const held = capsules.get(identity);
|
|
47
|
+
if (held !== undefined)
|
|
48
|
+
return held;
|
|
49
|
+
const bytes = observeFile(blobAt(store ?? ownStore(), digest));
|
|
50
|
+
if (bytes === undefined || `sha256:${createHash('sha256').update(bytes).digest('hex')}` !== digest)
|
|
51
|
+
return undefined;
|
|
52
|
+
const known = declared.get(digest) ?? ((lines) => ({ lines, declaration: declarationOf(lines) }))(linesOf(observeText(join(unpacked(digest, bytes), CAPSULE))));
|
|
53
|
+
declared.set(digest, known);
|
|
54
|
+
const names = Object.keys(known.declaration.regions);
|
|
55
|
+
const root = unpacked(digest, bytes);
|
|
56
|
+
const module = resolve(root, entry);
|
|
57
|
+
const coordinate = relative(root, module);
|
|
58
|
+
if (!entry || isAbsolute(entry) || (coordinate === '..' || coordinate.startsWith(`..${sep}`)) || entry.split(/[\\/]/).includes('..')) {
|
|
59
|
+
throw new Error(`REFUSE·capsule ${digest} declared entry ${entry || '(absent)'} unavailable`);
|
|
60
|
+
}
|
|
61
|
+
const capsule = { digest, ...known, ask: (requests) => {
|
|
62
|
+
if (!statSync(module, { throwIfNoEntry: false })?.isFile())
|
|
63
|
+
throw new Error(`REFUSE·capsule ${digest} declared entry ${entry} unavailable`);
|
|
64
|
+
return answered(digest, bytes, requests, names, root, store ?? ownStore(), module);
|
|
65
|
+
} };
|
|
66
|
+
capsules.set(identity, capsule);
|
|
67
|
+
return capsule;
|
|
68
|
+
}
|
|
69
|
+
const layoutsOf = () => said(ownLock(), 'release/layout')
|
|
70
|
+
.map((token) => token.split(':')).filter((pair) => pair.length === 2 && pair[0] !== undefined && pair[1] !== undefined);
|
|
71
|
+
const oneModule = (at) => {
|
|
72
|
+
const files = coordinatesUnder(at, at).filter((one) => one.endsWith('.js'));
|
|
73
|
+
return files.length === 1 ? files[0] : undefined;
|
|
74
|
+
};
|
|
75
|
+
const split = new Map();
|
|
76
|
+
const spawnOf = (at, names) => {
|
|
77
|
+
const held = split.get(at);
|
|
78
|
+
if (held !== undefined)
|
|
79
|
+
return held;
|
|
80
|
+
const layout = names.length === 0 ? undefined : layoutsOf().find(([dir, ext]) => names.some((name) => observeText(join(at, dir, `${name}${ext}`)) !== undefined));
|
|
81
|
+
const child = layout !== undefined ? [processOf('located', 'host'), at, layout[0], layout[1]]
|
|
82
|
+
: ((file) => (file === undefined ? [] : [processOf('located', 'host'), at, file, 'render']))(oneModule(at));
|
|
83
|
+
split.set(at, child);
|
|
84
|
+
return child;
|
|
85
|
+
};
|
|
86
|
+
export const childFlags = (argv = process.execArgv) => argv.filter((flag, i) => !/^(-e|-p|--eval|--print)$/.test(argv[i - 1] ?? '') && !/^(-e|-p|--eval|--print|--input-type)(=|$)/.test(flag));
|
|
87
|
+
function answered(digest, bytes, requests, names, tree, cacheStore = ownStore(), entry) {
|
|
88
|
+
const child = entry === undefined ? spawnOf(tree ?? unpacked(digest, bytes), names) : [entry];
|
|
89
|
+
if (!child.length) {
|
|
90
|
+
process.stderr.write(`LOAD regions · none of ${names.length} under the directories release/layout names\n`);
|
|
91
|
+
return requests.map(() => undefined);
|
|
92
|
+
}
|
|
93
|
+
const loader = entry !== undefined ? `${createHash('sha256').update(`${relative(tree, entry)}\n`).digest('hex')}\n` : child.length > 1 ? `${createHash('sha256').update(observeFile(child[0]) ?? new Uint8Array()).digest('hex')}\n` : '';
|
|
94
|
+
const slotOf = (request) => sha(`${digest}\n${loader}${request.verb}\n${request.rootScope}\n${request.region ?? ''}`);
|
|
95
|
+
const hitOf = (request) => sha(`${digest}\n${loader}${JSON.stringify(request)}`);
|
|
96
|
+
const kept = requests.map((request) => storeAt(cacheStore, 'cas', 'answers', slotOf(request)));
|
|
97
|
+
const hits = requests.map(hitOf);
|
|
98
|
+
const held = kept.map((at, i) => {
|
|
99
|
+
const text = observeText(at);
|
|
100
|
+
if (text === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
try {
|
|
103
|
+
const got = JSON.parse(text);
|
|
104
|
+
return got.format === 'capsule-answer/2' && got.hit === hits[i] && validResponse(got.said, requests[i]) && got.said.kind === 'fact' ? got.said : undefined;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
throw new Error(`${at}: ${error instanceof Error ? error.message : String(error)}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
const missing = requests.filter((_, i) => held[i] === undefined);
|
|
111
|
+
if (missing.length)
|
|
112
|
+
process.stderr.write(`CAPSULE ${digest.slice(7, 19)} · ${requests.length} asked · ${missing.length} run · ${missing.map((one) => `${one.rootScope}${one.region}`).join(' ')}\n`);
|
|
113
|
+
// Execute and validate the entire batch before committing any answer to the cache.
|
|
114
|
+
const ran = !missing.length ? [] : executeCapsule(process.execPath, [...childFlags(), hostOf(), ...child], missing);
|
|
115
|
+
return requests.map((request, i) => {
|
|
116
|
+
if (held[i] !== undefined)
|
|
117
|
+
return held[i];
|
|
118
|
+
const said = ran[missing.indexOf(request)];
|
|
119
|
+
if (said?.kind === 'fact') {
|
|
120
|
+
const text = JSON.stringify({ format: 'capsule-answer/2', hit: hits[i], said });
|
|
121
|
+
replaceWhole(kept[i], text);
|
|
122
|
+
holdStore(cacheStore, 'answers', slotOf(request), Buffer.byteLength(text));
|
|
123
|
+
}
|
|
124
|
+
return said;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** The host may supply its cache store independently of the located executable. */
|
|
128
|
+
export function locatedAt(tree, place, cacheStore = ownStore()) {
|
|
129
|
+
const at = join(tree, place);
|
|
130
|
+
const lines = linesOf(observeText(join(at, CAPSULE)));
|
|
131
|
+
if (!lines.length)
|
|
132
|
+
return undefined;
|
|
133
|
+
const known = { lines, declaration: declarationOf(lines) };
|
|
134
|
+
const names = Object.keys(known.declaration.regions);
|
|
135
|
+
if (!spawnOf(at, names).length)
|
|
136
|
+
return undefined;
|
|
137
|
+
const hash = createHash('sha256');
|
|
138
|
+
hash.update(observeFile(join(at, CAPSULE)) ?? new Uint8Array());
|
|
139
|
+
for (const name of names)
|
|
140
|
+
for (const [dir, ext] of layoutsOf()) {
|
|
141
|
+
const bytes = observeFile(join(at, dir, `${name}${ext}`));
|
|
142
|
+
if (bytes !== undefined) {
|
|
143
|
+
hash.update(bytes);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const digest = `sha256:${hash.digest('hex')}`;
|
|
148
|
+
return { digest, ...known, ask: (requests) => answered(digest, new Uint8Array(), requests, names, at, cacheStore) };
|
|
149
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { performance } from 'node:perf_hooks';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { canonical, objectHistory, parse, byBytes, wireAt, RECEIPTS } from '@lapxo/topos/wire';
|
|
6
|
+
import { cellInputs, cellReceiptContract } from '@lapxo/topos/cell-inputs';
|
|
7
|
+
import { renderCells } from '@lapxo/topos/cells-view';
|
|
8
|
+
import { admittedObjects } from "./objects.js";
|
|
9
|
+
import { objectFold } from "../fold/object.js";
|
|
10
|
+
import { fullDigest, bytesDigest } from "./digest.js";
|
|
11
|
+
import { closeReceipts, noteIdle } from "../fold/closed.js";
|
|
12
|
+
import { authorityFor, rootSigner } from "../fold/signers.js";
|
|
13
|
+
import { signaturesOf } from "../fold/digests.js";
|
|
14
|
+
import { storeOf } from "../land/ledger.js";
|
|
15
|
+
import { ownLock } from "../observe/runner.js";
|
|
16
|
+
import { toposAt } from "./selected-topos.js";
|
|
17
|
+
const fields = (line) => { const p = parse(line); return p.kind === 'fact' ? p.value.fields : {}; };
|
|
18
|
+
/** Only Topos input/render entry bytes enter region identity; host locations do not. */
|
|
19
|
+
function implementation(algorithm) {
|
|
20
|
+
return ['@lapxo/topos/cell-inputs', '@lapxo/topos/cells-view'].map(member => bytesDigest(readFileSync(fileURLToPath(import.meta.resolve(member))), algorithm)).sort(byBytes);
|
|
21
|
+
}
|
|
22
|
+
/** Native count/bytes receipt lines; carried render blobs are separate verified cache payloads. */
|
|
23
|
+
export async function cellsWithReceipts(root, history, emit) {
|
|
24
|
+
const started = performance.now(), store = storeOf(root), parsed = objectHistory(history);
|
|
25
|
+
if (parsed.kind !== 'fact')
|
|
26
|
+
throw Error(`REFUSE·wire ${parsed.why}`);
|
|
27
|
+
const authority = authorityFor(history, rootSigner(root), signaturesOf(store).admitted);
|
|
28
|
+
for (const record of parsed.value.objects)
|
|
29
|
+
if (authority.of(canonical(record.fields)).kind !== 'admitted')
|
|
30
|
+
throw Error(`REFUSE·authority receipt input ${record.fields.id}`);
|
|
31
|
+
const config = parsed.value.configuration.filter(f => authority.of(canonical(f)).kind === 'admitted');
|
|
32
|
+
cellReceiptContract(config);
|
|
33
|
+
const instrument = ownLock().map(fields);
|
|
34
|
+
const wire = wireAt(instrument, Number.MAX_SAFE_INTEGER);
|
|
35
|
+
if (!wire)
|
|
36
|
+
throw Error('REFUSE·receipt no admitted digest algorithm');
|
|
37
|
+
const algorithm = [...wire.digests][0];
|
|
38
|
+
if (!algorithm)
|
|
39
|
+
throw Error('REFUSE·receipt no digest algorithm');
|
|
40
|
+
const code = implementation(algorithm);
|
|
41
|
+
const inputs = cellInputs(parsed.value.objects), semantic = new Map(), inputBytes = new Map();
|
|
42
|
+
const pins = new Map();
|
|
43
|
+
for (const record of parsed.value.objects.filter(r => r.record === 'cell')) {
|
|
44
|
+
const pin = record.fields.topos;
|
|
45
|
+
if (!pins.has(pin)) {
|
|
46
|
+
const top = toposAt(pin);
|
|
47
|
+
pins.set(pin, [pin, ...top.dependencies]);
|
|
48
|
+
}
|
|
49
|
+
const scope = `receipts/cells/${record.fields.scope}`;
|
|
50
|
+
const bytes = [...inputs.get(record.fields.scope), ...pins.get(pin), ...code, 'resolution=1'].sort(byBytes).join('\n');
|
|
51
|
+
inputBytes.set(scope, bytes);
|
|
52
|
+
semantic.set(scope, fullDigest(bytes, algorithm));
|
|
53
|
+
}
|
|
54
|
+
// The context stamp is a cache validity guard, not any cell's semantic receipt identity.
|
|
55
|
+
const contextStamp = fullDigest([...history, ...code].sort(byBytes), algorithm);
|
|
56
|
+
const at = join(root, RECEIPTS), raw = existsSync(at) ? readFileSync(at, 'utf8').split('\n').filter(Boolean) : [], native = raw.filter(l => parse(l).kind === 'fact');
|
|
57
|
+
const headers = native.filter(l => fields(l).scope === 'receipts' && fields(l).measure === 'digest');
|
|
58
|
+
const carried = (digest) => join(store, 'cas', 'carried', digest.slice(digest.indexOf(':') + 1));
|
|
59
|
+
const readVerified = (digest) => {
|
|
60
|
+
if (!digest.startsWith(algorithm + ':') || !/^[a-f0-9]+$/.test(digest.split(':')[1] ?? ''))
|
|
61
|
+
return;
|
|
62
|
+
const file = carried(digest);
|
|
63
|
+
if (!existsSync(file))
|
|
64
|
+
return;
|
|
65
|
+
const bytes = readFileSync(file);
|
|
66
|
+
const got = bytesDigest(bytes, algorithm);
|
|
67
|
+
if (got !== digest) {
|
|
68
|
+
emit(`RECEIPT invalid ${digest} · got ${got} · recompute`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
return bytes.toString('utf8');
|
|
72
|
+
};
|
|
73
|
+
const opened = new Set(await closeReceipts(root, store, semantic));
|
|
74
|
+
const bodyLines = native.filter(l => fields(l).scope !== 'receipts').sort(byBytes).join('\n') + '\n';
|
|
75
|
+
const intact = raw.length === native.length && headers.length === 1 && readVerified(fields(headers[0]).at.replace(/^place:/, '')) === bodyLines;
|
|
76
|
+
if (!intact)
|
|
77
|
+
for (const scope of semantic.keys())
|
|
78
|
+
opened.add(scope);
|
|
79
|
+
const cached = new Map();
|
|
80
|
+
for (const [scope, digest] of semantic) {
|
|
81
|
+
const bytes = native.filter(l => fields(l).scope === scope && fields(l).measure === 'bytes');
|
|
82
|
+
const counts = native.filter(l => fields(l).scope === scope && fields(l).measure === 'count');
|
|
83
|
+
if (bytes.length !== 1 || counts.length !== 1 || fields(bytes[0]).value !== digest || fields(counts[0]).value !== '1..1') {
|
|
84
|
+
opened.add(scope);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const output = readVerified(fields(counts[0]).at.replace(/^place:/, ''));
|
|
88
|
+
if (output === undefined) {
|
|
89
|
+
opened.add(scope);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
cached.set(scope, output);
|
|
93
|
+
}
|
|
94
|
+
const idle = headers.length === 1 && fields(headers[0]).value === contextStamp && readVerified(fields(headers[0]).at.replace(/^place:/, '')) !== undefined && opened.size === 0;
|
|
95
|
+
let readings;
|
|
96
|
+
if (!idle) {
|
|
97
|
+
const held = await admittedObjects(root, history, emit);
|
|
98
|
+
readings = objectFold(held.records, held.context);
|
|
99
|
+
}
|
|
100
|
+
const newLines = [], outputs = [];
|
|
101
|
+
const save = (text) => { const digest = bytesDigest(Buffer.from(text), algorithm); const path = carried(digest); mkdirSync(dirname(path), { recursive: true }); if (!existsSync(path) || readFileSync(path, 'utf8') !== text)
|
|
102
|
+
writeFileSync(path, text); return digest; };
|
|
103
|
+
for (const [scope, digest] of semantic) {
|
|
104
|
+
const cellName = scope.slice('receipts/cells/'.length);
|
|
105
|
+
const output = (!opened.has(scope) && cached.has(scope)) ? cached.get(scope) : renderCells([readings.find(c => c.cell === cellName)]).join('\n') + '\n';
|
|
106
|
+
outputs.push(output.trimEnd());
|
|
107
|
+
const payload = save(output);
|
|
108
|
+
if (save(inputBytes.get(scope)) !== digest)
|
|
109
|
+
throw Error('REFUSE·receipt input closure hash mismatch');
|
|
110
|
+
newLines.push(canonical({ scope, role: 'writes', form: 'interval', measure: 'count', value: '1..1', at: `place:${payload}`, by: 'bound' }), canonical({ scope, role: 'writes', form: 'alphabet', measure: 'bytes', value: digest, at: `place:${digest}`, by: 'bound' }));
|
|
111
|
+
emit(`RECEIPT ${scope}@1 · ${digest} · ${opened.has(scope) ? 'opened' : 'same'}`);
|
|
112
|
+
}
|
|
113
|
+
const nativeBody = newLines.sort(byBytes).join('\n') + '\n', body = save(nativeBody);
|
|
114
|
+
// One native header names carried count/bytes lines; at closes the exact carried bytes.
|
|
115
|
+
const next = [canonical({ scope: 'receipts', role: 'writes', form: 'alphabet', measure: 'digest', value: contextStamp, at: `place:${body}`, by: 'bound' }), ...newLines.sort(byBytes)].join('\n') + '\n';
|
|
116
|
+
if (!existsSync(at) || readFileSync(at, 'utf8') !== next)
|
|
117
|
+
writeFileSync(at, next);
|
|
118
|
+
if (idle)
|
|
119
|
+
noteIdle();
|
|
120
|
+
emit(`RECEIPTS ${semantic.size} read · ${opened.size} opened · ${idle ? 'idle' : 'context checked'} · provider ${idle ? 0 : pins.size}`);
|
|
121
|
+
for (const output of outputs)
|
|
122
|
+
emit(output);
|
|
123
|
+
emit(`COST cells@1 · ${(performance.now() - started).toFixed(3)} ms · admission/context/receipt/render included`);
|
|
124
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { byBytes } from '@lapxo/topos/wire';
|
|
3
|
+
import { selfName } from "../fold/claims.js";
|
|
4
|
+
/** Sorted by UTF-8 bytes; the algorithm is a field (`sha256:…`), never bare hex. */
|
|
5
|
+
export function fullDigest(input, algorithm) {
|
|
6
|
+
if (!algorithm)
|
|
7
|
+
throw new Error(`${selfName()}: REFUSE·runtime a digest was asked for before the lock said which algorithm`);
|
|
8
|
+
const text = typeof input === 'string' ? input : [...input].sort(byBytes).join('\n');
|
|
9
|
+
return `${algorithm}:${createHash(algorithm).update(text).digest('hex')}`;
|
|
10
|
+
}
|
|
11
|
+
export function bytesDigest(bytes, algorithm) {
|
|
12
|
+
if (!algorithm)
|
|
13
|
+
throw new Error(`${selfName()}: REFUSE·runtime a digest was asked for before the lock said which algorithm`);
|
|
14
|
+
return `${algorithm}:${createHash(algorithm).update(bytes).digest('hex')}`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The host's node surface: kernel folders import here, never `node:`. World reads still go through observeFile. */
|
|
2
|
+
export { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
export { createHash } from 'node:crypto';
|
|
4
|
+
export { appendFileSync, closeSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
5
|
+
export { tmpdir } from 'node:os';
|
|
6
|
+
export { basename, dirname, join, relative, resolve } from 'node:path';
|
|
7
|
+
export { fileURLToPath } from 'node:url';
|
|
8
|
+
export { parentPort, workerData } from 'node:worker_threads';
|
|
9
|
+
export { gzipSync } from 'node:zlib';
|
package/dist/host/io.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The host's node surface: kernel folders import here, never `node:`. World reads still go through observeFile. */
|
|
2
|
+
export { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
export { createHash } from 'node:crypto';
|
|
4
|
+
export { appendFileSync, closeSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
5
|
+
export { tmpdir } from 'node:os';
|
|
6
|
+
export { basename, dirname, join, relative, resolve } from 'node:path';
|
|
7
|
+
export { fileURLToPath } from 'node:url';
|
|
8
|
+
export { parentPort, workerData } from 'node:worker_threads';
|
|
9
|
+
export { gzipSync } from 'node:zlib';
|
|
@@ -0,0 +1,105 @@
|
|
|
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 { readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { pinExport, provide } from "./provide.js";
|
|
13
|
+
const wanted = (role) => role === 'reader' ? ['observe', 'run'] : [role];
|
|
14
|
+
const foreign = (role) => ['render', 'receipt', 'observe', 'run'].filter((name) => !wanted(role).includes(name));
|
|
15
|
+
const isFn = (module, name) => typeof module[name] === 'function';
|
|
16
|
+
const text = (error) => (error instanceof Error ? error.message : String(error)).replace(/\s+/g, ' ').trim();
|
|
17
|
+
/**
|
|
18
|
+
* Wire and capsule of the world, from `dep/topos` (or the lock's `uses/topos` pin). The host never spells where that
|
|
19
|
+
* package lies.
|
|
20
|
+
*/
|
|
21
|
+
const sdk = async () => {
|
|
22
|
+
provide();
|
|
23
|
+
const [wireAt, capsuleAt] = [pinExport('topos', './wire'), pinExport('topos', './capsule')];
|
|
24
|
+
if (!wireAt || !capsuleAt)
|
|
25
|
+
throw new Error('REFUSE·pin dep/topos names no wire or capsule');
|
|
26
|
+
const [wire, capsule] = await Promise.all([import(__rewriteRelativeImportExtension(pathToFileURL(wireAt).href)), import(__rewriteRelativeImportExtension(pathToFileURL(capsuleAt).href))]);
|
|
27
|
+
return {
|
|
28
|
+
CAPSULE: String(wire['CAPSULE'] ?? ''),
|
|
29
|
+
PROTOCOL: String(wire['PROTOCOL'] ?? ''),
|
|
30
|
+
parse: wire['parse'],
|
|
31
|
+
declarationOf: capsule['declarationOf'],
|
|
32
|
+
readers: capsule['readers'],
|
|
33
|
+
receiptShell: capsule['receiptShell'],
|
|
34
|
+
shell: capsule['shell'],
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The host loads a world from its lock: the lock read once, each region imported from its one file by name and role into
|
|
39
|
+
* the shell of its kind. A region that fails is one line on stderr and is left out; the regions that loaded still form
|
|
40
|
+
* the shell. A world with no region of a kind has no shell of it. `load` is the host's child entry, the same load with
|
|
41
|
+
* its places read from the arguments the host spawned it with.
|
|
42
|
+
*/
|
|
43
|
+
/** A capsule that ships one render module: the host calls it per region and hands it the lines. The module does not read. */
|
|
44
|
+
const pagesOf = async (root, file) => {
|
|
45
|
+
const { CAPSULE, PROTOCOL, declarationOf, shell } = await sdk();
|
|
46
|
+
const lines = readFileSync(join(root, CAPSULE), 'utf8').split('\n').filter((line) => line.startsWith(PROTOCOL));
|
|
47
|
+
const { regions } = declarationOf(lines);
|
|
48
|
+
const module = await import(__rewriteRelativeImportExtension(pathToFileURL(join(root, file)).href));
|
|
49
|
+
const render = module['render'] ?? module['default']?.['render'];
|
|
50
|
+
if (typeof render !== 'function')
|
|
51
|
+
return {};
|
|
52
|
+
const held = Object.fromEntries(Object.entries(regions).map(([name, reads]) => [name, {
|
|
53
|
+
reads,
|
|
54
|
+
region: (asked) => render({ ...asked, region: name }),
|
|
55
|
+
}]));
|
|
56
|
+
return held && Object.keys(held).length ? { render: shell(held) } : {};
|
|
57
|
+
};
|
|
58
|
+
export const locate = async (root, dir, ext) => {
|
|
59
|
+
if (ext === 'render')
|
|
60
|
+
return pagesOf(root, dir);
|
|
61
|
+
const { CAPSULE, PROTOCOL, parse, declarationOf, readers, receiptShell, shell } = await sdk();
|
|
62
|
+
const lines = readFileSync(join(root, CAPSULE), 'utf8').split('\n').filter((line) => line.startsWith(PROTOCOL));
|
|
63
|
+
const field = (line, name) => ((got) => (got.kind === 'fact' ? got.value.fields[name] ?? '' : ''))(parse(line));
|
|
64
|
+
const { regions, writes } = declarationOf(lines);
|
|
65
|
+
const reader = new Set(lines.filter((line) => field(line, 'measure') === 'writes').map((line) => field(line, 'scope').slice('region/'.length)));
|
|
66
|
+
const kind = (name) => (reader.has(name) ? 'reader' : writes[name] ?? '');
|
|
67
|
+
const region = async (name, reads, role) => {
|
|
68
|
+
let module;
|
|
69
|
+
try {
|
|
70
|
+
module = await import(__rewriteRelativeImportExtension(pathToFileURL(join(root, dir, `${name}${ext}`)).href));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
process.stderr.write(`LOAD ${name} · module error · ${text(error)}\n`);
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const has = wanted(role).filter((key) => isFn(module, key));
|
|
77
|
+
const exported = foreign(role).filter((key) => isFn(module, key));
|
|
78
|
+
if (!has.length && exported.length) {
|
|
79
|
+
process.stderr.write(`LOAD ${name} · role mismatch · ${role} exports ${exported.join('|')}\n`);
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
if (!has.length) {
|
|
83
|
+
process.stderr.write(`LOAD ${name} · missing export · ${wanted(role)[0]}\n`);
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const picked = role === 'reader' ? { observe: module['observe'], run: module['run'] } : { region: module[role] };
|
|
87
|
+
return [name, { reads, ...picked }];
|
|
88
|
+
};
|
|
89
|
+
const loaded = async (role) => {
|
|
90
|
+
const held = [];
|
|
91
|
+
for (const [name, reads] of Object.entries(regions)) {
|
|
92
|
+
if (kind(name) !== role)
|
|
93
|
+
continue;
|
|
94
|
+
const row = await region(name, reads, role);
|
|
95
|
+
if (row)
|
|
96
|
+
held.push(row);
|
|
97
|
+
}
|
|
98
|
+
return held.length ? Object.fromEntries(held) : undefined;
|
|
99
|
+
};
|
|
100
|
+
const render = await loaded('render');
|
|
101
|
+
const receipt = await loaded('receipt');
|
|
102
|
+
const reading = await loaded('reader');
|
|
103
|
+
return { render: render && shell(render), receipt: receipt && receiptShell(receipt), ...(reading ? readers(reading) : {}) };
|
|
104
|
+
};
|
|
105
|
+
export const load = () => locate(process.argv[3] ?? '', process.argv[4] ?? '', process.argv[5] ?? '');
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Bound supplies host authority and I/O; topos owns grammar, selection and context validation. */
|
|
2
|
+
export declare function admittedObjects(root: string, lines: readonly string[], emit?: (text: string) => void): Promise<{
|
|
3
|
+
records: readonly import("@lapxo/topos/wire").ObjectRecord[];
|
|
4
|
+
context: import("@lapxo/topos/wire").ObjectContext;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { canonical, objectHistory, validateObjectContext } from '@lapxo/topos/wire';
|
|
2
|
+
import { objectProvider } from '@lapxo/topos/object-provider';
|
|
3
|
+
import { authorityFor, rootSigner } from "../fold/signers.js";
|
|
4
|
+
import { signaturesOf } from "../fold/digests.js";
|
|
5
|
+
import { storeOf } from "../land/ledger.js";
|
|
6
|
+
import { toposAt, contextModuleAt } from "./selected-topos.js";
|
|
7
|
+
/** Bound supplies host authority and I/O; topos owns grammar, selection and context validation. */
|
|
8
|
+
export async function admittedObjects(root, lines, emit = () => { }) {
|
|
9
|
+
const history = objectHistory(lines);
|
|
10
|
+
if (history.kind !== 'fact')
|
|
11
|
+
throw Error(`REFUSE·wire ${history.why}`);
|
|
12
|
+
const authority = authorityFor(lines, rootSigner(root), signaturesOf(storeOf(root)).admitted);
|
|
13
|
+
const context = await objectProvider(history.value.objects, history.value.configuration, { admit: fields => authority.of(canonical(fields)).kind === 'admitted', emit, topos: toposAt, module: contextModuleAt });
|
|
14
|
+
const checked = validateObjectContext(history.value.objects, context);
|
|
15
|
+
if (checked.kind !== 'fact')
|
|
16
|
+
throw Error(`REFUSE·context ${checked.why}`);
|
|
17
|
+
return { records: checked.value, context };
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The store root is the tree that holds a TARGET.bound beside its .bound store. */
|
|
2
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { EXTENSION, LOCK } from '@lapxo/topos/wire';
|
|
5
|
+
export function storeRoot(from) {
|
|
6
|
+
const abs = resolve(from || '.');
|
|
7
|
+
let at = abs;
|
|
8
|
+
for (let i = 0; i < 64; i += 1) {
|
|
9
|
+
if (existsSync(join(at, LOCK)) && existsSync(join(at, EXTENSION)))
|
|
10
|
+
return at;
|
|
11
|
+
const parent = dirname(at);
|
|
12
|
+
if (parent === at)
|
|
13
|
+
break;
|
|
14
|
+
at = parent;
|
|
15
|
+
}
|
|
16
|
+
return abs;
|
|
17
|
+
}
|
|
18
|
+
export const storeDirs = (store) => existsSync(store) ? readdirSync(store, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length : 0;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface ResolveContext {
|
|
2
|
+
parentURL?: string;
|
|
3
|
+
}
|
|
4
|
+
interface ResolveResult {
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
type NextResolve = (specifier: string, context: ResolveContext) => ResolveResult;
|
|
8
|
+
/**
|
|
9
|
+
* Where a named package or world lies: `dep/<name>` for a library the instrument installs, `uses/<name>` for a world
|
|
10
|
+
* pinned by its GitHub release. Both are the digest, laid. Nothing here spells a path.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pinPlace(name: string): string;
|
|
13
|
+
export declare function pinExport(name: string, sub: string): string;
|
|
14
|
+
export declare function pinName(name: string): string;
|
|
15
|
+
/** Node package resolution at a digest-laid library; exports remain authoritative. */
|
|
16
|
+
export declare function resolvePinned(specifier: string, context: ResolveContext, nextResolve: NextResolve, pin: {
|
|
17
|
+
readonly at: string;
|
|
18
|
+
readonly pkg: string;
|
|
19
|
+
readonly json: string;
|
|
20
|
+
readonly exports: boolean;
|
|
21
|
+
}): ResolveResult;
|
|
22
|
+
export declare function provide(): void;
|
|
23
|
+
export {};
|