functionalscript 0.45.0 → 0.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/fjs/asn.1/module.f.mjs +8 -1
- package/fjs/asn.1/proof.f.d.mts +10 -0
- package/fjs/asn.1/proof.f.mjs +16 -0
- package/fjs/basen/base128/module.f.mjs +22 -5
- package/fjs/bnf/data/module.f.d.mts +32 -6
- package/fjs/bnf/data/module.f.mjs +112 -7
- package/fjs/bnf/data/proof.f.d.mts +2 -0
- package/fjs/bnf/data/proof.f.mjs +74 -3
- package/fjs/bnf/data/types.d.ts +20 -2
- package/fjs/bnf/descent/module.f.d.mts +28 -14
- package/fjs/bnf/descent/module.f.mjs +122 -77
- package/fjs/bnf/descent/proof.f.d.mts +2 -0
- package/fjs/bnf/descent/proof.f.mjs +117 -31
- package/fjs/bnf/descent/types.d.ts +12 -14
- package/fjs/bnf/ll1/module.f.d.mts +64 -22
- package/fjs/bnf/ll1/module.f.mjs +214 -154
- package/fjs/bnf/ll1/proof.f.d.mts +15 -2
- package/fjs/bnf/ll1/proof.f.mjs +323 -149
- package/fjs/bnf/ll1/types.d.ts +16 -24
- package/fjs/bnf/matcher/module.f.d.mts +66 -0
- package/fjs/bnf/matcher/module.f.mjs +81 -0
- package/fjs/bnf/matcher/proof.f.d.mts +10 -0
- package/fjs/bnf/matcher/proof.f.mjs +79 -0
- package/fjs/bnf/matcher/types.d.ts +54 -0
- package/fjs/bnf/testlib.f.d.mts +31 -0
- package/fjs/bnf/testlib.f.mjs +80 -0
- package/fjs/cas/cli/module.f.d.mts +1 -1
- package/fjs/cas/cli/module.f.mjs +14 -20
- package/fjs/cas/cli/proof.f.d.mts +1 -3
- package/fjs/cas/cli/proof.f.mjs +44 -33
- package/fjs/cas/evo/module.f.d.mts +64 -18
- package/fjs/cas/evo/module.f.mjs +148 -70
- package/fjs/cas/evo/proof.f.d.mts +10 -1
- package/fjs/cas/evo/proof.f.mjs +305 -223
- package/fjs/cas/evo/types.d.ts +45 -9
- package/fjs/cas/module.f.d.mts +18 -32
- package/fjs/cas/module.f.mjs +129 -128
- package/fjs/cas/proof.f.d.mts +11 -8
- package/fjs/cas/proof.f.mjs +259 -147
- package/fjs/cas/types.d.ts +24 -10
- package/fjs/ci/config/module.f.d.mts +2 -2
- package/fjs/ci/config/module.f.mjs +2 -2
- package/fjs/ci/module.f.d.mts +5 -5
- package/fjs/ci/module.f.mjs +8 -7
- package/fjs/ci/nix/module.f.d.mts +7 -5
- package/fjs/ci/nix/module.f.mjs +13 -12
- package/fjs/ci/nix/proof.f.mjs +2 -2
- package/fjs/ci/proof.f.mjs +9 -5
- package/fjs/cli/module.f.d.mts +4 -6
- package/fjs/cli/module.f.mjs +4 -8
- package/fjs/cli/proof.f.mjs +17 -16
- package/fjs/cli/types.d.ts +2 -3
- package/fjs/common/monoid/types.d.ts +1 -1
- package/fjs/crypto/hmac/module.f.mjs +2 -2
- package/fjs/crypto/sha2/module.f.mjs +3 -1
- package/fjs/crypto/sha2/proof.f.d.mts +1 -0
- package/fjs/crypto/sha2/proof.f.mjs +24 -0
- package/fjs/crypto/sha2/types.d.ts +11 -0
- package/fjs/crypto/sign/module.f.mjs +2 -2
- package/fjs/dev/module.f.d.mts +13 -4
- package/fjs/dev/module.f.mjs +56 -27
- package/fjs/dev/update/module.f.d.mts +9 -4
- package/fjs/dev/update/module.f.mjs +14 -10
- package/fjs/dev/update/proof.f.d.mts +1 -3
- package/fjs/dev/update/proof.f.mjs +10 -5
- package/fjs/djs/module.f.d.mts +13 -5
- package/fjs/djs/module.f.mjs +31 -16
- package/fjs/djs/parser/module.f.d.mts +13 -3
- package/fjs/djs/parser/module.f.mjs +117 -16
- package/fjs/djs/parser/proof.f.d.mts +5 -1
- package/fjs/djs/parser/proof.f.mjs +274 -12
- package/fjs/djs/parser/types.d.ts +7 -1
- package/fjs/djs/proof.f.d.mts +18 -2
- package/fjs/djs/proof.f.mjs +187 -12
- package/fjs/djs/serializer/module.f.d.mts +25 -8
- package/fjs/djs/serializer/module.f.mjs +61 -15
- package/fjs/djs/serializer/proof.f.d.mts +5 -0
- package/fjs/djs/serializer/proof.f.mjs +23 -1
- package/fjs/djs/tokenizer/module.f.d.mts +17 -5
- package/fjs/djs/tokenizer/module.f.mjs +99 -60
- package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
- package/fjs/djs/tokenizer/proof.f.mjs +74 -67
- package/fjs/djs/transpiler/module.f.d.mts +12 -7
- package/fjs/djs/transpiler/module.f.mjs +82 -56
- package/fjs/djs/transpiler/types.d.ts +7 -3
- package/fjs/djs/types.d.ts +7 -1
- package/fjs/effects/list/module.f.d.mts +14 -11
- package/fjs/effects/list/module.f.mjs +13 -11
- package/fjs/effects/list/types.d.ts +27 -7
- package/fjs/effects/memory/module.f.d.mts +2 -1
- package/fjs/effects/memory/module.f.mjs +3 -4
- package/fjs/effects/memory/proof.f.mjs +13 -7
- package/fjs/effects/memory/types.d.ts +4 -3
- package/fjs/effects/mock/module.f.d.mts +20 -4
- package/fjs/effects/mock/module.f.mjs +37 -5
- package/fjs/effects/mock/types.d.ts +10 -1
- package/fjs/effects/module.d.mts +4 -2
- package/fjs/effects/module.f.d.mts +466 -280
- package/fjs/effects/module.f.mjs +537 -299
- package/fjs/effects/module.mjs +2 -1
- package/fjs/effects/node/memory/module.d.mts +4 -2
- package/fjs/effects/node/memory/module.mjs +6 -3
- package/fjs/effects/node/memory/proof.mjs +10 -4
- package/fjs/effects/node/module.d.mts +4 -3
- package/fjs/effects/node/module.f.d.mts +168 -33
- package/fjs/effects/node/module.f.mjs +256 -52
- package/fjs/effects/node/module.mjs +57 -34
- package/fjs/effects/node/proof.f.d.mts +28 -2
- package/fjs/effects/node/proof.f.mjs +161 -42
- package/fjs/effects/node/types.d.ts +106 -18
- package/fjs/effects/node/virtual/module.f.d.mts +18 -4
- package/fjs/effects/node/virtual/module.f.mjs +110 -68
- package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
- package/fjs/effects/node/virtual/proof.f.mjs +190 -9
- package/fjs/effects/proof.f.d.mts +69 -37
- package/fjs/effects/proof.f.mjs +410 -130
- package/fjs/effects/types.d.ts +161 -33
- package/fjs/emergent_testing/module.f.d.mts +19 -12
- package/fjs/emergent_testing/module.f.mjs +93 -33
- package/fjs/emergent_testing/proof.f.d.mts +21 -7
- package/fjs/emergent_testing/proof.f.mjs +166 -32
- package/fjs/emergent_testing/types.d.ts +22 -4
- package/fjs/fsm/module.f.d.mts +14 -4
- package/fjs/fsm/module.f.mjs +54 -37
- package/fjs/fsm/proof.f.d.mts +2 -0
- package/fjs/fsm/proof.f.mjs +83 -114
- package/fjs/js/keywords/module.f.d.mts +52 -0
- package/fjs/js/keywords/module.f.mjs +72 -0
- package/fjs/js/keywords/proof.f.d.mts +3 -0
- package/fjs/js/keywords/proof.f.mjs +13 -0
- package/fjs/js/tokenizer/module.f.d.mts +26 -6
- package/fjs/js/tokenizer/module.f.mjs +145 -159
- package/fjs/js/tokenizer/proof.f.d.mts +1 -0
- package/fjs/js/tokenizer/proof.f.mjs +54 -24
- package/fjs/js/tokenizer/types.d.ts +33 -24
- package/fjs/mcp/cas/module.f.d.mts +1 -6
- package/fjs/mcp/cas/module.f.mjs +55 -51
- package/fjs/mcp/cas/proof.f.d.mts +15 -0
- package/fjs/mcp/cas/proof.f.mjs +174 -0
- package/fjs/mcp/evo/module.f.d.mts +19 -10
- package/fjs/mcp/evo/module.f.mjs +48 -27
- package/fjs/mcp/evo/proof.f.d.mts +6 -1
- package/fjs/mcp/evo/proof.f.mjs +115 -31
- package/fjs/mcp/module.f.d.mts +4 -4
- package/fjs/mcp/module.f.mjs +6 -6
- package/fjs/mcp/proof.f.d.mts +5 -3
- package/fjs/mcp/proof.f.mjs +112 -46
- package/fjs/media/html/module.f.mjs +1 -1
- package/fjs/media/json/extended/module.f.d.mts +82 -0
- package/fjs/media/json/extended/module.f.mjs +153 -0
- package/fjs/media/json/extended/proof.f.d.mts +42 -0
- package/fjs/media/json/extended/proof.f.mjs +127 -0
- package/fjs/media/json/extended/types.d.ts +23 -0
- package/fjs/media/json/module.f.d.mts +8 -2
- package/fjs/media/json/module.f.mjs +43 -41
- package/fjs/media/json/number/module.f.d.mts +59 -0
- package/fjs/media/json/number/module.f.mjs +136 -0
- package/fjs/media/json/number/proof.f.d.mts +24 -0
- package/fjs/media/json/number/proof.f.mjs +86 -0
- package/fjs/media/json/number/types.d.ts +28 -0
- package/fjs/media/json/parser/module.f.d.mts +25 -13
- package/fjs/media/json/parser/module.f.mjs +114 -70
- package/fjs/media/json/parser/proof.f.d.mts +5 -0
- package/fjs/media/json/parser/proof.f.mjs +31 -1
- package/fjs/media/json/parser/types.d.ts +33 -14
- package/fjs/media/json/rtti/module.f.d.mts +1 -1
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/rtti/proof.f.mjs +9 -9
- package/fjs/media/json/schema/module.f.mjs +3 -13
- package/fjs/media/json/schema/proof.f.d.mts +0 -1
- package/fjs/media/json/schema/proof.f.mjs +1 -2
- package/fjs/media/json/serializer/module.f.d.mts +32 -1
- package/fjs/media/json/serializer/module.f.mjs +64 -2
- package/fjs/media/json/tokenizer/module.f.mjs +7 -3
- package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
- package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
- package/fjs/media/json/types.d.ts +36 -10
- package/fjs/media/lock/module.f.d.mts +100 -0
- package/fjs/media/lock/module.f.mjs +125 -0
- package/fjs/media/lock/proof.f.d.mts +33 -0
- package/fjs/media/lock/proof.f.mjs +196 -0
- package/fjs/media/lock/types.d.ts +15 -0
- package/fjs/media/module.f.d.mts +6 -5
- package/fjs/media/module.f.mjs +8 -7
- package/fjs/media/note/module.f.d.mts +121 -0
- package/fjs/media/note/module.f.mjs +131 -0
- package/fjs/media/note/proof.f.d.mts +29 -0
- package/fjs/media/note/proof.f.mjs +150 -0
- package/fjs/media/note/types.d.ts +10 -0
- package/fjs/media/proof.f.d.mts +4 -1
- package/fjs/media/proof.f.mjs +40 -21
- package/fjs/media/revision/module.f.d.mts +78 -7
- package/fjs/media/revision/module.f.mjs +119 -12
- package/fjs/media/revision/proof.f.d.mts +10 -0
- package/fjs/media/revision/proof.f.mjs +88 -0
- package/fjs/media/revision/types.d.ts +34 -5
- package/fjs/media/type/module.f.d.mts +33 -15
- package/fjs/media/type/module.f.mjs +35 -29
- package/fjs/media/type/proof.f.d.mts +2 -1
- package/fjs/media/type/proof.f.mjs +30 -10
- package/fjs/module.f.mjs +29 -8
- package/fjs/nanvm/proof.f.mjs +3 -3
- package/fjs/nanvm/rust/module.f.mjs +1 -1
- package/fjs/nanvm/update/module.f.d.mts +4 -4
- package/fjs/nanvm/update/module.f.mjs +8 -9
- package/fjs/nanvm/update/proof.f.mjs +4 -3
- package/fjs/proof.f.d.mts +3 -3
- package/fjs/proof.f.mjs +36 -10
- package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
- package/fjs/protocol/json_rpc/module.f.mjs +3 -3
- package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
- package/fjs/protocol/mcp/module.f.d.mts +10 -13
- package/fjs/protocol/mcp/module.f.mjs +74 -58
- package/fjs/protocol/mcp/proof.f.d.mts +13 -4
- package/fjs/protocol/mcp/proof.f.mjs +193 -61
- package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
- package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
- package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
- package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
- package/fjs/protocol/mcp/types.d.ts +17 -6
- package/fjs/sul/id/module.f.d.mts +0 -1
- package/fjs/sul/id/module.f.mjs +2 -3
- package/fjs/sul/level/hash/module.f.mjs +2 -1
- package/fjs/sul/level/hash/proof.f.mjs +2 -2
- package/fjs/sul/module.f.mjs +18 -13
- package/fjs/text/code_point/module.f.d.mts +8 -0
- package/fjs/text/code_point/module.f.mjs +8 -1
- package/fjs/text/code_point/proof.f.d.mts +1 -0
- package/fjs/text/code_point/proof.f.mjs +11 -0
- package/fjs/text/sgr/module.f.d.mts +4 -6
- package/fjs/text/sgr/module.f.mjs +6 -7
- package/fjs/text/utf16/module.f.mjs +7 -2
- package/fjs/text/utf16/proof.f.mjs +3 -3
- package/fjs/text/utf8/module.f.mjs +4 -2
- package/fjs/types/array/module.f.mjs +14 -2
- package/fjs/types/bit_vec/module.f.d.mts +0 -2
- package/fjs/types/bit_vec/module.f.mjs +46 -32
- package/fjs/types/bit_vec/proof.f.mjs +2 -2
- package/fjs/types/btree/remove/module.f.mjs +1 -1
- package/fjs/types/btree/set/module.f.mjs +9 -11
- package/fjs/types/btree/set/proof.f.mjs +12 -0
- package/fjs/types/byte_set/module.f.d.mts +11 -4
- package/fjs/types/byte_set/module.f.mjs +14 -6
- package/fjs/types/byte_set/proof.f.mjs +7 -7
- package/fjs/types/function/compare/module.f.mjs +10 -3
- package/fjs/types/list/module.f.d.mts +1 -1
- package/fjs/types/list/module.f.mjs +1 -1
- package/fjs/types/nullable/module.f.d.mts +18 -4
- package/fjs/types/nullable/module.f.mjs +21 -4
- package/fjs/types/nullable/proof.f.d.mts +4 -0
- package/fjs/types/nullable/proof.f.mjs +15 -0
- package/fjs/types/object/module.f.d.mts +12 -2
- package/fjs/types/object/module.f.mjs +11 -1
- package/fjs/types/patricia_trie/module.f.mjs +26 -11
- package/fjs/types/result/module.f.d.mts +3 -3
- package/fjs/types/result/module.f.mjs +3 -3
- package/fjs/types/rtti/common/module.f.d.mts +25 -28
- package/fjs/types/rtti/common/module.f.mjs +36 -32
- package/fjs/types/rtti/common/proof.f.mjs +5 -5
- package/fjs/types/rtti/data/module.f.d.mts +14 -0
- package/fjs/types/rtti/data/module.f.mjs +25 -2
- package/fjs/types/rtti/data/proof.f.d.mts +1 -0
- package/fjs/types/rtti/data/proof.f.mjs +25 -2
- package/fjs/types/rtti/parse/module.f.d.mts +24 -14
- package/fjs/types/rtti/parse/module.f.mjs +37 -28
- package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
- package/fjs/types/rtti/parse/proof.f.mjs +33 -14
- package/fjs/types/rtti/proof.f.mjs +3 -1
- package/fjs/types/rtti/ts/module.f.mjs +13 -17
- package/fjs/types/rtti/ts/types.d.ts +14 -1
- package/fjs/types/rtti/validate/module.f.d.mts +81 -19
- package/fjs/types/rtti/validate/module.f.mjs +107 -61
- package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
- package/fjs/types/rtti/validate/proof.f.mjs +186 -197
- package/fjs/types/sorted_set/module.f.d.mts +18 -0
- package/fjs/types/sorted_set/module.f.mjs +22 -0
- package/fjs/types/sorted_set/proof.f.d.mts +1 -0
- package/fjs/types/sorted_set/proof.f.mjs +16 -1
- package/fjs/types/uint8array/module.f.d.mts +1 -1
- package/fjs/types/uint8array/module.f.mjs +1 -1
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +4 -7
- package/fjs/website/proof.f.mjs +2 -1
- package/package.json +2 -2
- package/fjs/dev/package_json/module.f.d.mts +0 -39
- package/fjs/dev/package_json/module.f.mjs +0 -40
- package/fjs/dev/package_json/proof.f.d.mts +0 -6
- package/fjs/dev/package_json/proof.f.mjs +0 -32
- package/fjs/effects/eff/module.f.d.mts +0 -20
- package/fjs/effects/eff/module.f.mjs +0 -70
- package/fjs/effects/eff/proof.f.d.mts +0 -15
- package/fjs/effects/eff/proof.f.mjs +0 -69
- package/fjs/effects/eff/types.d.ts +0 -71
- package/fjs/types/rtti/validate/types.d.ts +0 -6
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vnd.fjs.note` — a human-authored text item (a note, todo, issue, calendar
|
|
3
|
+
* event, …) as a BLOB of its own.
|
|
4
|
+
*
|
|
5
|
+
* The format is deliberately minimal: the dialect tag, the text, and
|
|
6
|
+
* optionally the subjects the item depends on and a priority. Everything
|
|
7
|
+
* else a richer item needs — a title, tags, dates, a status — is a future
|
|
8
|
+
* **optional** field:
|
|
9
|
+
* rtti structs are open, so additive extension keeps the tag (see the
|
|
10
|
+
* versioning rule in `fjs/media/revision/README.md`), and starting minimal is
|
|
11
|
+
* what keeps every extension additive.
|
|
12
|
+
*
|
|
13
|
+
* Like `vnd.fjs.lock`, a note is a **value**, not a step: no timestamps, no
|
|
14
|
+
* author, no history of its own. Edits over time are ordinary
|
|
15
|
+
* `vnd.fjs.revision` steps whose `subject` identifies the note and whose
|
|
16
|
+
* `snapshot` is one of these blobs, so no second history mechanism appears
|
|
17
|
+
* and `revision` stays the only one.
|
|
18
|
+
*
|
|
19
|
+
* This module is the pure format only: the rtti schema, the `dialect` tag,
|
|
20
|
+
* and decode/validate. Unlike its CAS siblings it has no hash fields, so
|
|
21
|
+
* there is no semantic refinement stage — structural validation is the whole
|
|
22
|
+
* check.
|
|
23
|
+
*
|
|
24
|
+
* See `README.md` for the full spec.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*
|
|
28
|
+
* @import { Unknown } from '../json/types.ts'
|
|
29
|
+
* @import { Result } from '../../types/result/types.ts'
|
|
30
|
+
* @import { ValidationError } from '../../types/rtti/common/types.ts'
|
|
31
|
+
* @import { DialectEntry } from '../types.ts'
|
|
32
|
+
* @import { Note, NoteError } from './types.ts'
|
|
33
|
+
*/
|
|
34
|
+
import type { Unknown } from '../json/types.ts';
|
|
35
|
+
import type { Result } from '../../types/result/types.ts';
|
|
36
|
+
import type { ValidationError } from '../../types/rtti/common/types.ts';
|
|
37
|
+
import type { DialectEntry } from '../types.ts';
|
|
38
|
+
import type { Note, NoteError } from './types.ts';
|
|
39
|
+
/**
|
|
40
|
+
* Format tag: names the dialect of this BLOB. The media type it is served
|
|
41
|
+
* with is derived mechanically: `application/` + `dialect` + `+json`.
|
|
42
|
+
*/
|
|
43
|
+
export declare const dialect: 'vnd.fjs.note';
|
|
44
|
+
/** The media type derived from {@link dialect}: `application/vnd.fjs.note+json`. */
|
|
45
|
+
export declare const mediaType: "application/vnd.fjs.note+json";
|
|
46
|
+
/**
|
|
47
|
+
* rtti schema for a `note` BLOB: the dialect tag, the text, and optionally
|
|
48
|
+
* the subjects the item depends on.
|
|
49
|
+
*
|
|
50
|
+
* `text` is **required** and may be `''`: an absent text and an empty one
|
|
51
|
+
* would otherwise be two spellings of one blob, and a blob whose only purpose
|
|
52
|
+
* is to hold text has nothing to say when it does not. Any string is valid —
|
|
53
|
+
* the format records the text and defines no markup for it; how a reader
|
|
54
|
+
* renders it (e.g. as Markdown) is the reader's decision.
|
|
55
|
+
*
|
|
56
|
+
* `dependencies` entries are **subject identity strings** — the vocabulary of
|
|
57
|
+
* `vnd.fjs.revision`'s `subject` and of lock-map keys — naming the mutable
|
|
58
|
+
* items this one depends on (a blocked-by todo, an issue's prerequisite).
|
|
59
|
+
* They are never content hashes: a dependency tracks the live item, and
|
|
60
|
+
* pinning it to immutable content is the revision layer's `lock`. Like
|
|
61
|
+
* `subject`, an identity string is unconstrained, so no semantic check
|
|
62
|
+
* applies. The field is optional because its absent value is the constant
|
|
63
|
+
* "depends on nothing"; an explicit `[]` says the same thing, and the format
|
|
64
|
+
* does not distinguish the two.
|
|
65
|
+
*
|
|
66
|
+
* `text` may reference an entry by its zero-based index in square brackets —
|
|
67
|
+
* `[0]` names `dependencies[0]` — so the entry order is **significant**:
|
|
68
|
+
* reordering or removing entries renumbers references. A bracketed integer
|
|
69
|
+
* that indexes no entry is ordinary text, not a broken reference, so the
|
|
70
|
+
* convention adds no validation stage (see the README).
|
|
71
|
+
*
|
|
72
|
+
* `priority` is the author's decision about the item's urgency, on the
|
|
73
|
+
* {@link priorities} scale — a closed literal union, so it too is enforced
|
|
74
|
+
* entirely structurally (a number would invite scale and range questions
|
|
75
|
+
* only a semantic check could answer). Absent means **unprioritized** — the
|
|
76
|
+
* author has not decided — which is the constant default that makes the
|
|
77
|
+
* field optional, and is deliberately distinct from any rank.
|
|
78
|
+
*/
|
|
79
|
+
/**
|
|
80
|
+
* The priority scale, most urgent first: `P1` is "drop everything", `P5` is
|
|
81
|
+
* "someday". The vocabulary of `todo/README.md`'s issue format, reused
|
|
82
|
+
* rather than invented, so one scale ranks the repository's own issues and a
|
|
83
|
+
* note blob alike. Widening it later (e.g. a `P0`) follows the fail-closed
|
|
84
|
+
* path the versioning rule allows: an older reader rejects the new rank
|
|
85
|
+
* rather than misreading it.
|
|
86
|
+
*/
|
|
87
|
+
export declare const priorities: readonly ['P1', 'P2', 'P3', 'P4', 'P5'];
|
|
88
|
+
export declare const noteSchema: {
|
|
89
|
+
readonly dialect: "vnd.fjs.note";
|
|
90
|
+
readonly text: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
91
|
+
readonly dependencies: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Type1<"array", import("../../types/rtti/types.ts")._Type0<"string">>, undefined]>;
|
|
92
|
+
readonly priority: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Or<["P1", "P2", "P3", "P4", "P5"]>, undefined]>;
|
|
93
|
+
};
|
|
94
|
+
/** Serializes a note canonically, sorting every object's property names.
|
|
95
|
+
* @type {(note: Note) => string}
|
|
96
|
+
*/
|
|
97
|
+
export declare const encodeText: (note: Note) => string;
|
|
98
|
+
/**
|
|
99
|
+
* Validates an already-parsed JSON value as a `note` BLOB. Structural (rtti)
|
|
100
|
+
* validation is the whole check: a note has no hash fields, so there is no
|
|
101
|
+
* semantic refinement stage and no `checkReferences` half.
|
|
102
|
+
*
|
|
103
|
+
* @type {(value: Unknown) => Result<Note, ValidationError>}
|
|
104
|
+
*/
|
|
105
|
+
export declare const validate: (value: Unknown) => Result<Note, ValidationError>;
|
|
106
|
+
/**
|
|
107
|
+
* Decodes `text` as a `note` BLOB: JSON-parses it, then validates it per
|
|
108
|
+
* {@link validate}. Detection is semantic, not syntactic — any JSON that
|
|
109
|
+
* satisfies the schema is a note, regardless of key order or whitespace.
|
|
110
|
+
*
|
|
111
|
+
* @type {(text: string) => Result<Note, NoteError>}
|
|
112
|
+
*/
|
|
113
|
+
export declare const decodeText: (text: string) => Result<Note, NoteError>;
|
|
114
|
+
/**
|
|
115
|
+
* This dialect as a registry entry for `fjs/media`'s `detect`. Registered
|
|
116
|
+
* with no refinement — structure alone decides the match — so a blob is
|
|
117
|
+
* detected as `vnd.fjs.note` exactly when {@link decodeText} would accept it.
|
|
118
|
+
*
|
|
119
|
+
* @type {DialectEntry}
|
|
120
|
+
*/
|
|
121
|
+
export declare const noteDialect: DialectEntry;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vnd.fjs.note` — a human-authored text item (a note, todo, issue, calendar
|
|
3
|
+
* event, …) as a BLOB of its own.
|
|
4
|
+
*
|
|
5
|
+
* The format is deliberately minimal: the dialect tag, the text, and
|
|
6
|
+
* optionally the subjects the item depends on and a priority. Everything
|
|
7
|
+
* else a richer item needs — a title, tags, dates, a status — is a future
|
|
8
|
+
* **optional** field:
|
|
9
|
+
* rtti structs are open, so additive extension keeps the tag (see the
|
|
10
|
+
* versioning rule in `fjs/media/revision/README.md`), and starting minimal is
|
|
11
|
+
* what keeps every extension additive.
|
|
12
|
+
*
|
|
13
|
+
* Like `vnd.fjs.lock`, a note is a **value**, not a step: no timestamps, no
|
|
14
|
+
* author, no history of its own. Edits over time are ordinary
|
|
15
|
+
* `vnd.fjs.revision` steps whose `subject` identifies the note and whose
|
|
16
|
+
* `snapshot` is one of these blobs, so no second history mechanism appears
|
|
17
|
+
* and `revision` stays the only one.
|
|
18
|
+
*
|
|
19
|
+
* This module is the pure format only: the rtti schema, the `dialect` tag,
|
|
20
|
+
* and decode/validate. Unlike its CAS siblings it has no hash fields, so
|
|
21
|
+
* there is no semantic refinement stage — structural validation is the whole
|
|
22
|
+
* check.
|
|
23
|
+
*
|
|
24
|
+
* See `README.md` for the full spec.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*
|
|
28
|
+
* @import { Unknown } from '../json/types.ts'
|
|
29
|
+
* @import { Result } from '../../types/result/types.ts'
|
|
30
|
+
* @import { ValidationError } from '../../types/rtti/common/types.ts'
|
|
31
|
+
* @import { DialectEntry } from '../types.ts'
|
|
32
|
+
* @import { Note, NoteError } from './types.ts'
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { array, option, or, string } from '../../types/rtti/module.f.mjs'
|
|
36
|
+
import { parse as rttiParse } from '../../types/rtti/parse/module.f.mjs'
|
|
37
|
+
import { parse as parseJson, stringify } from '../json/module.f.mjs'
|
|
38
|
+
import { okThen } from '../../types/result/module.f.mjs'
|
|
39
|
+
import { dialectEntry } from '../module.f.mjs'
|
|
40
|
+
import { sort } from '../../types/object/module.f.mjs'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Format tag: names the dialect of this BLOB. The media type it is served
|
|
44
|
+
* with is derived mechanically: `application/` + `dialect` + `+json`.
|
|
45
|
+
*/
|
|
46
|
+
export const dialect = /** @type {const} */ ('vnd.fjs.note')
|
|
47
|
+
|
|
48
|
+
/** The media type derived from {@link dialect}: `application/vnd.fjs.note+json`. */
|
|
49
|
+
export const mediaType = /** @type {const} */ (`application/${dialect}+json`)
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* rtti schema for a `note` BLOB: the dialect tag, the text, and optionally
|
|
53
|
+
* the subjects the item depends on.
|
|
54
|
+
*
|
|
55
|
+
* `text` is **required** and may be `''`: an absent text and an empty one
|
|
56
|
+
* would otherwise be two spellings of one blob, and a blob whose only purpose
|
|
57
|
+
* is to hold text has nothing to say when it does not. Any string is valid —
|
|
58
|
+
* the format records the text and defines no markup for it; how a reader
|
|
59
|
+
* renders it (e.g. as Markdown) is the reader's decision.
|
|
60
|
+
*
|
|
61
|
+
* `dependencies` entries are **subject identity strings** — the vocabulary of
|
|
62
|
+
* `vnd.fjs.revision`'s `subject` and of lock-map keys — naming the mutable
|
|
63
|
+
* items this one depends on (a blocked-by todo, an issue's prerequisite).
|
|
64
|
+
* They are never content hashes: a dependency tracks the live item, and
|
|
65
|
+
* pinning it to immutable content is the revision layer's `lock`. Like
|
|
66
|
+
* `subject`, an identity string is unconstrained, so no semantic check
|
|
67
|
+
* applies. The field is optional because its absent value is the constant
|
|
68
|
+
* "depends on nothing"; an explicit `[]` says the same thing, and the format
|
|
69
|
+
* does not distinguish the two.
|
|
70
|
+
*
|
|
71
|
+
* `text` may reference an entry by its zero-based index in square brackets —
|
|
72
|
+
* `[0]` names `dependencies[0]` — so the entry order is **significant**:
|
|
73
|
+
* reordering or removing entries renumbers references. A bracketed integer
|
|
74
|
+
* that indexes no entry is ordinary text, not a broken reference, so the
|
|
75
|
+
* convention adds no validation stage (see the README).
|
|
76
|
+
*
|
|
77
|
+
* `priority` is the author's decision about the item's urgency, on the
|
|
78
|
+
* {@link priorities} scale — a closed literal union, so it too is enforced
|
|
79
|
+
* entirely structurally (a number would invite scale and range questions
|
|
80
|
+
* only a semantic check could answer). Absent means **unprioritized** — the
|
|
81
|
+
* author has not decided — which is the constant default that makes the
|
|
82
|
+
* field optional, and is deliberately distinct from any rank.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* The priority scale, most urgent first: `P1` is "drop everything", `P5` is
|
|
86
|
+
* "someday". The vocabulary of `todo/README.md`'s issue format, reused
|
|
87
|
+
* rather than invented, so one scale ranks the repository's own issues and a
|
|
88
|
+
* note blob alike. Widening it later (e.g. a `P0`) follows the fail-closed
|
|
89
|
+
* path the versioning rule allows: an older reader rejects the new rank
|
|
90
|
+
* rather than misreading it.
|
|
91
|
+
*/
|
|
92
|
+
export const priorities = /** @type {const} */ (['P1', 'P2', 'P3', 'P4', 'P5'])
|
|
93
|
+
|
|
94
|
+
export const noteSchema = /** @type {const} */ ({
|
|
95
|
+
dialect,
|
|
96
|
+
text: string,
|
|
97
|
+
dependencies: option(array(string)),
|
|
98
|
+
priority: option(or(...priorities)),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/** Serializes a note canonically, sorting every object's property names.
|
|
102
|
+
* @type {(note: Note) => string}
|
|
103
|
+
*/
|
|
104
|
+
export const encodeText = stringify(sort)
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validates an already-parsed JSON value as a `note` BLOB. Structural (rtti)
|
|
108
|
+
* validation is the whole check: a note has no hash fields, so there is no
|
|
109
|
+
* semantic refinement stage and no `checkReferences` half.
|
|
110
|
+
*
|
|
111
|
+
* @type {(value: Unknown) => Result<Note, ValidationError>}
|
|
112
|
+
*/
|
|
113
|
+
export const validate = rttiParse(noteSchema)
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Decodes `text` as a `note` BLOB: JSON-parses it, then validates it per
|
|
117
|
+
* {@link validate}. Detection is semantic, not syntactic — any JSON that
|
|
118
|
+
* satisfies the schema is a note, regardless of key order or whitespace.
|
|
119
|
+
*
|
|
120
|
+
* @type {(text: string) => Result<Note, NoteError>}
|
|
121
|
+
*/
|
|
122
|
+
export const decodeText = text => okThen(validate)(parseJson(text))
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* This dialect as a registry entry for `fjs/media`'s `detect`. Registered
|
|
126
|
+
* with no refinement — structure alone decides the match — so a blob is
|
|
127
|
+
* detected as `vnd.fjs.note` exactly when {@link decodeText} would accept it.
|
|
128
|
+
*
|
|
129
|
+
* @type {DialectEntry}
|
|
130
|
+
*/
|
|
131
|
+
export const noteDialect = dialectEntry(noteSchema)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const proof: {
|
|
2
|
+
dialectAndMediaType: () => void;
|
|
3
|
+
priorityScale: () => void;
|
|
4
|
+
validate: {
|
|
5
|
+
minimalNoteAccepted: () => void;
|
|
6
|
+
emptyTextAccepted: () => void;
|
|
7
|
+
missingTextRejected: () => void;
|
|
8
|
+
nonStringTextRejected: () => void;
|
|
9
|
+
dependenciesAccepted: () => void;
|
|
10
|
+
emptyDependenciesAccepted: () => void;
|
|
11
|
+
nonArrayDependenciesRejected: () => void;
|
|
12
|
+
nonStringDependencyRejected: () => void;
|
|
13
|
+
priorityAccepted: () => void;
|
|
14
|
+
unknownPriorityRejected: () => void;
|
|
15
|
+
numericPriorityRejected: () => void;
|
|
16
|
+
unknownFieldsIgnored: () => void;
|
|
17
|
+
otherDialectRejected: () => void;
|
|
18
|
+
};
|
|
19
|
+
decodeText: {
|
|
20
|
+
validJson: () => void;
|
|
21
|
+
keyOrderIndependent: () => void;
|
|
22
|
+
malformedJsonRejected: () => void;
|
|
23
|
+
ordinaryJsonRejected: () => void;
|
|
24
|
+
};
|
|
25
|
+
encodeText: {
|
|
26
|
+
sortsPropertiesLexicographically: () => void;
|
|
27
|
+
};
|
|
28
|
+
noteDialectTag: () => void;
|
|
29
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { assert, assertEq } from '../../asserts/module.f.mjs'
|
|
2
|
+
import { dialect, mediaType, decodeText, encodeText, noteDialect, priorities, validate } from './module.f.mjs'
|
|
3
|
+
import { dialect as lockDialect } from '../lock/module.f.mjs'
|
|
4
|
+
|
|
5
|
+
export const proof = {
|
|
6
|
+
dialectAndMediaType: () => {
|
|
7
|
+
assertEq(dialect, 'vnd.fjs.note')
|
|
8
|
+
assertEq(mediaType, 'application/vnd.fjs.note+json')
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
// The published scale, most urgent first — `todo/README.md`'s vocabulary.
|
|
12
|
+
priorityScale: () => {
|
|
13
|
+
assertEq(priorities.join(','), 'P1,P2,P3,P4,P5')
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
validate: {
|
|
17
|
+
// The ordinary case: the tag and the text.
|
|
18
|
+
minimalNoteAccepted: () => {
|
|
19
|
+
const r = validate({ dialect, text: 'buy milk' })
|
|
20
|
+
assert(r[0] === 'ok', ['expected ok', r])
|
|
21
|
+
assertEq(r[1].text, 'buy milk')
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// `text` may be empty — required rather than optional, so an absent
|
|
25
|
+
// text and an empty one are not two spellings of one blob.
|
|
26
|
+
emptyTextAccepted: () => {
|
|
27
|
+
const [t] = validate({ dialect, text: '' })
|
|
28
|
+
assertEq(t, 'ok')
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// `text` is required: a blob carrying only the tag is not a note.
|
|
32
|
+
missingTextRejected: () => {
|
|
33
|
+
const [t] = validate({ dialect })
|
|
34
|
+
assertEq(t, 'error')
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// The text is a string, nothing else.
|
|
38
|
+
nonStringTextRejected: () => {
|
|
39
|
+
const [t] = validate({ dialect, text: 42 })
|
|
40
|
+
assertEq(t, 'error')
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Dependencies are subject identity strings, unconstrained like
|
|
44
|
+
// `vnd.fjs.revision`'s `subject` — no semantic check applies. The
|
|
45
|
+
// text may reference them by index (`[0]`, `[1]`), which is a
|
|
46
|
+
// reading convention, not a validation rule: this blob validates the
|
|
47
|
+
// same with any text.
|
|
48
|
+
dependenciesAccepted: () => {
|
|
49
|
+
const r = validate({ dialect, text: 'ship it once [0] and [1] land', dependencies: ['write the spec', 'review'] })
|
|
50
|
+
assert(r[0] === 'ok', ['expected ok', r])
|
|
51
|
+
assertEq(r[1].dependencies?.length, 2)
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// `[]` and an absent field both say "depends on nothing"; the format
|
|
55
|
+
// does not distinguish them, and both validate.
|
|
56
|
+
emptyDependenciesAccepted: () => {
|
|
57
|
+
const [t] = validate({ dialect, text: 'hi', dependencies: [] })
|
|
58
|
+
assertEq(t, 'ok')
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// The field is a list of strings, nothing else.
|
|
62
|
+
nonArrayDependenciesRejected: () => {
|
|
63
|
+
const [t] = validate({ dialect, text: 'hi', dependencies: 'write the spec' })
|
|
64
|
+
assertEq(t, 'error')
|
|
65
|
+
},
|
|
66
|
+
nonStringDependencyRejected: () => {
|
|
67
|
+
const [t] = validate({ dialect, text: 'hi', dependencies: [42] })
|
|
68
|
+
assertEq(t, 'error')
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// A priority is one of the closed `priorities` literals — the
|
|
72
|
+
// author's urgency decision, enforced entirely structurally.
|
|
73
|
+
priorityAccepted: () => {
|
|
74
|
+
const r = validate({ dialect, text: 'fix the build', priority: 'P1' })
|
|
75
|
+
assert(r[0] === 'ok', ['expected ok', r])
|
|
76
|
+
assertEq(r[1].priority, 'P1')
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// The scale is closed: an unknown rank fails structurally rather than
|
|
80
|
+
// passing as a free-form string — the fail-closed widening path.
|
|
81
|
+
unknownPriorityRejected: () => {
|
|
82
|
+
const [t] = validate({ dialect, text: 'hi', priority: 'P0' })
|
|
83
|
+
assertEq(t, 'error')
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// The rank is a literal, not a number: `1` is not `'P1'`.
|
|
87
|
+
numericPriorityRejected: () => {
|
|
88
|
+
const [t] = validate({ dialect, text: 'hi', priority: 1 })
|
|
89
|
+
assertEq(t, 'error')
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// rtti structs are open, so unknown fields are ignored rather than
|
|
93
|
+
// rejected — the additive forward-compatibility path every future
|
|
94
|
+
// extension (title, tags, dates, …) relies on.
|
|
95
|
+
unknownFieldsIgnored: () => {
|
|
96
|
+
const [t] = validate({ dialect, text: 'call Bob', title: 'todo', done: true })
|
|
97
|
+
assertEq(t, 'ok')
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Another dialect's blob is not a note: the tag is matched as an
|
|
101
|
+
// exact literal.
|
|
102
|
+
otherDialectRejected: () => {
|
|
103
|
+
const [t] = validate({ dialect: lockDialect, text: 'hi' })
|
|
104
|
+
assertEq(t, 'error')
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
decodeText: {
|
|
109
|
+
validJson: () => {
|
|
110
|
+
const r = decodeText(`{"dialect":"${dialect}","text":"buy milk"}`)
|
|
111
|
+
assert(r[0] === 'ok', ['expected ok', r])
|
|
112
|
+
assertEq(r[1].text, 'buy milk')
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Key order carries no meaning: the JSON is parsed and the parsed
|
|
116
|
+
// value validated, so `dialect` need not come first.
|
|
117
|
+
keyOrderIndependent: () => {
|
|
118
|
+
const [t] = decodeText(`{"text":"hi","dialect":"${dialect}"}`)
|
|
119
|
+
assertEq(t, 'ok')
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
malformedJsonRejected: () => {
|
|
123
|
+
const [t] = decodeText('{not json')
|
|
124
|
+
assertEq(t, 'error')
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
ordinaryJsonRejected: () => {
|
|
128
|
+
const [t] = decodeText('{"hello":"world"}')
|
|
129
|
+
assertEq(t, 'error')
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
encodeText: {
|
|
134
|
+
// Two blobs differing only in property order converge on one byte
|
|
135
|
+
// sequence, so they address the same CAS blob. `dependencies` sorts
|
|
136
|
+
// ahead of `dialect`, and its array order is preserved — arrays retain
|
|
137
|
+
// their declared order under canonical serialization.
|
|
138
|
+
sortsPropertiesLexicographically: () => {
|
|
139
|
+
const decoded = decodeText(`{"text":"hi","dependencies":["b","a"],"dialect":"${dialect}"}`)
|
|
140
|
+
assert(decoded[0] === 'ok', ['expected ok', decoded])
|
|
141
|
+
assertEq(encodeText(decoded[1]), `{"dependencies":["b","a"],"dialect":"${dialect}","text":"hi"}`)
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// The registry entry carries the schema's own tag; matching is exercised
|
|
146
|
+
// end to end through `detect` in `fjs/media/proof.f.mjs`.
|
|
147
|
+
noteDialectTag: () => {
|
|
148
|
+
assertEq(noteDialect.dialect, dialect)
|
|
149
|
+
},
|
|
150
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level API for `fjs/media/note/module.f.mjs`: `Note` and `NoteError`.
|
|
3
|
+
*/
|
|
4
|
+
import type { ValidationError } from '../../types/rtti/common/types.ts';
|
|
5
|
+
import type { Ts } from '../../types/rtti/ts/types.ts';
|
|
6
|
+
import type { noteSchema } from './module.f.mjs';
|
|
7
|
+
/** The TypeScript type derived from `noteSchema` — the single source of truth. */
|
|
8
|
+
export type Note = Ts<typeof noteSchema>;
|
|
9
|
+
/** Either a structural validation error or a JSON parse error message. */
|
|
10
|
+
export type NoteError = ValidationError | string;
|
package/fjs/media/proof.f.d.mts
CHANGED
|
@@ -4,7 +4,10 @@ export declare const proof: {
|
|
|
4
4
|
keyOrderIndependent: () => void;
|
|
5
5
|
invalidRevisionFallsThrough: () => void;
|
|
6
6
|
nonHashSnapshotFallsThrough: () => void;
|
|
7
|
-
|
|
7
|
+
validLock: () => void;
|
|
8
|
+
lockAndRevisionDoNotOverlap: () => void;
|
|
9
|
+
nonHashLockValueFallsThrough: () => void;
|
|
10
|
+
validNote: () => void;
|
|
8
11
|
nonVndDialectName: () => void;
|
|
9
12
|
firstMatchWins: () => void;
|
|
10
13
|
noDialects: () => void;
|
package/fjs/media/proof.f.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { assertEq } from '../asserts/module.f.mjs'
|
|
|
6
6
|
import { msb, u8ListToVec, repeat, vec8 } from '../types/bit_vec/module.f.mjs'
|
|
7
7
|
import { detect, dialectEntry } from './module.f.mjs'
|
|
8
8
|
import { dialect, revisionDialect } from './revision/module.f.mjs'
|
|
9
|
+
import { dialect as lockDialectName, lockDialect } from './lock/module.f.mjs'
|
|
10
|
+
import { dialect as noteDialectName, noteDialect } from './note/module.f.mjs'
|
|
9
11
|
import { number, string } from '../types/rtti/module.f.mjs'
|
|
10
12
|
|
|
11
13
|
// All test strings here are ASCII, so char code === UTF-8 byte value.
|
|
@@ -14,22 +16,12 @@ const utf8Bytes = s => u8ListToVec(msb)([...s].map(c => c.charCodeAt(0)))
|
|
|
14
16
|
|
|
15
17
|
const revisionJson = `{"dialect":"${dialect}","subject":"8","parents":[],"snapshot":"8","generation":0}`
|
|
16
18
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const detectRevision = detect(dialects)
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* A second dialect following the same `vnd.fjs.<name>` convention, registered
|
|
24
|
-
* with no refinement: structure alone decides the match.
|
|
19
|
+
/** The three dialects `fjs/media` itself ships, in the order `fjs/mcp` registers them.
|
|
20
|
+
* @type {readonly DialectEntry[]}
|
|
25
21
|
*/
|
|
26
|
-
const
|
|
27
|
-
dialect: 'vnd.fjs.note',
|
|
28
|
-
text: string,
|
|
29
|
-
})
|
|
22
|
+
const dialects = [revisionDialect, lockDialect, noteDialect]
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
const noteDialect = dialectEntry(noteSchema)
|
|
24
|
+
const detectRevision = detect(dialects)
|
|
33
25
|
|
|
34
26
|
/** A dialect name outside `vnd.fjs.*` — registerable, and detected as itself. */
|
|
35
27
|
const gadgetSchema = /** @type {const} */ ({
|
|
@@ -83,14 +75,41 @@ export const proof = {
|
|
|
83
75
|
assertEq(m.mime_type, 'text/plain')
|
|
84
76
|
},
|
|
85
77
|
|
|
86
|
-
// A
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
assertEq(
|
|
91
|
-
assertEq(
|
|
78
|
+
// A valid shared-lock blob is recognized as its own dialect, alongside the
|
|
79
|
+
// revision one, and reported under the derived media type.
|
|
80
|
+
validLock: () => {
|
|
81
|
+
const m = detectRevision(utf8Bytes(`{"dialect":"${lockDialectName}","lock":{"dependency":"8"}}`))
|
|
82
|
+
assertEq(m.type, 'text')
|
|
83
|
+
assertEq(m.mime_type, 'application/vnd.fjs.lock+json')
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// The two dialects never claim each other's blobs: each schema matches its
|
|
87
|
+
// own `dialect` literal, so a revision carrying an inline `lock` is still a
|
|
88
|
+
// revision, and a lock blob is never a revision.
|
|
89
|
+
lockAndRevisionDoNotOverlap: () => {
|
|
90
|
+
const withLock = `{"dialect":"${dialect}","subject":"8","parents":[],"snapshot":"8","generation":0,"lock":{"d":"8"}}`
|
|
91
|
+
assertEq(detectRevision(utf8Bytes(withLock)).mime_type, 'application/vnd.fjs.revision+json')
|
|
92
|
+
assertEq(detectRevision(utf8Bytes(revisionJson)).mime_type, 'application/vnd.fjs.revision+json')
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// `lockDialect` carries the semantic check too: a structurally valid lock
|
|
96
|
+
// blob whose binding is not a cbase32 hash is not detected as one, exactly
|
|
97
|
+
// as its `decodeText` would say.
|
|
98
|
+
nonHashLockValueFallsThrough: () => {
|
|
99
|
+
const text = `{"dialect":"${lockDialectName}","lock":{"dependency":"not a hash"}}`
|
|
100
|
+
const m = detectRevision(utf8Bytes(text))
|
|
101
|
+
assertEq(m.type, 'text')
|
|
102
|
+
assertEq(m.mime_type, 'text/plain')
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// The note dialect is registered with no refinement, so structure alone
|
|
106
|
+
// decides the match — a valid note is recognized alongside its siblings.
|
|
107
|
+
validNote: () => {
|
|
108
|
+
const m = detectRevision(utf8Bytes(`{"dialect":"${noteDialectName}","text":"hi"}`))
|
|
109
|
+
assertEq(m.type, 'text')
|
|
110
|
+
assertEq(m.mime_type, 'application/vnd.fjs.note+json')
|
|
92
111
|
// Same tag, wrong shape: no entry matches.
|
|
93
|
-
assertEq(
|
|
112
|
+
assertEq(detectRevision(utf8Bytes(`{"dialect":"${noteDialectName}","text":42}`)).mime_type, 'text/plain')
|
|
94
113
|
},
|
|
95
114
|
|
|
96
115
|
// The name is neither grammar-checked nor allowlisted: a dialect outside
|
|
@@ -17,12 +17,13 @@
|
|
|
17
17
|
* @import { Unknown } from '../json/types.ts'
|
|
18
18
|
* @import { Result } from '../../types/result/types.ts'
|
|
19
19
|
* @import { DialectEntry } from '../types.ts'
|
|
20
|
-
* @import {
|
|
20
|
+
* @import { String as RttiString } from '../../types/rtti/types.ts'
|
|
21
|
+
* @import { LockField, LockFieldSchema, LockMap, LockSchema, Revision, RevisionError } from './types.ts'
|
|
21
22
|
*/
|
|
22
23
|
import type { Unknown } from '../json/types.ts';
|
|
23
24
|
import type { Result } from '../../types/result/types.ts';
|
|
24
25
|
import type { DialectEntry } from '../types.ts';
|
|
25
|
-
import type { Revision, RevisionError } from './types.ts';
|
|
26
|
+
import type { LockField, LockFieldSchema, LockMap, LockSchema, Revision, RevisionError } from './types.ts';
|
|
26
27
|
/**
|
|
27
28
|
* Format tag: names the dialect of this BLOB. The media type it is served
|
|
28
29
|
* with is derived mechanically: `application/` + `dialect` + `+json`.
|
|
@@ -40,8 +41,52 @@ export declare const mediaType: "application/vnd.fjs.revision+json";
|
|
|
40
41
|
* not by this schema on its own.
|
|
41
42
|
*/
|
|
42
43
|
export declare const hash: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
43
|
-
/**
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* rtti schema for a lock map: an open map whose every value is either a
|
|
46
|
+
* direct hash string or a nested lock map, to any depth.
|
|
47
|
+
*
|
|
48
|
+
* Self-referential through {@link lockValue}, which is a module-level
|
|
49
|
+
* constant rather than a union rebuilt inside the thunk: the rtti data form
|
|
50
|
+
* (`fjs/types/rtti/data`, which `toJsonSchema` routes through) closes
|
|
51
|
+
* reference cycles by *identity*, so a schema handing out a fresh union thunk
|
|
52
|
+
* on every call would present an infinite graph and never terminate.
|
|
53
|
+
*
|
|
54
|
+
* The named `@type` — rather than `@type {const}` — is what a self-referential
|
|
55
|
+
* schema needs twice over: a `const` cannot reference itself in its own
|
|
56
|
+
* initializer at all, and naming the recursive position is also what keeps
|
|
57
|
+
* declaration emit from inlining the structure and giving up at depth (see
|
|
58
|
+
* `fjs/AGENTS.md` §3.2 and `../json/rtti/module.f.mjs`).
|
|
59
|
+
*
|
|
60
|
+
* Like `hash`, this is `string` at the structural level; cbase32 decodability
|
|
61
|
+
* of every direct value, at every depth, is enforced by
|
|
62
|
+
* {@link checkReferences}.
|
|
63
|
+
*
|
|
64
|
+
* @type {LockSchema}
|
|
65
|
+
*/
|
|
66
|
+
export declare const lock: LockSchema;
|
|
67
|
+
/**
|
|
68
|
+
* rtti schema for a revision's `lock` **field**: the bindings inline as a lock
|
|
69
|
+
* map, or a hash naming a `vnd.fjs.lock` blob (`fjs/media/lock`) that holds
|
|
70
|
+
* one to share — see [Shared lock references](./README.md#shared-lock-references).
|
|
71
|
+
*
|
|
72
|
+
* Structurally identical to {@link lockValue}, and deliberately a separate
|
|
73
|
+
* name: the two positions mean different things. A string *inside* a map is a
|
|
74
|
+
* dependency's content hash; a string in this position is a lock blob's hash,
|
|
75
|
+
* i.e. where the whole map lives. Only the top level is widened, so a nested
|
|
76
|
+
* string keeps meaning exactly what it always did and no position is
|
|
77
|
+
* ambiguous.
|
|
78
|
+
*
|
|
79
|
+
* Widening the field rather than adding a `lockRef` sibling is what keeps this
|
|
80
|
+
* dialect: an older reader validates `lock` as a map and rejects a string
|
|
81
|
+
* outright, whereas an unknown sibling field would validate and be read as
|
|
82
|
+
* "no bindings were recorded" — the fail-open misread the versioning rule
|
|
83
|
+
* exists to prevent. It also makes "inline map *and* reference" unstatable, so
|
|
84
|
+
* the format defines no precedence between them, consistent with its refusal
|
|
85
|
+
* to define overlay or inheritance for nested maps.
|
|
86
|
+
*
|
|
87
|
+
* @type {LockFieldSchema}
|
|
88
|
+
*/
|
|
89
|
+
export declare const lockField: LockFieldSchema;
|
|
45
90
|
/**
|
|
46
91
|
* rtti schema for a `revision` BLOB. See the README for the full semantics of
|
|
47
92
|
* each field; `dialect` is the type discriminant, matched here as an exact
|
|
@@ -54,7 +99,7 @@ export declare const revisionSchema: {
|
|
|
54
99
|
readonly snapshot: import("../../types/rtti/types.ts")._Type0<"string">;
|
|
55
100
|
readonly generation: import("../../types/rtti/types.ts")._Type0<"number">;
|
|
56
101
|
readonly archived: import("../../types/rtti/types.ts").Or<readonly [true, undefined]>;
|
|
57
|
-
readonly lock: import("../../types/rtti/types.ts").Or<readonly [
|
|
102
|
+
readonly lock: import("../../types/rtti/types.ts").Or<readonly [LockFieldSchema, undefined]>;
|
|
58
103
|
};
|
|
59
104
|
/** Serializes a revision canonically, recursively sorting every object's property names.
|
|
60
105
|
* @type {(revision: Revision) => string}
|
|
@@ -64,12 +109,38 @@ export declare const encodeText: (revision: Revision) => string;
|
|
|
64
109
|
* @type {(s: string) => boolean}
|
|
65
110
|
*/
|
|
66
111
|
export declare const isHash: (s: string) => boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The first reason a structurally valid lock map is not a valid one, or `null`
|
|
114
|
+
* — {@link lockError} rooted at the empty scope, so a reported path is
|
|
115
|
+
* relative to the map itself.
|
|
116
|
+
*
|
|
117
|
+
* Exported because `fjs/media/lock` validates the very same map as a
|
|
118
|
+
* standalone blob: one recursive schema and one semantic check, so a map means
|
|
119
|
+
* the same thing inline and shared, and the two forms cannot drift.
|
|
120
|
+
*
|
|
121
|
+
* @type {(lock: LockMap) => string | null}
|
|
122
|
+
*/
|
|
123
|
+
export declare const lockMapError: (lock: LockMap) => string | null;
|
|
124
|
+
/**
|
|
125
|
+
* The first reason a structurally valid `lock` field is not a valid one, or
|
|
126
|
+
* `null`. A map is checked entry by entry ({@link lockMapError}); a string is
|
|
127
|
+
* a reference to a `vnd.fjs.lock` blob and is checked as a cbase32 hash and
|
|
128
|
+
* nothing more — this module is pure format with no store access, so whether
|
|
129
|
+
* the blob exists, and what its bindings mean once fetched, stay a resolver's
|
|
130
|
+
* business exactly as they do for `snapshot`.
|
|
131
|
+
*
|
|
132
|
+
* @type {(value: LockField) => string | null}
|
|
133
|
+
*/
|
|
134
|
+
export declare const lockFieldError: (value: LockField) => string | null;
|
|
67
135
|
/**
|
|
68
136
|
* Checks the semantic refinements the structural schema can't express on an
|
|
69
137
|
* already shape-valid revision: every `parents` entry and the `snapshot` must
|
|
70
|
-
* decode as a cbase32 hash ({@link isHash}),
|
|
138
|
+
* decode as a cbase32 hash ({@link isHash}), the `lock` field must too —
|
|
139
|
+
* every direct value at every depth of an inline map, or the shared-lock
|
|
140
|
+
* reference itself ({@link lockFieldError}) — and `generation` must be a
|
|
71
141
|
* non-negative *safe* integer. `subject` is not checked — it is an identity
|
|
72
|
-
* string, never a snapshot reference, so any string is valid
|
|
142
|
+
* string, never a snapshot reference, so any string is valid, and the same
|
|
143
|
+
* goes for a lock map's keys, which are subjects.
|
|
73
144
|
*
|
|
74
145
|
* `generation` uses `Number.isSafeInteger`, not `Number.isInteger`: a value at
|
|
75
146
|
* or above `2 ** 53` passes `isInteger` but is no longer uniquely
|