@lapxo/topos 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/LICENSE +21 -0
- package/README.md +213 -0
- package/TARGET.bound +567 -0
- package/dist/authority/admit.d.ts +2 -0
- package/dist/authority/admit.js +5 -0
- package/dist/authority/index.d.ts +4 -0
- package/dist/authority/index.js +4 -0
- package/dist/authority/motion.d.ts +11 -0
- package/dist/authority/motion.js +3 -0
- package/dist/capsule/asked.d.ts +28 -0
- package/dist/capsule/asked.js +9 -0
- package/dist/capsule/declaration.d.ts +26 -0
- package/dist/capsule/declaration.js +45 -0
- package/dist/capsule/index.d.ts +3 -0
- package/dist/capsule/index.js +3 -0
- package/dist/capsule/shell.d.ts +51 -0
- package/dist/capsule/shell.js +64 -0
- package/dist/classes/scale.d.ts +16 -0
- package/dist/classes/scale.js +1 -0
- package/dist/contract/index.d.ts +27 -0
- package/dist/contract/index.js +63 -0
- package/dist/forms/alphabet.d.ts +3 -0
- package/dist/forms/alphabet.js +19 -0
- package/dist/forms/budget.d.ts +3 -0
- package/dist/forms/budget.js +11 -0
- package/dist/forms/distribution.d.ts +3 -0
- package/dist/forms/distribution.js +36 -0
- package/dist/forms/form.d.ts +31 -0
- package/dist/forms/form.js +42 -0
- package/dist/forms/index.d.ts +12 -0
- package/dist/forms/index.js +11 -0
- package/dist/forms/interval.d.ts +3 -0
- package/dist/forms/interval.js +11 -0
- package/dist/forms/ladder.d.ts +3 -0
- package/dist/forms/ladder.js +11 -0
- package/dist/forms/lattice.d.ts +3 -0
- package/dist/forms/lattice.js +18 -0
- package/dist/forms/phase.d.ts +3 -0
- package/dist/forms/phase.js +11 -0
- package/dist/forms/rank.d.ts +3 -0
- package/dist/forms/rank.js +11 -0
- package/dist/forms/stretch.d.ts +3 -0
- package/dist/forms/stretch.js +11 -0
- package/dist/readers/bindings.d.ts +21 -0
- package/dist/readers/bindings.js +1 -0
- package/dist/readers/define.d.ts +37 -0
- package/dist/readers/define.js +59 -0
- package/dist/readers/index.d.ts +3 -0
- package/dist/readers/index.js +2 -0
- package/dist/wire/claim.d.ts +4 -0
- package/dist/wire/claim.js +43 -0
- package/dist/wire/claimline.d.ts +78 -0
- package/dist/wire/claimline.js +339 -0
- package/dist/wire/classes.d.ts +17 -0
- package/dist/wire/classes.js +39 -0
- package/dist/wire/descriptor.d.ts +45 -0
- package/dist/wire/descriptor.js +8 -0
- package/dist/wire/evidence.d.ts +2 -0
- package/dist/wire/evidence.js +2 -0
- package/dist/wire/grammar.d.ts +22 -0
- package/dist/wire/grammar.js +42 -0
- package/dist/wire/index.d.ts +14 -0
- package/dist/wire/index.js +11 -0
- package/dist/wire/line.d.ts +10 -0
- package/dist/wire/line.js +118 -0
- package/dist/wire/measures.d.ts +2 -0
- package/dist/wire/measures.js +13 -0
- package/dist/wire/names.d.ts +8 -0
- package/dist/wire/names.js +8 -0
- package/dist/wire/outcome.d.ts +18 -0
- package/dist/wire/outcome.js +3 -0
- package/dist/wire/sig.d.ts +15 -0
- package/dist/wire/sig.js +15 -0
- package/dist/wire/spec.d.ts +28 -0
- package/dist/wire/spec.js +1 -0
- package/dist/wire/types.d.ts +73 -0
- package/dist/wire/types.js +2 -0
- package/dist/wire/values.d.ts +23 -0
- package/dist/wire/values.js +80 -0
- package/package.json +68 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { abstain, fact, refuse } from "./outcome.js";
|
|
2
|
+
import { boundOf } from "./values.js";
|
|
3
|
+
export const PROTOCOL = 'bound-lock/1';
|
|
4
|
+
const NEEDS_QUOTE = /[ ="\n\\]/;
|
|
5
|
+
function escape(value) {
|
|
6
|
+
if (!NEEDS_QUOTE.test(value))
|
|
7
|
+
return value;
|
|
8
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
|
|
9
|
+
}
|
|
10
|
+
function unescape(token) {
|
|
11
|
+
if (!token.startsWith('"'))
|
|
12
|
+
return NEEDS_QUOTE.test(token) ? null : token;
|
|
13
|
+
if (token.length < 2 || !token.endsWith('"'))
|
|
14
|
+
return null;
|
|
15
|
+
const body = token.slice(1, -1);
|
|
16
|
+
let out = '';
|
|
17
|
+
for (let i = 0; i < body.length; i++) {
|
|
18
|
+
const c = body[i];
|
|
19
|
+
if (c !== '\\') {
|
|
20
|
+
if (c === '"')
|
|
21
|
+
return null;
|
|
22
|
+
out += c;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const next = body[i + 1];
|
|
26
|
+
if (next === '"')
|
|
27
|
+
out += '"';
|
|
28
|
+
else if (next === '\\')
|
|
29
|
+
out += '\\';
|
|
30
|
+
else if (next === 'n')
|
|
31
|
+
out += '\n';
|
|
32
|
+
else
|
|
33
|
+
return null;
|
|
34
|
+
i += 1;
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
const UTF8 = new TextEncoder();
|
|
39
|
+
export function byBytes(a, b) {
|
|
40
|
+
const x = UTF8.encode(a);
|
|
41
|
+
const y = UTF8.encode(b);
|
|
42
|
+
for (let i = 0; i < x.length && i < y.length; i++) {
|
|
43
|
+
if (x[i] !== y[i])
|
|
44
|
+
return x[i] - y[i];
|
|
45
|
+
}
|
|
46
|
+
return x.length - y.length;
|
|
47
|
+
}
|
|
48
|
+
/* NO UNICODE NORMALISATION — NFC and NFD are two scopes until a bridge. */
|
|
49
|
+
const unwritten = (fields) => fields['form'] === 'interval' && fields['value'] !== undefined && fields['value'] !== 'withdraw' && boundOf('interval', fields['value']) === null;
|
|
50
|
+
export function canonical(fields, version = '1') {
|
|
51
|
+
if (unwritten(fields))
|
|
52
|
+
throw new Error(`REFUSE·wire canonical: \`${fields['value']}\` is no interval the wire writes: two ends, each decimal or *`);
|
|
53
|
+
const keys = Object.keys(fields).sort(byBytes);
|
|
54
|
+
const tokens = keys.map((k) => `${k}=${escape(fields[k])}`);
|
|
55
|
+
return [`${PROTOCOL.split('/')[0]}/${version}`, ...tokens].join(' ');
|
|
56
|
+
}
|
|
57
|
+
export function signedBytes(fields, version = '1') {
|
|
58
|
+
const { sig: _sig, ...rest } = fields;
|
|
59
|
+
return canonical(rest, version);
|
|
60
|
+
}
|
|
61
|
+
const ABSENT = '-';
|
|
62
|
+
export function parse(text) {
|
|
63
|
+
if (text.includes('\n')) {
|
|
64
|
+
return refuse('a line is one line — a newline in a value must be escaped as `\\n`');
|
|
65
|
+
}
|
|
66
|
+
const space = text.indexOf(' ');
|
|
67
|
+
const envelope = space === -1 ? text : text.slice(0, space);
|
|
68
|
+
const [name, version] = envelope.split('/');
|
|
69
|
+
if (name !== PROTOCOL.split('/')[0] || !version) {
|
|
70
|
+
return refuse(`no \`bound-lock/<v>\` envelope — found \`${envelope.slice(0, 24)}\``);
|
|
71
|
+
}
|
|
72
|
+
if (version !== PROTOCOL.split('/')[1]) {
|
|
73
|
+
return abstain(`\`bound-lock/${version}\` is a version this reader does not know`);
|
|
74
|
+
}
|
|
75
|
+
const fields = {};
|
|
76
|
+
for (const token of tokensOf(space === -1 ? '' : text.slice(space + 1))) {
|
|
77
|
+
if (token === null)
|
|
78
|
+
return refuse('a quoted value is not closed');
|
|
79
|
+
const at = token.indexOf('=');
|
|
80
|
+
if (at <= 0)
|
|
81
|
+
return refuse(`\`${token.slice(0, 24)}\` is not \`key=value\``);
|
|
82
|
+
const key = token.slice(0, at);
|
|
83
|
+
if (key in fields)
|
|
84
|
+
return refuse(`\`${key}\` appears twice — one line, one value per key`);
|
|
85
|
+
const value = unescape(token.slice(at + 1));
|
|
86
|
+
if (value === null)
|
|
87
|
+
return refuse(`\`${key}\` has an escaping this format does not define`);
|
|
88
|
+
if (value !== ABSENT)
|
|
89
|
+
fields[key] = value;
|
|
90
|
+
}
|
|
91
|
+
if (unwritten(fields))
|
|
92
|
+
return refuse(`\`${fields['value']}\` is no interval the wire reads: two ends, each decimal or *`);
|
|
93
|
+
return fact({ version, fields });
|
|
94
|
+
}
|
|
95
|
+
function* tokensOf(rest) {
|
|
96
|
+
let at = 0;
|
|
97
|
+
while (at < rest.length) {
|
|
98
|
+
while (rest[at] === ' ')
|
|
99
|
+
at += 1;
|
|
100
|
+
if (at >= rest.length)
|
|
101
|
+
return;
|
|
102
|
+
let end = at;
|
|
103
|
+
let quoted = false;
|
|
104
|
+
while (end < rest.length && (quoted || rest[end] !== ' ')) {
|
|
105
|
+
if (rest[end] === '\\' && quoted)
|
|
106
|
+
end += 1;
|
|
107
|
+
else if (rest[end] === '"')
|
|
108
|
+
quoted = !quoted;
|
|
109
|
+
end += 1;
|
|
110
|
+
}
|
|
111
|
+
if (quoted) {
|
|
112
|
+
yield null;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
yield rest.slice(at, end);
|
|
116
|
+
at = end;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { intervals } from '@lapxo/obligations';
|
|
2
|
+
const READINGS = intervals(-Infinity, Infinity);
|
|
3
|
+
/** How the readings of one measure combine when a line reads more than one of them: counts, sizes and showings add up; a longest is the longest. */
|
|
4
|
+
const COMBINATIONS = { count: 'sum', size: 'sum', shown: 'sum', longest: 'max' };
|
|
5
|
+
/** The readings of a measure combined as the measure says; one this table names no combination for is refused when it is more than one. */
|
|
6
|
+
export function combine(measure, values) {
|
|
7
|
+
const how = COMBINATIONS[measure];
|
|
8
|
+
if (how === 'max')
|
|
9
|
+
return values.reduce((held, one) => READINGS.join(held, { lo: -Infinity, hi: one }), { lo: -Infinity, hi: 0 }).hi;
|
|
10
|
+
if (how === 'sum' || values.length <= 1)
|
|
11
|
+
return values.reduce((sum, one) => sum + one, 0);
|
|
12
|
+
throw new Error(`REFUSE·measure ${measure}: ${values.length} readings and no combination the wire names for them`);
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The format's own names, spelled once: the extension every file of the format carries, and the three files it names
|
|
3
|
+
* by it.
|
|
4
|
+
*/
|
|
5
|
+
export declare const EXTENSION = ".bound";
|
|
6
|
+
export declare const LOCK = "TARGET.bound";
|
|
7
|
+
export declare const CAPSULE = "capsule.bound";
|
|
8
|
+
export declare const RECEIPTS = "receipts.bound";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The format's own names, spelled once: the extension every file of the format carries, and the three files it names
|
|
3
|
+
* by it.
|
|
4
|
+
*/
|
|
5
|
+
export const EXTENSION = '.bound';
|
|
6
|
+
export const LOCK = `TARGET${EXTENSION}`;
|
|
7
|
+
export const CAPSULE = `capsule${EXTENSION}`;
|
|
8
|
+
export const RECEIPTS = `receipts${EXTENSION}`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three arms, and there is no fourth. Isolated so the line kernel
|
|
3
|
+
* does not import the rest of the wire.
|
|
4
|
+
*/
|
|
5
|
+
export type Outcome<T> = {
|
|
6
|
+
readonly kind: 'fact';
|
|
7
|
+
readonly value: T;
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: 'abstain';
|
|
10
|
+
readonly why: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'refuse';
|
|
13
|
+
readonly why: string;
|
|
14
|
+
readonly named?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const fact: <T>(value: T) => Outcome<T>;
|
|
17
|
+
export declare const abstain: <T>(why: string) => Outcome<T>;
|
|
18
|
+
export declare const refuse: <T>(why: string, named?: string) => Outcome<T>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A signature is a value, not an act: `<algorithm>:<raw>` over the canonical bytes of every other field of a claim,
|
|
3
|
+
* with the signer named. The algorithm is named by the digest of its definition, never by a name of its era, so no
|
|
4
|
+
* alphabet of algorithms is closed here: which ones a wire admits, and from which epoch, is a signed line of its lock.
|
|
5
|
+
* The raw part never holds a colon, so the last colon parts them; a digest may carry its own.
|
|
6
|
+
*/
|
|
7
|
+
interface SignatureValue {
|
|
8
|
+
readonly algorithm: string;
|
|
9
|
+
readonly raw: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function formatSignature(algorithm: string, raw: string): string;
|
|
12
|
+
export declare function parseSignature(value: string): SignatureValue | null;
|
|
13
|
+
/** The fields a signature covers: every field but the signature itself, with the signer named. */
|
|
14
|
+
export declare function signedFieldsOf(fields: Readonly<Record<string, string>>, signer: string): Record<string, string>;
|
|
15
|
+
export {};
|
package/dist/wire/sig.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function formatSignature(algorithm, raw) {
|
|
2
|
+
return `${algorithm}:${raw}`;
|
|
3
|
+
}
|
|
4
|
+
export function parseSignature(value) {
|
|
5
|
+
const colon = value.lastIndexOf(':');
|
|
6
|
+
if (colon <= 0 || colon === value.length - 1)
|
|
7
|
+
return null;
|
|
8
|
+
return { algorithm: value.slice(0, colon), raw: value.slice(colon + 1) };
|
|
9
|
+
}
|
|
10
|
+
/** The fields a signature covers: every field but the signature itself, with the signer named. */
|
|
11
|
+
export function signedFieldsOf(fields, signer) {
|
|
12
|
+
const covered = { ...fields, by: signer };
|
|
13
|
+
delete covered['sig'];
|
|
14
|
+
return covered;
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PROTOCOL } from './index.ts';
|
|
2
|
+
export interface Request {
|
|
3
|
+
readonly protocol: typeof PROTOCOL;
|
|
4
|
+
readonly verb: 'describe' | 'read' | 'render';
|
|
5
|
+
readonly rootScope: string;
|
|
6
|
+
readonly files: readonly {
|
|
7
|
+
readonly place: string;
|
|
8
|
+
readonly text: string;
|
|
9
|
+
}[];
|
|
10
|
+
readonly held?: readonly (readonly [string, string])[];
|
|
11
|
+
readonly region?: string;
|
|
12
|
+
readonly at?: number;
|
|
13
|
+
readonly name?: string;
|
|
14
|
+
readonly lines?: readonly string[];
|
|
15
|
+
readonly reads?: readonly string[];
|
|
16
|
+
readonly shape?: string;
|
|
17
|
+
readonly regions?: Readonly<Record<string, {
|
|
18
|
+
readonly lines: readonly string[];
|
|
19
|
+
readonly receipts: readonly string[];
|
|
20
|
+
}>>;
|
|
21
|
+
}
|
|
22
|
+
export interface Response {
|
|
23
|
+
readonly protocol: typeof PROTOCOL;
|
|
24
|
+
readonly kind: 'fact' | 'abstain' | 'refuse';
|
|
25
|
+
readonly claims?: readonly unknown[];
|
|
26
|
+
readonly lines?: readonly string[];
|
|
27
|
+
readonly why?: string;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Method, MeasureForm as Form } from '@lapxo/obligations';
|
|
2
|
+
import type { Poset, MeetSemilattice, JoinSemilattice, Lattice } from '@lapxo/obligations/lattice';
|
|
3
|
+
export type { Method, Form, Poset, MeetSemilattice, JoinSemilattice, Lattice };
|
|
4
|
+
export { PROTOCOL } from './line.ts';
|
|
5
|
+
import { PROTOCOL } from './line.ts';
|
|
6
|
+
type Measure = {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly unit?: string;
|
|
9
|
+
readonly exist?: readonly string[];
|
|
10
|
+
} & ({
|
|
11
|
+
readonly form: 'ladder';
|
|
12
|
+
readonly levels: readonly string[];
|
|
13
|
+
} | {
|
|
14
|
+
readonly form: 'alphabet';
|
|
15
|
+
readonly tokens: readonly string[];
|
|
16
|
+
} | {
|
|
17
|
+
readonly form: 'continuous';
|
|
18
|
+
readonly lo: number | null;
|
|
19
|
+
readonly hi: number | null;
|
|
20
|
+
});
|
|
21
|
+
export type Bound = {
|
|
22
|
+
readonly kind: 'band';
|
|
23
|
+
readonly floor: string;
|
|
24
|
+
readonly ceiling: string;
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: 'enumerated';
|
|
27
|
+
readonly values: readonly string[];
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'interval';
|
|
30
|
+
readonly lo: number | null;
|
|
31
|
+
readonly hi: number | null;
|
|
32
|
+
};
|
|
33
|
+
type Multiplicity = 'one' | 'many' | 'unknown';
|
|
34
|
+
interface Reference {
|
|
35
|
+
readonly rel: 'within' | 'copies' | 'derives';
|
|
36
|
+
readonly to: readonly string[];
|
|
37
|
+
readonly by: 'inferred' | 'declared';
|
|
38
|
+
}
|
|
39
|
+
interface Authority {
|
|
40
|
+
readonly author: string;
|
|
41
|
+
readonly scopePrefix: string;
|
|
42
|
+
readonly signature?: string;
|
|
43
|
+
}
|
|
44
|
+
interface Support {
|
|
45
|
+
readonly observed: number;
|
|
46
|
+
readonly supporters: number;
|
|
47
|
+
readonly method: Method;
|
|
48
|
+
readonly channel: string;
|
|
49
|
+
readonly control: 'ok' | 'UNRELIABLE';
|
|
50
|
+
readonly excludedRatio?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface Claim {
|
|
53
|
+
readonly scope: string;
|
|
54
|
+
readonly measure: string;
|
|
55
|
+
readonly bound: Bound;
|
|
56
|
+
readonly role: 'writes' | 'reads' | 'demands';
|
|
57
|
+
readonly by: string;
|
|
58
|
+
readonly at: string;
|
|
59
|
+
readonly unit?: string;
|
|
60
|
+
readonly epoch?: string;
|
|
61
|
+
readonly multiplicity?: Multiplicity;
|
|
62
|
+
readonly reference?: Reference | null;
|
|
63
|
+
readonly support?: Support;
|
|
64
|
+
readonly extra?: Readonly<Record<string, string>>;
|
|
65
|
+
}
|
|
66
|
+
export interface Lock {
|
|
67
|
+
readonly protocol: typeof PROTOCOL;
|
|
68
|
+
readonly measures?: readonly Measure[];
|
|
69
|
+
readonly claims?: readonly Claim[];
|
|
70
|
+
readonly authorities?: readonly Authority[];
|
|
71
|
+
}
|
|
72
|
+
export type { Outcome } from './outcome.ts';
|
|
73
|
+
export { fact, abstain, refuse } from './outcome.ts';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Bound } from './types.ts';
|
|
2
|
+
export declare function unpipe(value: string): readonly string[] | null;
|
|
3
|
+
/**
|
|
4
|
+
* A number as the wire writes it, the only spelling it reads: decimal digits, a minus when negative and a fraction
|
|
5
|
+
* when it has one, never an exponent, a hex prefix, a leading dot or padding. The digits are the shortest that read
|
|
6
|
+
* back to the number, so a second head in any language writes the same bytes; a number that is not finite has none.
|
|
7
|
+
*/
|
|
8
|
+
export declare function decimal(n: number): string;
|
|
9
|
+
export interface FormGrammar {
|
|
10
|
+
readonly form: string;
|
|
11
|
+
readonly parse: (value: string) => Bound | null;
|
|
12
|
+
}
|
|
13
|
+
export declare function boundOf(form: string, value: string, grammars?: readonly FormGrammar[]): Bound | null;
|
|
14
|
+
export declare const BOUND_FORMS: readonly string[];
|
|
15
|
+
export declare const REF_RELATIONS: readonly ["within", "copies", "derives"];
|
|
16
|
+
export interface Refusal {
|
|
17
|
+
readonly kind: 'refuse';
|
|
18
|
+
readonly scope: string;
|
|
19
|
+
readonly measure: string;
|
|
20
|
+
readonly why: string;
|
|
21
|
+
readonly witness: string;
|
|
22
|
+
readonly at?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function unpipe(value) {
|
|
2
|
+
const out = [];
|
|
3
|
+
let at = '';
|
|
4
|
+
for (let i = 0; i < value.length; i++) {
|
|
5
|
+
const c = value[i];
|
|
6
|
+
if (c === '\\') {
|
|
7
|
+
const next = value[i + 1];
|
|
8
|
+
if (next !== '|' && next !== '\\')
|
|
9
|
+
return null;
|
|
10
|
+
at += next;
|
|
11
|
+
i += 1;
|
|
12
|
+
}
|
|
13
|
+
else if (c === '|') {
|
|
14
|
+
out.push(at);
|
|
15
|
+
at = '';
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
at += c;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
out.push(at);
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
const OPEN = '*';
|
|
25
|
+
const num = (s) => {
|
|
26
|
+
if (!/^-?\d+(\.\d+)?$/.test(s))
|
|
27
|
+
return null;
|
|
28
|
+
const n = Number(s);
|
|
29
|
+
return isFinite(n) ? n : null;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A number as the wire writes it, the only spelling it reads: decimal digits, a minus when negative and a fraction
|
|
33
|
+
* when it has one, never an exponent, a hex prefix, a leading dot or padding. The digits are the shortest that read
|
|
34
|
+
* back to the number, so a second head in any language writes the same bytes; a number that is not finite has none.
|
|
35
|
+
*/
|
|
36
|
+
export function decimal(n) {
|
|
37
|
+
if (!isFinite(n))
|
|
38
|
+
throw new Error(`REFUSE·wire decimal: \`${n}\` is not a finite number`);
|
|
39
|
+
const got = /^(-?)(\d)(?:\.(\d+))?e([+-])(\d+)$/.exec(String(n));
|
|
40
|
+
if (got === null)
|
|
41
|
+
return String(n);
|
|
42
|
+
const [, sign = '', lead = '', rest = '', ahead = '', exp = '0'] = got;
|
|
43
|
+
return ahead === '+' ? `${sign}${lead}${rest}${'0'.repeat(Number(exp) - rest.length)}` : `${sign}0.${'0'.repeat(Number(exp) - 1)}${lead}${rest}`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The forms this reader parses, each with the grammar that reads its value. The alphabet of forms is what these
|
|
47
|
+
* implement — never a list kept beside them, which the lock could move and the list could not.
|
|
48
|
+
*/
|
|
49
|
+
const GRAMMARS = {
|
|
50
|
+
interval: (value) => {
|
|
51
|
+
const at = value.indexOf('..');
|
|
52
|
+
if (at < 0)
|
|
53
|
+
return null;
|
|
54
|
+
const lo = value.slice(0, at);
|
|
55
|
+
const hi = value.slice(at + 2);
|
|
56
|
+
const l = lo === OPEN ? null : num(lo);
|
|
57
|
+
const h = hi === OPEN ? null : num(hi);
|
|
58
|
+
if ((lo !== OPEN && l === null) || (hi !== OPEN && h === null))
|
|
59
|
+
return null;
|
|
60
|
+
return { kind: 'interval', lo: l, hi: h };
|
|
61
|
+
},
|
|
62
|
+
alphabet: (value) => {
|
|
63
|
+
const values = unpipe(value);
|
|
64
|
+
return values === null ? null : { kind: 'enumerated', values };
|
|
65
|
+
},
|
|
66
|
+
ladder: (value) => {
|
|
67
|
+
const parts = unpipe(value);
|
|
68
|
+
if (parts === null || parts.length !== 2)
|
|
69
|
+
return null;
|
|
70
|
+
return { kind: 'band', floor: parts[0] ?? '', ceiling: parts[1] ?? '' };
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
export function boundOf(form, value, grammars) {
|
|
74
|
+
const extra = Array.isArray(grammars) ? grammars.find((g) => g.form === form) : undefined;
|
|
75
|
+
if (extra)
|
|
76
|
+
return extra.parse(value);
|
|
77
|
+
return GRAMMARS[form]?.(value) ?? null;
|
|
78
|
+
}
|
|
79
|
+
export const BOUND_FORMS = Object.keys(GRAMMARS);
|
|
80
|
+
export const REF_RELATIONS = ['within', 'copies', 'derives'];
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lapxo/topos",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bound's SDK: the wire, the forms, the contract and the shell you build a world with — a reader of any source or a renderer of any artefact, in an afternoon.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Lapxo",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Lapxo/topos.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"topos",
|
|
14
|
+
"lock",
|
|
15
|
+
"wire",
|
|
16
|
+
"capsule"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.12"
|
|
21
|
+
},
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/readers/index.d.ts",
|
|
25
|
+
"default": "./dist/readers/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./authority": {
|
|
28
|
+
"types": "./dist/authority/index.d.ts",
|
|
29
|
+
"default": "./dist/authority/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./capsule": {
|
|
32
|
+
"types": "./dist/capsule/index.d.ts",
|
|
33
|
+
"default": "./dist/capsule/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./contract": {
|
|
36
|
+
"types": "./dist/contract/index.d.ts",
|
|
37
|
+
"default": "./dist/contract/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./forms": {
|
|
40
|
+
"types": "./dist/forms/index.d.ts",
|
|
41
|
+
"default": "./dist/forms/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./readers": {
|
|
44
|
+
"types": "./dist/readers/index.d.ts",
|
|
45
|
+
"default": "./dist/readers/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./wire": {
|
|
48
|
+
"types": "./dist/wire/index.d.ts",
|
|
49
|
+
"default": "./dist/wire/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./package.json": "./package.json"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE",
|
|
57
|
+
"TARGET.bound"
|
|
58
|
+
],
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc --build"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@lapxo/obligations": "^0.3.5"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"typescript": "^5.9.3"
|
|
67
|
+
}
|
|
68
|
+
}
|