functionalscript 0.36.0 → 0.37.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.
Files changed (255) hide show
  1. package/fs/asn.1/module.f.js +1 -1
  2. package/fs/asserts/proof.f.js +1 -1
  3. package/fs/base_n/module.f.js +3 -3
  4. package/fs/base_n/proof.f.js +3 -3
  5. package/fs/{base128 → basen/base128}/module.f.d.ts +1 -1
  6. package/fs/{base128 → basen/base128}/module.f.js +1 -1
  7. package/fs/{base128 → basen/base128}/proof.f.js +3 -3
  8. package/fs/{base64 → basen/base64}/module.f.d.ts +2 -2
  9. package/fs/{base64 → basen/base64}/module.f.js +25 -15
  10. package/fs/{base64 → basen/base64}/proof.f.d.ts +1 -0
  11. package/fs/{base64 → basen/base64}/proof.f.js +11 -5
  12. package/fs/{cbase32 → basen/cbase32}/module.f.d.ts +2 -2
  13. package/fs/{cbase32 → basen/cbase32}/module.f.js +2 -2
  14. package/fs/{cbase32 → basen/cbase32}/proof.f.js +1 -1
  15. package/fs/bnf/data/module.f.js +4 -4
  16. package/fs/bnf/data/proof.f.js +6 -6
  17. package/fs/bnf/descent/module.f.js +6 -6
  18. package/fs/bnf/descent/proof.f.js +6 -6
  19. package/fs/bnf/ll1/module.f.js +9 -9
  20. package/fs/bnf/ll1/proof.f.js +6 -6
  21. package/fs/bnf/module.f.js +4 -4
  22. package/fs/bnf/proof.f.js +2 -2
  23. package/fs/bnf/testlib.f.js +1 -1
  24. package/fs/cas/cli/module.f.js +7 -7
  25. package/fs/cas/cli/proof.f.js +8 -8
  26. package/fs/cas/mcp/module.f.js +95 -55
  27. package/fs/cas/mcp/proof.f.d.ts +3 -1
  28. package/fs/cas/mcp/proof.f.js +79 -51
  29. package/fs/cas/module.f.js +10 -10
  30. package/fs/cas/proof.f.d.ts +4 -2
  31. package/fs/cas/proof.f.js +10 -10
  32. package/fs/ci/bun/module.f.js +2 -2
  33. package/fs/ci/common/module.f.d.ts +2 -2
  34. package/fs/ci/common/module.f.js +4 -4
  35. package/fs/ci/config/module.f.d.ts +19 -20
  36. package/fs/ci/config/module.f.js +3 -5
  37. package/fs/ci/deno/module.f.js +8 -4
  38. package/fs/ci/module.f.js +9 -9
  39. package/fs/ci/node/module.f.d.ts +1 -1
  40. package/fs/ci/node/module.f.js +3 -5
  41. package/fs/ci/playwright/module.f.js +3 -3
  42. package/fs/ci/proof.f.js +14 -14
  43. package/fs/ci/rust/module.f.js +2 -2
  44. package/fs/cli/module.f.js +3 -3
  45. package/fs/cli/proof.f.js +3 -3
  46. package/fs/{types → common}/monoid/module.f.d.ts +1 -1
  47. package/fs/crypto/hmac/module.f.js +2 -2
  48. package/fs/crypto/hmac/proof.f.js +5 -5
  49. package/fs/crypto/pow/module.f.js +3 -3
  50. package/fs/crypto/pow/proof.f.js +4 -4
  51. package/fs/crypto/secp/module.f.js +2 -2
  52. package/fs/crypto/secp/proof.f.js +3 -3
  53. package/fs/crypto/sha2/module.f.js +2 -2
  54. package/fs/crypto/sha2/proof.f.js +6 -6
  55. package/fs/crypto/sign/module.f.js +4 -4
  56. package/fs/crypto/vdf/module.f.js +1 -1
  57. package/fs/crypto/vdf/proof.f.js +1 -1
  58. package/fs/dev/module.f.js +6 -6
  59. package/fs/dev/package_json/module.f.js +6 -6
  60. package/fs/dev/package_json/proof.f.js +3 -3
  61. package/fs/dev/proof.f.js +2 -2
  62. package/fs/djs/ast/module.f.js +2 -2
  63. package/fs/djs/ast/proof.f.js +4 -4
  64. package/fs/djs/examples/m.f.d.ts +1 -1
  65. package/fs/djs/module.f.d.ts +1 -1
  66. package/fs/djs/module.f.js +5 -5
  67. package/fs/djs/parser/module.f.js +4 -4
  68. package/fs/djs/parser/proof.f.js +7 -7
  69. package/fs/djs/proof.f.js +3 -3
  70. package/fs/djs/serializer/module.f.js +6 -6
  71. package/fs/djs/serializer/proof.f.js +4 -4
  72. package/fs/djs/tokenizer/module.f.js +3 -3
  73. package/fs/djs/tokenizer/proof.f.js +5 -5
  74. package/fs/djs/tokenizer-new/proof.f.js +6 -6
  75. package/fs/djs/transpiler/module.f.d.ts +5 -0
  76. package/fs/djs/transpiler/module.f.js +21 -11
  77. package/fs/djs/transpiler/proof.f.js +5 -5
  78. package/fs/effects/memory/module.f.js +4 -4
  79. package/fs/effects/memory/proof.f.js +3 -3
  80. package/fs/effects/module.f.js +1 -1
  81. package/fs/effects/node/memory/module.js +2 -2
  82. package/fs/effects/node/memory/proof.js +3 -3
  83. package/fs/effects/node/module.f.js +7 -7
  84. package/fs/effects/node/module.js +10 -10
  85. package/fs/effects/node/virtual/module.f.js +7 -7
  86. package/fs/effects/node/virtual/proof.f.d.ts +1 -0
  87. package/fs/effects/node/virtual/proof.f.js +11 -4
  88. package/fs/effects/proof.f.js +2 -2
  89. package/fs/emergent_testing/all.test.js +2 -2
  90. package/fs/emergent_testing/example.f.d.ts +1 -1
  91. package/fs/emergent_testing/module.f.js +6 -6
  92. package/fs/emergent_testing/proof.f.d.ts +19 -17
  93. package/fs/emergent_testing/proof.f.js +7 -7
  94. package/fs/emergent_testing/scenarios/all.js +1 -1
  95. package/fs/fsc/bnf.f.js +2 -2
  96. package/fs/fsc/json.f.js +1 -1
  97. package/fs/fsc/module.f.js +7 -7
  98. package/fs/fsc/proof.f.js +4 -4
  99. package/fs/fsm/module.f.js +9 -9
  100. package/fs/fsm/proof.f.js +7 -7
  101. package/fs/js/tokenizer/module.f.js +7 -7
  102. package/fs/js/tokenizer/proof.f.js +5 -5
  103. package/fs/mcp/module.f.d.ts +13 -13
  104. package/fs/mcp/module.f.js +8 -8
  105. package/fs/mcp/proof.f.js +5 -5
  106. package/fs/mcp/stdio/module.f.d.ts +2 -2
  107. package/fs/mcp/stdio/module.f.js +10 -10
  108. package/fs/mcp/stdio/proof.f.js +10 -10
  109. package/fs/{html → media/html}/module.f.d.ts +3 -3
  110. package/fs/{html → media/html}/module.f.js +9 -9
  111. package/fs/media/json/module.f.d.ts +50 -0
  112. package/fs/{json → media/json}/module.f.js +7 -7
  113. package/fs/{json → media/json}/parser/module.f.d.ts +2 -2
  114. package/fs/{json → media/json}/parser/module.f.js +7 -7
  115. package/fs/{json → media/json}/parser/proof.f.js +6 -6
  116. package/fs/{json → media/json}/proof.f.js +4 -4
  117. package/fs/media/json/rpc/module.f.d.ts +114 -0
  118. package/fs/{json → media/json}/rpc/module.f.js +3 -3
  119. package/fs/{json → media/json}/rpc/proof.f.js +4 -4
  120. package/fs/media/json/schema/module.f.d.ts +78 -0
  121. package/fs/{json → media/json}/schema/module.f.js +2 -2
  122. package/fs/{json → media/json}/schema/proof.f.js +3 -3
  123. package/fs/{json → media/json}/serializer/module.f.d.ts +1 -1
  124. package/fs/{json → media/json}/serializer/module.f.js +2 -2
  125. package/fs/{json → media/json}/serializer/proof.f.js +2 -2
  126. package/fs/{json → media/json}/tokenizer/module.f.d.ts +2 -2
  127. package/fs/{json → media/json}/tokenizer/module.f.js +4 -4
  128. package/fs/{json → media/json}/tokenizer/proof.f.js +5 -5
  129. package/fs/media/module.f.d.ts +37 -0
  130. package/fs/media/module.f.js +24 -0
  131. package/fs/media/proof.f.d.ts +9 -0
  132. package/fs/media/proof.f.js +57 -0
  133. package/fs/media/revision/module.f.d.ts +51 -0
  134. package/fs/media/revision/module.f.js +110 -0
  135. package/fs/media/revision/proof.f.d.ts +29 -0
  136. package/fs/media/revision/proof.f.js +119 -0
  137. package/fs/{mime → media/type}/module.f.d.ts +6 -6
  138. package/fs/{mime → media/type}/module.f.js +6 -6
  139. package/fs/{mime → media/type}/proof.f.js +6 -6
  140. package/fs/module.f.d.ts +2 -0
  141. package/fs/{fjs/module.f.js → module.f.js} +8 -8
  142. package/fs/module.js +4 -0
  143. package/fs/path/module.f.js +2 -2
  144. package/fs/{fjs/proof.f.js → proof.f.js} +4 -4
  145. package/fs/sul/id/module.f.js +9 -9
  146. package/fs/sul/id/proof.f.js +5 -5
  147. package/fs/sul/level/hash/module.f.js +3 -3
  148. package/fs/sul/level/hash/proof.f.js +3 -3
  149. package/fs/sul/level/literal/module.f.js +5 -5
  150. package/fs/sul/level/literal/proof.f.js +3 -3
  151. package/fs/sul/module.f.js +3 -3
  152. package/fs/sul/proof.f.js +3 -3
  153. package/fs/text/ascii/proof.f.js +3 -3
  154. package/fs/text/code_point/module.f.js +2 -2
  155. package/fs/text/code_point/proof.f.js +1 -1
  156. package/fs/text/module.f.js +5 -5
  157. package/fs/text/proof.f.js +4 -4
  158. package/fs/text/sgr/module.f.js +2 -2
  159. package/fs/text/sgr/proof.f.js +2 -2
  160. package/fs/text/utf16/module.f.js +5 -5
  161. package/fs/text/utf16/proof.f.js +4 -4
  162. package/fs/text/utf8/module.f.js +22 -17
  163. package/fs/text/utf8/proof.f.js +5 -5
  164. package/fs/types/array/module.f.js +1 -1
  165. package/fs/types/array/proof.f.js +3 -3
  166. package/fs/types/bigfloat/module.f.js +1 -1
  167. package/fs/types/bigfloat/proof.f.js +1 -1
  168. package/fs/types/bigint/module.f.js +2 -2
  169. package/fs/types/bigint/proof.f.js +3 -3
  170. package/fs/types/bit_vec/module.f.js +8 -8
  171. package/fs/types/bit_vec/proof.f.js +5 -5
  172. package/fs/types/btree/find/module.f.js +1 -1
  173. package/fs/types/btree/find/proof.f.d.ts +3 -1
  174. package/fs/types/btree/find/proof.f.js +7 -7
  175. package/fs/types/btree/module.f.js +2 -2
  176. package/fs/types/btree/proof.f.d.ts +11 -5
  177. package/fs/types/btree/proof.f.js +7 -7
  178. package/fs/types/btree/remove/module.f.js +4 -4
  179. package/fs/types/btree/remove/proof.f.d.ts +5 -2
  180. package/fs/types/btree/remove/proof.f.js +5 -5
  181. package/fs/types/btree/set/module.f.js +3 -3
  182. package/fs/types/btree/set/proof.f.js +4 -4
  183. package/fs/types/byte_set/module.f.js +2 -2
  184. package/fs/types/byte_set/proof.f.js +4 -4
  185. package/fs/types/function/compare/proof.f.js +1 -1
  186. package/fs/types/function/operator/proof.f.d.ts +21 -10
  187. package/fs/types/function/operator/proof.f.js +1 -1
  188. package/fs/types/function/proof.f.js +1 -1
  189. package/fs/types/list/module.f.js +2 -2
  190. package/fs/types/list/proof.f.d.ts +15 -7
  191. package/fs/types/list/proof.f.js +4 -4
  192. package/fs/types/map/proof.f.js +2 -2
  193. package/fs/types/nibble_set/proof.f.js +2 -2
  194. package/fs/types/nullable/module.f.js +2 -2
  195. package/fs/types/nullable/proof.f.js +1 -1
  196. package/fs/types/number/module.f.js +5 -5
  197. package/fs/types/number/proof.f.js +1 -1
  198. package/fs/types/object/module.f.js +4 -4
  199. package/fs/types/object/proof.f.js +1 -1
  200. package/fs/types/ordered_map/module.f.js +6 -6
  201. package/fs/types/ordered_map/proof.f.js +3 -3
  202. package/fs/types/patricia_trie/proof.f.d.ts +11 -5
  203. package/fs/types/patricia_trie/proof.f.js +2 -2
  204. package/fs/types/prime_field/module.f.js +2 -2
  205. package/fs/types/prime_field/proof.f.js +2 -2
  206. package/fs/types/range/proof.f.js +1 -1
  207. package/fs/types/range_map/module.f.js +4 -4
  208. package/fs/types/range_map/proof.f.js +7 -7
  209. package/fs/types/result/module.js +1 -1
  210. package/fs/types/result/proof.f.d.ts +9 -4
  211. package/fs/types/rtti/common/module.f.js +4 -4
  212. package/fs/types/rtti/module.f.d.ts +2 -2
  213. package/fs/types/rtti/module.f.js +1 -1
  214. package/fs/types/rtti/parse/module.f.js +5 -5
  215. package/fs/types/rtti/parse/proof.f.js +2 -2
  216. package/fs/types/rtti/ts/module.f.js +1 -1
  217. package/fs/types/rtti/ts/proof.f.js +2 -2
  218. package/fs/types/rtti/validate/module.f.js +4 -4
  219. package/fs/types/rtti/validate/proof.f.js +2 -2
  220. package/fs/types/sorted_list/module.f.js +3 -3
  221. package/fs/types/sorted_list/proof.f.js +6 -6
  222. package/fs/types/sorted_set/module.f.js +1 -1
  223. package/fs/types/sorted_set/proof.f.js +6 -6
  224. package/fs/types/string/module.f.js +4 -4
  225. package/fs/types/string/proof.f.js +2 -2
  226. package/fs/types/string_set/module.f.js +6 -6
  227. package/fs/types/string_set/proof.f.js +1 -1
  228. package/fs/types/ts/proof.f.d.ts +18 -18
  229. package/fs/types/ts/proof.f.js +1 -1
  230. package/fs/types/uint8array/module.f.js +4 -4
  231. package/fs/types/uint8array/proof.f.js +4 -4
  232. package/fs/website/module.f.js +3 -3
  233. package/fs/website/proof.f.js +3 -3
  234. package/package.json +8 -8
  235. package/fs/fjs/module.f.d.ts +0 -2
  236. package/fs/fjs/module.js +0 -4
  237. package/fs/json/module.f.d.ts +0 -50
  238. package/fs/json/rpc/module.f.d.ts +0 -114
  239. package/fs/json/schema/module.f.d.ts +0 -77
  240. /package/fs/{base128 → basen/base128}/proof.f.d.ts +0 -0
  241. /package/fs/{cbase32 → basen/cbase32}/proof.f.d.ts +0 -0
  242. /package/fs/{types → common}/monoid/module.f.js +0 -0
  243. /package/fs/{types → common}/monoid/proof.f.d.ts +0 -0
  244. /package/fs/{types → common}/monoid/proof.f.js +0 -0
  245. /package/fs/{html → media/html}/proof.f.d.ts +0 -0
  246. /package/fs/{html → media/html}/proof.f.js +0 -0
  247. /package/fs/{json → media/json}/parser/proof.f.d.ts +0 -0
  248. /package/fs/{json → media/json}/proof.f.d.ts +0 -0
  249. /package/fs/{json → media/json}/rpc/proof.f.d.ts +0 -0
  250. /package/fs/{json → media/json}/schema/proof.f.d.ts +0 -0
  251. /package/fs/{json → media/json}/serializer/proof.f.d.ts +0 -0
  252. /package/fs/{json → media/json}/tokenizer/proof.f.d.ts +0 -0
  253. /package/fs/{mime → media/type}/proof.f.d.ts +0 -0
  254. /package/fs/{fjs/module.d.ts → module.d.ts} +0 -0
  255. /package/fs/{fjs/proof.f.d.ts → proof.f.d.ts} +0 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Converts an rtti schema to a JSON Schema (draft 2020-12) object.
3
+ *
4
+ * Mirrors the visitor structure of `fs/types/rtti/ts/module.f.ts` (`printer` / `toTs`),
5
+ * but emits a JSON Schema object instead of a TypeScript type string.
6
+ *
7
+ * @module
8
+ */
9
+ import { type Type as RttiType } from '../../../types/rtti/module.f.ts';
10
+ import type { Ts } from '../../../types/rtti/ts/module.f.ts';
11
+ import type { Phantom } from '../../../types/phantom/module.f.ts';
12
+ import { unknown as jsonUnknown } from '../module.f.ts';
13
+ declare const unknownThunk: () => readonly ['const', {
14
+ readonly type: import("../../../types/rtti/module.f.ts").Or<["boolean", "number", "string", "integer", "array", "object", undefined]>;
15
+ readonly const: import("../../../types/rtti/module.f.ts").Or<readonly [typeof jsonUnknown, undefined]>;
16
+ readonly not: import("../../../types/rtti/module.f.ts").Or<readonly [Phantom<() => readonly ['const', /*elided*/ any], UnknownConst>, undefined]>;
17
+ readonly anyOf: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"array", Phantom<() => readonly ['const', any], UnknownConst>>, undefined]>;
18
+ readonly items: import("../../../types/rtti/module.f.ts").Or<[Phantom<() => readonly ['const', any], UnknownConst>, false, undefined]>;
19
+ readonly prefixItems: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"array", Phantom<() => readonly ['const', any], UnknownConst>>, undefined]>;
20
+ readonly properties: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"record", Phantom<() => readonly ['const', any], UnknownConst>>, undefined]>;
21
+ readonly required: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"array", import("../../../types/rtti/module.f.ts").String>, undefined]>;
22
+ readonly additionalProperties: import("../../../types/rtti/module.f.ts").Or<readonly [Phantom<() => readonly ['const', any], UnknownConst>, undefined]>;
23
+ }];
24
+ /** rtti schema for a JSON Schema (draft 2020-12) document. */
25
+ export declare const unknown: Phantom<typeof unknownThunk, UnknownConst>;
26
+ /** A JSON Schema (draft 2020-12) document — the subset of keywords that `toJsonSchema` emits. */
27
+ export type Unknown = Ts<typeof unknown>;
28
+ declare const unknownConst: {
29
+ readonly type: import("../../../types/rtti/module.f.ts").Or<["boolean", "number", "string", "integer", "array", "object", undefined]>;
30
+ readonly const: import("../../../types/rtti/module.f.ts").Or<readonly [typeof jsonUnknown, undefined]>;
31
+ readonly not: import("../../../types/rtti/module.f.ts").Or<readonly [Phantom<typeof unknownThunk, UnknownConst>, undefined]>;
32
+ readonly anyOf: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"array", Phantom<typeof unknownThunk, UnknownConst>>, undefined]>;
33
+ readonly items: import("../../../types/rtti/module.f.ts").Or<[Phantom<typeof unknownThunk, UnknownConst>, false, undefined]>;
34
+ readonly prefixItems: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"array", Phantom<typeof unknownThunk, UnknownConst>>, undefined]>;
35
+ readonly properties: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"record", Phantom<typeof unknownThunk, UnknownConst>>, undefined]>;
36
+ readonly required: import("../../../types/rtti/module.f.ts").Or<readonly [import("../../../types/rtti/module.f.ts").Type1<"array", import("../../../types/rtti/module.f.ts").String>, undefined]>;
37
+ readonly additionalProperties: import("../../../types/rtti/module.f.ts").Or<readonly [Phantom<typeof unknownThunk, UnknownConst>, undefined]>;
38
+ };
39
+ /**
40
+ * Hand-written base type used as the `$out` annotation on `unknown`.
41
+ *
42
+ * The `?` markers are required even though `Ts<>` already includes `undefined`
43
+ * in each field type. Without `?`, `Unknown = UnknownConst` would require all
44
+ * 9 fields to be present in every object literal returned by `toJsonSchema`,
45
+ * because TypeScript distinguishes "field absent" (`?`) from "field present but
46
+ * undefined" (`T | undefined`). JSON Schema objects only include the fields
47
+ * they need, so all fields must be optional.
48
+ */
49
+ type UnknownConst = {
50
+ readonly type?: Ts<typeof unknownConst.type>;
51
+ readonly const?: Ts<typeof unknownConst.const>;
52
+ readonly not?: Ts<typeof unknownConst.not>;
53
+ readonly anyOf?: Ts<typeof unknownConst.anyOf>;
54
+ readonly items?: Ts<typeof unknownConst.items>;
55
+ readonly prefixItems?: Ts<typeof unknownConst.prefixItems>;
56
+ readonly properties?: Ts<typeof unknownConst.properties>;
57
+ readonly required?: Ts<typeof unknownConst.required>;
58
+ readonly additionalProperties?: Ts<typeof unknownConst.additionalProperties>;
59
+ };
60
+ /**
61
+ * Converts an rtti `Type` to a JSON Schema (draft 2020-12) object.
62
+ *
63
+ * | rtti | JSON Schema |
64
+ * |-----------------------------------------------|-------------------------------------------------------------------------------------|
65
+ * | `boolean` / `number` / `string` | `{ "type": "..." }` |
66
+ * | `bigint` | `{ "type": "integer" }` (lossy; JSON integers are IEEE-754 doubles) |
67
+ * | `unknown` | `{}` (always-true schema) |
68
+ * | primitive const (`42`, `'x'`, `true`, `null`) | `{ "const": <value> }` |
69
+ * | `bigint` const | `{ "const": Number(value) }` (lossy for \|value\| > MAX_SAFE_INTEGER) |
70
+ * | `undefined` const | `{ "not": {} }` (no JSON value satisfies this) |
71
+ * | struct `{ a: T, … }` | `{ "type": "object", "properties": { "a": …T… }, "required": [non-optional keys] }` |
72
+ * | tuple `[A, B]` | `{ "type": "array", "prefixItems": […A…, …B…], "items": false }` |
73
+ * | `array(T)` | `{ "type": "array", "items": …T… }` |
74
+ * | `record(T)` | `{ "type": "object", "additionalProperties": …T… }` |
75
+ * | `or(...types)` | `{ "anyOf": […each…] }` |
76
+ */
77
+ export declare const toJsonSchema: (rtti: RttiType) => Unknown;
78
+ export {};
@@ -6,8 +6,8 @@
6
6
  *
7
7
  * @module
8
8
  */
9
- import { array, option, or, record, string } from "../../types/rtti/module.f.js";
10
- import { unknown as jsonUnknown } from "../module.f.js";
9
+ import { array, option, or, record, string } from '../../../types/rtti/module.f.js';
10
+ import { unknown as jsonUnknown } from '../module.f.js';
11
11
  const unknownThunk = () => ['const', unknownConst];
12
12
  /** rtti schema for a JSON Schema (draft 2020-12) document. */
13
13
  export const unknown = unknownThunk;
@@ -1,6 +1,6 @@
1
- import { boolean, number, string, bigint, unknown, array, record, or, option } from "../../types/rtti/module.f.js";
2
- import { stringify } from "../module.f.js";
3
- import { toJsonSchema, unknown as schemaUnknown } from "./module.f.js";
1
+ import { boolean, number, string, bigint, unknown, array, record, or, option } from '../../../types/rtti/module.f.js';
2
+ import { stringify } from '../module.f.js';
3
+ import { toJsonSchema, unknown as schemaUnknown } from './module.f.js';
4
4
  const serialize = (v) => stringify(e => e)(v);
5
5
  const eq = (rtti, expected) => () => {
6
6
  const result = serialize(toJsonSchema(rtti));
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { type List } from '../../types/list/module.f.ts';
6
+ import { type List } from '../../../types/list/module.f.ts';
7
7
  /**
8
8
  * Serializes a string as a JSON string literal.
9
9
  */
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { flat, reduce, empty } from "../../types/list/module.f.js";
7
- import {} from "../../types/function/operator/module.f.js";
6
+ import { flat, reduce, empty } from '../../../types/list/module.f.js';
7
+ import {} from '../../../types/function/operator/module.f.js';
8
8
  const jsonStringify = JSON.stringify;
9
9
  /**
10
10
  * Serializes a string as a JSON string literal.
@@ -1,5 +1,5 @@
1
- import { arrayWrap, boolSerialize, numberSerialize, objectWrap, stringSerialize } from "./module.f.js";
2
- import * as list from "../../types/list/module.f.js";
1
+ import { arrayWrap, boolSerialize, numberSerialize, objectWrap, stringSerialize } from './module.f.js';
2
+ import * as list from '../../../types/list/module.f.js';
3
3
  const { toArray } = list;
4
4
  export const proof = {
5
5
  arrayWrap: [
@@ -1,5 +1,5 @@
1
- import { type List } from '../../types/list/module.f.ts';
2
- import { type EofToken, type ErrorToken, type NumberToken, type StringToken } from '../../js/tokenizer/module.f.ts';
1
+ import { type List } from '../../../types/list/module.f.ts';
2
+ import { type EofToken, type ErrorToken, type NumberToken, type StringToken } from '../../../js/tokenizer/module.f.ts';
3
3
  export type JsonToken = {
4
4
  readonly kind: 'true' | 'false' | 'null';
5
5
  } | {
@@ -3,10 +3,10 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import {} from "../../types/function/operator/module.f.js";
7
- import { concat, empty, flat, stateScan } from "../../types/list/module.f.js";
8
- import { multiply } from "../../types/bigfloat/module.f.js";
9
- import { tokenize as jsTokenize } from "../../js/tokenizer/module.f.js";
6
+ import {} from '../../../types/function/operator/module.f.js';
7
+ import { concat, empty, flat, stateScan } from '../../../types/list/module.f.js';
8
+ import { multiply } from '../../../types/bigfloat/module.f.js';
9
+ import { tokenize as jsTokenize } from '../../../js/tokenizer/module.f.js';
10
10
  const mapToken = input => {
11
11
  switch (input.kind) {
12
12
  case '{':
@@ -1,8 +1,8 @@
1
- import { tokenize } from "./module.f.js";
2
- import { toArray } from "../../types/list/module.f.js";
3
- import { stringifyAsTree } from "../../djs/serializer/module.f.js";
4
- import { sort } from "../../types/object/module.f.js";
5
- import { stringToList } from "../../text/utf16/module.f.js";
1
+ import { tokenize } from './module.f.js';
2
+ import { toArray } from '../../../types/list/module.f.js';
3
+ import { stringifyAsTree } from '../../../djs/serializer/module.f.js';
4
+ import { sort } from '../../../types/object/module.f.js';
5
+ import { stringToList } from '../../../text/utf16/module.f.js';
6
6
  const tokenizeString = s => toArray(tokenize(stringToList(s)));
7
7
  const stringify = stringifyAsTree(sort);
8
8
  export const proof = {
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Media-type detection entry point: dialect-tagged JSON recognition layered
3
+ * on top of the byte-signature / UTF-8 detector.
4
+ *
5
+ * `fs/media/type` classifies raw bytes (magic-byte signatures, UTF-8
6
+ * text-vs-binary) with no notion of a JSON dialect. `detect` here adds one
7
+ * more classification step in front of it: when the whole blob is
8
+ * whole-blob-valid UTF-8 text (as `fs/media/type` already determined), try
9
+ * parsing it as JSON and validating it against a known dialect's rtti schema
10
+ * (currently just `vnd.fjs.revision` — see `fs/media/revision`). A match
11
+ * reports the dialect's derived media type; anything that isn't valid JSON,
12
+ * or doesn't validate against a known dialect, falls through to the
13
+ * `fs/media/type` verdict unchanged.
14
+ *
15
+ * Detection is semantic, not syntactic: any JSON that satisfies a dialect's
16
+ * schema is recognized regardless of key order, whitespace, or any other
17
+ * serialization detail — there is no byte-level shortcut such as a
18
+ * `{"dialect":` prefix check.
19
+ *
20
+ * This only classifies an already-buffered `Vec` (`detectVec`'s single-`Vec`
21
+ * form), because dialect validation needs the whole parsed value. A single
22
+ * `Vec` already caps at `maxLength` bits (128 KiB — the same bound as the
23
+ * existing inline-content cap), so dialect detection is naturally
24
+ * size-bounded. The unbounded streaming detector (`fs/media/type`
25
+ * `detectStream`) is untouched and stays a pure byte-signature/UTF-8
26
+ * classifier with no dialect awareness.
27
+ *
28
+ * @module
29
+ */
30
+ import type { Vec } from '../types/bit_vec/module.f.ts';
31
+ import { type DetectMeta } from './type/module.f.ts';
32
+ /**
33
+ * Classifies a whole buffered `Vec`, the same three-way `{ length, mime_type,
34
+ * type }` shape as `fs/media/type` `detectVec`, but with dialect-tagged JSON
35
+ * recognized ahead of the plain `text/plain` fallback.
36
+ */
37
+ export declare const detect: (bytes: Vec) => DetectMeta;
@@ -0,0 +1,24 @@
1
+ import { fromVec } from '../text/utf8/module.f.js';
2
+ import { detectVec } from './type/module.f.js';
3
+ import { decodeText as decodeRevisionText, mediaType as revisionMediaType } from './revision/module.f.js';
4
+ import { assert } from '../asserts/module.f.js';
5
+ /**
6
+ * Classifies a whole buffered `Vec`, the same three-way `{ length, mime_type,
7
+ * type }` shape as `fs/media/type` `detectVec`, but with dialect-tagged JSON
8
+ * recognized ahead of the plain `text/plain` fallback.
9
+ */
10
+ export const detect = (bytes) => {
11
+ const base = detectVec(bytes);
12
+ // Only whole-blob-valid UTF-8 text can possibly be JSON; a magic-byte hit
13
+ // or binary fallback is never a dialect match.
14
+ if (base.type !== 'text') {
15
+ return base;
16
+ }
17
+ const text = fromVec(bytes);
18
+ // `detectVec`'s `type: 'text'` verdict already means `bytes` is byte-aligned,
19
+ // whole-blob-valid UTF-8 — the same two conditions `fromVec` checks, via the
20
+ // same decoder — so `fromVec` cannot return `null` here.
21
+ assert(text !== null, 'detect: type text implies fromVec succeeds');
22
+ const [tag] = decodeRevisionText(text);
23
+ return tag === 'ok' ? { ...base, mime_type: revisionMediaType } : base;
24
+ };
@@ -0,0 +1,9 @@
1
+ export declare const proof: {
2
+ validRevision: () => void;
3
+ keyOrderIndependent: () => void;
4
+ invalidRevisionFallsThrough: () => void;
5
+ ordinaryJsonFallsThrough: () => void;
6
+ plainTextFallsThrough: () => void;
7
+ binaryFallsThrough: () => void;
8
+ largeNonJsonWithinBound: () => void;
9
+ };
@@ -0,0 +1,57 @@
1
+ import { assertEq } from '../asserts/module.f.js';
2
+ import { msb, u8ListToVec, repeat, vec8 } from '../types/bit_vec/module.f.js';
3
+ import { detect } from './module.f.js';
4
+ import { dialect } from './revision/module.f.js';
5
+ // All test strings here are ASCII, so char code === UTF-8 byte value.
6
+ const utf8Bytes = (s) => u8ListToVec(msb)([...s].map(c => c.charCodeAt(0)));
7
+ const revisionJson = `{"dialect":"${dialect}","subject":"8","parents":[]}`;
8
+ export const proof = {
9
+ // A valid revision blob is recognized and reported under its derived media type.
10
+ validRevision: () => {
11
+ const m = detect(utf8Bytes(revisionJson));
12
+ assertEq(m.type, 'text');
13
+ assertEq(m.mime_type, 'application/vnd.fjs.revision+json');
14
+ },
15
+ // Key order carries no meaning: a valid revision whose `dialect` key is
16
+ // not first must still be detected.
17
+ keyOrderIndependent: () => {
18
+ const text = `{"parents":[],"subject":"8","dialect":"${dialect}"}`;
19
+ const m = detect(utf8Bytes(text));
20
+ assertEq(m.mime_type, 'application/vnd.fjs.revision+json');
21
+ },
22
+ // Structurally invalid revision (bad multi-parent snapshot rule) falls
23
+ // through to the ordinary text/plain classification.
24
+ invalidRevisionFallsThrough: () => {
25
+ const text = `{"dialect":"${dialect}","subject":"8","parents":["8","r"]}`;
26
+ const m = detect(utf8Bytes(text));
27
+ assertEq(m.type, 'text');
28
+ assertEq(m.mime_type, 'text/plain');
29
+ },
30
+ // Ordinary JSON with no `dialect` field at all falls through unchanged.
31
+ ordinaryJsonFallsThrough: () => {
32
+ const m = detect(utf8Bytes('{"hello":"world"}'));
33
+ assertEq(m.type, 'text');
34
+ assertEq(m.mime_type, 'text/plain');
35
+ },
36
+ // Plain (non-JSON) text is unaffected.
37
+ plainTextFallsThrough: () => {
38
+ const m = detect(utf8Bytes('hello'));
39
+ assertEq(m.type, 'text');
40
+ assertEq(m.mime_type, 'text/plain');
41
+ },
42
+ // Binary content (magic-byte hit) is unaffected by dialect detection.
43
+ binaryFallsThrough: () => {
44
+ const m = detect(u8ListToVec(msb)([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
45
+ assertEq(m.type, 'base64');
46
+ assertEq(m.mime_type, 'image/png');
47
+ },
48
+ // A large blob is still bounded by the single-`Vec` `maxLength` cap; this
49
+ // only exercises that dialect detection doesn't change size handling for
50
+ // non-JSON content within that bound.
51
+ largeNonJsonWithinBound: () => {
52
+ const a = repeat(1000n)(vec8(0x61n)); // 1,000 bytes of 'a'
53
+ const m = detect(a);
54
+ assertEq(m.type, 'text');
55
+ assertEq(m.mime_type, 'text/plain');
56
+ },
57
+ };
@@ -0,0 +1,51 @@
1
+ import { type ValidationError } from '../../types/rtti/validate/module.f.ts';
2
+ import type { Ts } from '../../types/rtti/ts/module.f.ts';
3
+ import type { Unknown } from '../json/module.f.ts';
4
+ import { type Result } from '../../types/result/module.f.ts';
5
+ /**
6
+ * Format tag: names the dialect of this BLOB. The media type it is served
7
+ * with is derived mechanically: `application/` + `dialect` + `+json`.
8
+ */
9
+ export declare const dialect: 'vnd.fjs.revision';
10
+ /** The media type derived from {@link dialect}: `application/vnd.fjs.revision+json`. */
11
+ export declare const mediaType: "application/vnd.fjs.revision+json";
12
+ /**
13
+ * rtti schema for the snapshot-reference type: a cbase32 CAS hash string.
14
+ *
15
+ * This is `string` at the rtti (structural) level — rtti schemas can't
16
+ * express string-content refinements — so cbase32 decodability, and the
17
+ * rejection of `https://` bridge URLs and any other location-addressed
18
+ * reference form, is enforced by {@link isHash} / {@link validate} below,
19
+ * not by this schema on its own.
20
+ */
21
+ export declare const hash: import("../../types/rtti/module.f.ts").String;
22
+ /**
23
+ * rtti schema for a `revision` BLOB. See the README for the full semantics of
24
+ * each field; `dialect` is the type discriminant, matched here as an exact
25
+ * literal so structural validation alone rejects any other dialect's blob.
26
+ */
27
+ export declare const revisionSchema: {
28
+ readonly dialect: "vnd.fjs.revision";
29
+ readonly subject: import("../../types/rtti/module.f.ts").String;
30
+ readonly parents: import("../../types/rtti/module.f.ts").Type1<"array", import("../../types/rtti/module.f.ts").String>;
31
+ readonly snapshot: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").String, undefined]>;
32
+ readonly generation: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Number, undefined]>;
33
+ readonly archived: import("../../types/rtti/module.f.ts").Or<readonly [true, undefined]>;
34
+ };
35
+ /** The TypeScript type derived from {@link revisionSchema} — the single source of truth. */
36
+ export type Revision = Ts<typeof revisionSchema>;
37
+ /** True when `s` decodes as a cbase32 CAS hash (rejects `https://` and any other non-cbase32 string). */
38
+ export declare const isHash: (s: string) => boolean;
39
+ /** Either a structural validation error or a semantic (snapshot-reference) error message. */
40
+ export type RevisionError = ValidationError | string;
41
+ /**
42
+ * Validates an already-parsed JSON value as a `revision` BLOB: structural
43
+ * (rtti) validation followed by the snapshot-reference semantic checks.
44
+ */
45
+ export declare const validate: (value: Unknown) => Result<Revision, RevisionError>;
46
+ /**
47
+ * Decodes `text` as a `revision` BLOB: JSON-parses it, then validates it per
48
+ * {@link validate}. Detection is semantic, not syntactic — any JSON that
49
+ * satisfies the schema is a revision, regardless of key order or whitespace.
50
+ */
51
+ export declare const decodeText: (text: string) => Result<Revision, RevisionError>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * `vnd.fjs.revision` — one step in the evolution of a mutable object on top
3
+ * of an immutable content-addressable store.
4
+ *
5
+ * A revision BLOB links back to its parent revision(s) (a DAG, not just a
6
+ * chain, so concurrent edits can merge) and carries the full materialized
7
+ * content of that step — never an incremental diff (see the versioning rule
8
+ * in the README). This module is the pure format only: the rtti schema, the
9
+ * `dialect` tag, and decode/validate. No store access, no effects — head
10
+ * resolution, materialization, and reverse indexes are a separate, deferred
11
+ * concern.
12
+ *
13
+ * See `README.md` for the full spec.
14
+ *
15
+ * @module
16
+ */
17
+ import { array, number, option, string } from '../../types/rtti/module.f.js';
18
+ import { validate as rttiValidate } from '../../types/rtti/validate/module.f.js';
19
+ import { parse as jsonParse } from '../json/parser/module.f.js';
20
+ import { tokenize as jsonTokenize } from '../json/tokenizer/module.f.js';
21
+ import { stringToList } from '../../text/utf16/module.f.js';
22
+ import { cBase32ToVec } from '../../basen/cbase32/module.f.js';
23
+ import { error, ok } from '../../types/result/module.f.js';
24
+ /**
25
+ * Format tag: names the dialect of this BLOB. The media type it is served
26
+ * with is derived mechanically: `application/` + `dialect` + `+json`.
27
+ */
28
+ export const dialect = 'vnd.fjs.revision';
29
+ /** The media type derived from {@link dialect}: `application/vnd.fjs.revision+json`. */
30
+ export const mediaType = `application/${dialect}+json`;
31
+ /**
32
+ * rtti schema for the snapshot-reference type: a cbase32 CAS hash string.
33
+ *
34
+ * This is `string` at the rtti (structural) level — rtti schemas can't
35
+ * express string-content refinements — so cbase32 decodability, and the
36
+ * rejection of `https://` bridge URLs and any other location-addressed
37
+ * reference form, is enforced by {@link isHash} / {@link validate} below,
38
+ * not by this schema on its own.
39
+ */
40
+ export const hash = string;
41
+ /**
42
+ * rtti schema for a `revision` BLOB. See the README for the full semantics of
43
+ * each field; `dialect` is the type discriminant, matched here as an exact
44
+ * literal so structural validation alone rejects any other dialect's blob.
45
+ */
46
+ export const revisionSchema = {
47
+ dialect,
48
+ subject: string,
49
+ parents: array(hash),
50
+ snapshot: option(hash),
51
+ generation: option(number),
52
+ archived: option(true),
53
+ };
54
+ /** Structural-only validator: checks the shape, not the snapshot-reference semantics. */
55
+ const validateShape = rttiValidate(revisionSchema);
56
+ /** True when `s` decodes as a cbase32 CAS hash (rejects `https://` and any other non-cbase32 string). */
57
+ export const isHash = (s) => cBase32ToVec(s) !== null;
58
+ /**
59
+ * Checks the snapshot-reference semantics of an already shape-valid revision:
60
+ * every parent must be a hash; `snapshot`, when present, must be a hash;
61
+ * when `snapshot` is absent, zero parents fall back to `subject` (which must
62
+ * then be a hash), exactly one parent inherits that parent's snapshot, and
63
+ * more than one parent without an explicit `snapshot` is invalid — there is
64
+ * no single parent snapshot to inherit, and falling back to `subject` would
65
+ * silently lose the merge result.
66
+ */
67
+ const checkReferences = (r) => {
68
+ for (const p of r.parents) {
69
+ if (!isHash(p)) {
70
+ return error(`parent is not a valid hash: ${p}`);
71
+ }
72
+ }
73
+ if (r.snapshot !== undefined) {
74
+ return isHash(r.snapshot) ? ok(r) : error(`snapshot is not a valid hash: ${r.snapshot}`);
75
+ }
76
+ if (r.parents.length === 0) {
77
+ return isHash(r.subject)
78
+ ? ok(r)
79
+ : error(`subject must be a valid hash when snapshot is absent and there are no parents: ${r.subject}`);
80
+ }
81
+ if (r.parents.length === 1) {
82
+ return ok(r);
83
+ }
84
+ return error('snapshot is required when a revision has more than one parent');
85
+ };
86
+ /**
87
+ * Validates an already-parsed JSON value as a `revision` BLOB: structural
88
+ * (rtti) validation followed by the snapshot-reference semantic checks.
89
+ */
90
+ export const validate = (value) => {
91
+ const [t, v] = validateShape(value);
92
+ if (t === 'error') {
93
+ return error(v);
94
+ }
95
+ return checkReferences(v);
96
+ };
97
+ /** Parses `text` as JSON without relying on `JSON.parse` (the shared `fs/media/json` parser/tokenizer). */
98
+ const parseJson = (text) => jsonParse(jsonTokenize(stringToList(text)));
99
+ /**
100
+ * Decodes `text` as a `revision` BLOB: JSON-parses it, then validates it per
101
+ * {@link validate}. Detection is semantic, not syntactic — any JSON that
102
+ * satisfies the schema is a revision, regardless of key order or whitespace.
103
+ */
104
+ export const decodeText = (text) => {
105
+ const [t, v] = parseJson(text);
106
+ if (t === 'error') {
107
+ return error(v);
108
+ }
109
+ return validate(v);
110
+ };
@@ -0,0 +1,29 @@
1
+ export declare const proof: {
2
+ dialectAndMediaType: () => void;
3
+ isHash: {
4
+ validHash: () => void;
5
+ httpsRejected: () => void;
6
+ arbitraryStringRejected: () => void;
7
+ };
8
+ validate: {
9
+ firstRevisionFallsBackToSubjectHash: () => void;
10
+ firstRevisionRejectsNonHashSubject: () => void;
11
+ explicitSnapshotAllowsArbitrarySubject: () => void;
12
+ singleParentInheritsSnapshot: () => void;
13
+ multiParentWithoutSnapshotRejected: () => void;
14
+ multiParentWithSnapshotAccepted: () => void;
15
+ httpsRejectedInParents: () => void;
16
+ httpsRejectedInSnapshot: () => void;
17
+ httpsRejectedAsFallbackSubject: () => void;
18
+ archivedAccepted: () => void;
19
+ wrongDialectRejected: () => void;
20
+ missingSubjectRejected: () => void;
21
+ extraFieldsAccepted: () => void;
22
+ };
23
+ decodeText: {
24
+ validJson: () => void;
25
+ keyOrderIndependent: () => void;
26
+ malformedJsonRejected: () => void;
27
+ ordinaryJsonRejected: () => void;
28
+ };
29
+ };
@@ -0,0 +1,119 @@
1
+ import { assert, assertEq } from '../../asserts/module.f.js';
2
+ import { dialect, mediaType, isHash, validate, decodeText } from './module.f.js';
3
+ // Valid cbase32 hashes (round-tripped in fs/basen/cbase32/proof.f.ts): single
4
+ // cbase32 symbols, cheap to write inline here.
5
+ const h1 = '8';
6
+ const h2 = 'r';
7
+ const revisionOf = (extra) => ({
8
+ dialect,
9
+ subject: h1,
10
+ parents: [],
11
+ ...extra,
12
+ });
13
+ export const proof = {
14
+ dialectAndMediaType: () => {
15
+ assertEq(dialect, 'vnd.fjs.revision');
16
+ assertEq(mediaType, 'application/vnd.fjs.revision+json');
17
+ },
18
+ isHash: {
19
+ validHash: () => assert(isHash(h1)),
20
+ httpsRejected: () => assert(!isHash('https://example.com/blob/abc')),
21
+ arbitraryStringRejected: () => assert(!isHash('not-a-hash!')),
22
+ },
23
+ validate: {
24
+ // Zero parents, no `snapshot`: `subject` is the fallback snapshot
25
+ // reference and must itself be a hash.
26
+ firstRevisionFallsBackToSubjectHash: () => {
27
+ const [t] = validate(revisionOf({}));
28
+ assertEq(t, 'ok');
29
+ },
30
+ // Zero parents, no `snapshot`, `subject` is not a hash: invalid.
31
+ firstRevisionRejectsNonHashSubject: () => {
32
+ const [t] = validate(revisionOf({ subject: 'my-config' }));
33
+ assertEq(t, 'error');
34
+ },
35
+ // `snapshot` present: `subject` may be an arbitrary string.
36
+ explicitSnapshotAllowsArbitrarySubject: () => {
37
+ const [t] = validate(revisionOf({ subject: 'my-config', snapshot: h2 }));
38
+ assertEq(t, 'ok');
39
+ },
40
+ // Exactly one parent, no `snapshot`: the parent's snapshot is inherited,
41
+ // and `subject` may be an arbitrary string.
42
+ singleParentInheritsSnapshot: () => {
43
+ const [t] = validate(revisionOf({ subject: 'my-config', parents: [h2] }));
44
+ assertEq(t, 'ok');
45
+ },
46
+ // More than one parent with no `snapshot`: invalid — no single parent
47
+ // snapshot to inherit, and falling back to `subject` would silently
48
+ // lose the merge result.
49
+ multiParentWithoutSnapshotRejected: () => {
50
+ const [t] = validate(revisionOf({ subject: 'my-config', parents: [h1, h2] }));
51
+ assertEq(t, 'error');
52
+ },
53
+ // A merge revision with an explicit `snapshot` is valid.
54
+ multiParentWithSnapshotAccepted: () => {
55
+ const [t, v] = validate(revisionOf({ subject: 'my-config', parents: [h1, h2], snapshot: h2 }));
56
+ assertEq(t, 'ok');
57
+ assertEq(v.snapshot, h2);
58
+ },
59
+ // `https://` bridge URLs are rejected wherever a hash is required.
60
+ httpsRejectedInParents: () => {
61
+ const [t] = validate(revisionOf({ subject: 'my-config', parents: ['https://example.com/x'], snapshot: h2 }));
62
+ assertEq(t, 'error');
63
+ },
64
+ httpsRejectedInSnapshot: () => {
65
+ const [t] = validate(revisionOf({ subject: 'my-config', snapshot: 'https://example.com/x' }));
66
+ assertEq(t, 'error');
67
+ },
68
+ httpsRejectedAsFallbackSubject: () => {
69
+ const [t] = validate(revisionOf({ subject: 'https://example.com/x' }));
70
+ assertEq(t, 'error');
71
+ },
72
+ // `archived` follows the presence-only `option(true)` idiom.
73
+ archivedAccepted: () => {
74
+ const [t] = validate(revisionOf({ archived: true }));
75
+ assertEq(t, 'ok');
76
+ },
77
+ // Wrong dialect tag: structural validation rejects it outright.
78
+ wrongDialectRejected: () => {
79
+ const [t] = validate({ dialect: 'vnd.fjs.other', subject: h1, parents: [] });
80
+ assertEq(t, 'error');
81
+ },
82
+ // Missing required fields: rejected.
83
+ missingSubjectRejected: () => {
84
+ const [t] = validate({ dialect, parents: [] });
85
+ assertEq(t, 'error');
86
+ },
87
+ // rtti structs are open: extra fields don't break validation — the
88
+ // additive forward-compatibility path the versioning rule relies on.
89
+ extraFieldsAccepted: () => {
90
+ const [t] = validate(revisionOf({ future: 'field' }));
91
+ assertEq(t, 'ok');
92
+ },
93
+ },
94
+ decodeText: {
95
+ validJson: () => {
96
+ const [t, v] = decodeText(JSON.stringify(revisionOf({ generation: 0 })));
97
+ assertEq(t, 'ok');
98
+ assertEq(v.subject, h1);
99
+ },
100
+ // Key order carries no meaning: detection parses the JSON and
101
+ // validates the parsed value, so `dialect` need not be first.
102
+ keyOrderIndependent: () => {
103
+ const text = `{"parents":[],"subject":"${h1}","dialect":"${dialect}"}`;
104
+ const [t] = decodeText(text);
105
+ assertEq(t, 'ok');
106
+ },
107
+ malformedJsonRejected: () => {
108
+ const [t] = decodeText('{not json');
109
+ assertEq(t, 'error');
110
+ },
111
+ // Ordinary JSON that isn't a revision at all falls through as an error
112
+ // here — `fs/media`'s composed detector is what falls back to the
113
+ // ordinary detector; this module only reports validity.
114
+ ordinaryJsonRejected: () => {
115
+ const [t] = decodeText('{"hello":"world"}');
116
+ assertEq(t, 'error');
117
+ },
118
+ },
119
+ };
@@ -32,12 +32,12 @@
32
32
  *
33
33
  * @module
34
34
  */
35
- import { type Vec } from '../types/bit_vec/module.f.ts';
36
- import type { Nullable } from '../types/nullable/module.f.ts';
37
- import { type Effect, type Operation } from '../effects/module.f.ts';
38
- import type { List } from '../effects/list/module.f.ts';
39
- import type { IoResult } from '../effects/node/module.f.ts';
40
- import { type Utf8State } from '../text/utf8/module.f.ts';
35
+ import { type Vec } from '../../types/bit_vec/module.f.ts';
36
+ import type { Nullable } from '../../types/nullable/module.f.ts';
37
+ import { type Effect, type Operation } from '../../effects/module.f.ts';
38
+ import type { List } from '../../effects/list/module.f.ts';
39
+ import type { IoResult } from '../../effects/node/module.f.ts';
40
+ import { type Utf8State } from '../../text/utf8/module.f.ts';
41
41
  /**
42
42
  * Detects the MIME type of `bytes` from its leading magic-byte signature.
43
43
  *