functionalscript 0.46.1 → 0.47.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 (178) hide show
  1. package/fjs/basen/cbase32/module.f.mjs +18 -10
  2. package/fjs/basen/cbase32/proof.f.d.mts +3 -0
  3. package/fjs/basen/cbase32/proof.f.mjs +24 -2
  4. package/fjs/basen/proof.f.mjs +8 -16
  5. package/fjs/bnf/ll1/proof.f.d.mts +2 -0
  6. package/fjs/bnf/ll1/proof.f.mjs +33 -18
  7. package/fjs/bnf/module.f.d.mts +2 -2
  8. package/fjs/bnf/module.f.mjs +2 -2
  9. package/fjs/cas/proof.f.mjs +4 -26
  10. package/fjs/ci/common/module.f.mjs +6 -0
  11. package/fjs/ci/config/module.f.d.mts +7 -7
  12. package/fjs/ci/config/module.f.mjs +7 -7
  13. package/fjs/cli/module.f.mjs +14 -10
  14. package/fjs/crypto/vdf/module.f.d.mts +0 -1
  15. package/fjs/crypto/vdf/module.f.mjs +3 -14
  16. package/fjs/dev/module.f.mjs +10 -20
  17. package/fjs/djs/parser/module.f.d.mts +101 -53
  18. package/fjs/djs/parser/module.f.mjs +732 -525
  19. package/fjs/djs/parser/proof.f.d.mts +6 -0
  20. package/fjs/djs/parser/proof.f.mjs +315 -11
  21. package/fjs/djs/parser/types.d.ts +29 -2
  22. package/fjs/djs/tokenizer/module.f.mjs +122 -105
  23. package/fjs/djs/tokenizer/proof.f.mjs +17 -0
  24. package/fjs/djs/transpiler/module.f.mjs +4 -5
  25. package/fjs/djs/transpiler/proof.f.d.mts +2 -0
  26. package/fjs/djs/transpiler/proof.f.mjs +25 -0
  27. package/fjs/edag/amnesia/module.f.d.mts +20 -0
  28. package/fjs/edag/amnesia/module.f.mjs +303 -0
  29. package/fjs/edag/amnesia/proof.f.d.mts +79 -0
  30. package/fjs/edag/amnesia/proof.f.mjs +573 -0
  31. package/fjs/edag/amnesia/types.d.ts +15 -0
  32. package/fjs/edag/module.f.d.mts +410 -0
  33. package/fjs/edag/module.f.mjs +559 -0
  34. package/fjs/edag/proof.f.d.mts +116 -0
  35. package/fjs/edag/proof.f.mjs +686 -0
  36. package/fjs/edag/types.d.ts +55 -0
  37. package/fjs/effects/node/memory/module.d.mts +37 -3
  38. package/fjs/effects/node/memory/module.mjs +38 -4
  39. package/fjs/effects/node/memory/proof.d.mts +5 -2
  40. package/fjs/effects/node/memory/proof.mjs +36 -6
  41. package/fjs/effects/node/module.d.mts +9 -1
  42. package/fjs/effects/node/module.f.d.mts +39 -0
  43. package/fjs/effects/node/module.f.mjs +46 -0
  44. package/fjs/effects/node/module.mjs +234 -32
  45. package/fjs/effects/node/types.d.ts +28 -2
  46. package/fjs/effects/node/virtual/module.f.d.mts +19 -10
  47. package/fjs/effects/node/virtual/module.f.mjs +246 -32
  48. package/fjs/effects/node/virtual/proof.f.d.mts +17 -2
  49. package/fjs/effects/node/virtual/proof.f.mjs +232 -8
  50. package/fjs/effects/node/virtual/types.d.ts +57 -1
  51. package/fjs/emergent_testing/browser/proof.d.mts +55 -0
  52. package/fjs/emergent_testing/browser/proof.mjs +352 -0
  53. package/fjs/emergent_testing/browser/species.proof.d.mts +4 -0
  54. package/fjs/emergent_testing/browser/species.proof.mjs +45 -0
  55. package/fjs/emergent_testing/browser.d.mts +79 -0
  56. package/fjs/emergent_testing/browser.mjs +432 -0
  57. package/fjs/emergent_testing/proof.f.d.mts +1 -1
  58. package/fjs/emergent_testing/proof.f.mjs +2 -2
  59. package/fjs/js/string_escape/module.f.d.mts +47 -0
  60. package/fjs/js/string_escape/module.f.mjs +95 -0
  61. package/fjs/js/string_escape/proof.f.d.mts +8 -0
  62. package/fjs/js/string_escape/proof.f.mjs +68 -0
  63. package/fjs/js/tokenizer/module.f.mjs +14 -12
  64. package/fjs/mcp/cas/module.f.d.mts +1 -7
  65. package/fjs/mcp/cas/module.f.mjs +24 -15
  66. package/fjs/mcp/evo/module.f.mjs +9 -24
  67. package/fjs/mcp/module.f.d.mts +3 -1
  68. package/fjs/mcp/module.f.mjs +4 -2
  69. package/fjs/media/json/rtti/module.f.d.mts +2 -2
  70. package/fjs/media/json/rtti/module.f.mjs +1 -1
  71. package/fjs/media/json/schema/module.f.d.mts +15 -5
  72. package/fjs/media/json/schema/module.f.mjs +42 -15
  73. package/fjs/media/json/schema/proof.f.d.mts +10 -1
  74. package/fjs/media/json/schema/proof.f.mjs +88 -16
  75. package/fjs/media/json/serializer/module.f.mjs +14 -26
  76. package/fjs/media/json/types.d.ts +1 -1
  77. package/fjs/media/lock/module.f.d.mts +8 -3
  78. package/fjs/media/lock/module.f.mjs +9 -3
  79. package/fjs/media/lock/proof.f.d.mts +1 -1
  80. package/fjs/media/lock/proof.f.mjs +2 -2
  81. package/fjs/media/lock/types.d.ts +1 -3
  82. package/fjs/media/module.f.d.mts +20 -13
  83. package/fjs/media/module.f.mjs +29 -16
  84. package/fjs/media/note/module.f.d.mts +14 -7
  85. package/fjs/media/note/module.f.mjs +14 -7
  86. package/fjs/media/note/proof.f.mjs +1 -1
  87. package/fjs/media/proof.f.mjs +5 -5
  88. package/fjs/media/revision/module.f.d.mts +8 -2
  89. package/fjs/media/revision/module.f.mjs +9 -3
  90. package/fjs/media/revision/proof.f.mjs +1 -1
  91. package/fjs/media/revision/types.d.ts +1 -1
  92. package/fjs/media/type/module.f.d.mts +20 -0
  93. package/fjs/media/type/module.f.mjs +74 -0
  94. package/fjs/media/type/proof.f.d.mts +5 -0
  95. package/fjs/media/type/proof.f.mjs +24 -1
  96. package/fjs/module.f.mjs +6 -0
  97. package/fjs/path/module.f.d.mts +60 -3
  98. package/fjs/path/module.f.mjs +138 -14
  99. package/fjs/path/proof.f.d.mts +3 -0
  100. package/fjs/path/proof.f.mjs +298 -2
  101. package/fjs/protocol/json_rpc/module.f.d.mts +65 -28
  102. package/fjs/protocol/json_rpc/module.f.mjs +49 -13
  103. package/fjs/protocol/json_rpc/proof.f.d.mts +4 -0
  104. package/fjs/protocol/json_rpc/proof.f.mjs +29 -0
  105. package/fjs/protocol/json_rpc/types.d.ts +12 -2
  106. package/fjs/protocol/mcp/module.f.d.mts +89 -54
  107. package/fjs/protocol/mcp/module.f.mjs +110 -56
  108. package/fjs/protocol/mcp/proof.f.d.mts +13 -0
  109. package/fjs/protocol/mcp/proof.f.mjs +107 -7
  110. package/fjs/protocol/mcp/stdio/module.f.mjs +3 -4
  111. package/fjs/protocol/mcp/stdio/proof.f.mjs +2 -2
  112. package/fjs/protocol/mcp/types.d.ts +14 -2
  113. package/fjs/text/utf8/module.f.mjs +27 -1
  114. package/fjs/text/utf8/proof.f.mjs +37 -0
  115. package/fjs/types/array/module.f.mjs +18 -11
  116. package/fjs/types/array/proof.f.mjs +27 -1
  117. package/fjs/types/bigfloat/module.f.d.mts +48 -9
  118. package/fjs/types/bigfloat/module.f.mjs +147 -59
  119. package/fjs/types/bigfloat/proof.f.d.mts +2 -0
  120. package/fjs/types/bigfloat/proof.f.mjs +136 -4
  121. package/fjs/types/bigfloat/types.d.ts +17 -0
  122. package/fjs/types/function/module.f.d.mts +11 -2
  123. package/fjs/types/function/module.f.mjs +20 -2
  124. package/fjs/types/function/proof.f.mjs +5 -1
  125. package/fjs/types/phantom/types.d.ts +21 -0
  126. package/fjs/types/result/module.f.d.mts +4 -4
  127. package/fjs/types/result/module.f.mjs +2 -2
  128. package/fjs/types/result/proof.f.d.mts +6 -2
  129. package/fjs/types/result/proof.f.mjs +17 -1
  130. package/fjs/types/rtti/common/module.f.d.mts +78 -5
  131. package/fjs/types/rtti/common/module.f.mjs +168 -3
  132. package/fjs/types/rtti/common/proof.f.d.mts +14 -0
  133. package/fjs/types/rtti/common/proof.f.mjs +61 -2
  134. package/fjs/types/rtti/common/types.d.ts +23 -1
  135. package/fjs/types/rtti/data/module.f.d.mts +57 -1
  136. package/fjs/types/rtti/data/module.f.mjs +241 -47
  137. package/fjs/types/rtti/data/proof.f.d.mts +10 -0
  138. package/fjs/types/rtti/data/proof.f.mjs +272 -39
  139. package/fjs/types/rtti/data/types.d.ts +14 -7
  140. package/fjs/types/rtti/host.proof.d.mts +33 -0
  141. package/fjs/types/rtti/host.proof.mjs +170 -0
  142. package/fjs/types/rtti/module.f.d.mts +51 -6
  143. package/fjs/types/rtti/module.f.mjs +50 -3
  144. package/fjs/types/rtti/parse/module.f.d.mts +31 -24
  145. package/fjs/types/rtti/parse/module.f.mjs +197 -59
  146. package/fjs/types/rtti/parse/proof.f.d.mts +37 -2
  147. package/fjs/types/rtti/parse/proof.f.mjs +171 -47
  148. package/fjs/types/rtti/proof.f.d.mts +6 -0
  149. package/fjs/types/rtti/proof.f.mjs +42 -1
  150. package/fjs/types/rtti/ts/module.f.d.mts +9 -3
  151. package/fjs/types/rtti/ts/module.f.mjs +68 -15
  152. package/fjs/types/rtti/ts/proof.f.d.mts +42 -2
  153. package/fjs/types/rtti/ts/proof.f.mjs +139 -6
  154. package/fjs/types/rtti/ts/types.d.ts +164 -16
  155. package/fjs/types/rtti/types.d.ts +31 -3
  156. package/fjs/types/rtti/validate/module.f.d.mts +42 -25
  157. package/fjs/types/rtti/validate/module.f.mjs +187 -53
  158. package/fjs/types/rtti/validate/proof.f.d.mts +28 -2
  159. package/fjs/types/rtti/validate/proof.f.mjs +430 -75
  160. package/fjs/types/ts/types.d.ts +4 -0
  161. package/fjs/web/module.f.d.mts +115 -0
  162. package/fjs/web/module.f.mjs +581 -0
  163. package/fjs/web/proof.f.d.mts +39 -0
  164. package/fjs/web/proof.f.mjs +413 -0
  165. package/fjs/web/types.d.ts +39 -0
  166. package/fjs/website/browser-prepare.d.mts +5 -0
  167. package/fjs/website/browser-prepare.mjs +115 -0
  168. package/fjs/website/browser-source.d.mts +37 -0
  169. package/fjs/website/browser-source.mjs +202 -0
  170. package/fjs/website/browser-source.proof.d.mts +31 -0
  171. package/fjs/website/browser-source.proof.mjs +162 -0
  172. package/fjs/website/browser.d.mts +5 -0
  173. package/fjs/website/browser.mjs +8 -0
  174. package/fjs/website/module.f.d.mts +3 -3
  175. package/fjs/website/module.f.mjs +62 -9
  176. package/fjs/website/proof.f.d.mts +4 -0
  177. package/fjs/website/proof.f.mjs +18 -3
  178. package/package.json +2 -3
@@ -3,7 +3,7 @@
3
3
  * @import { Data } from '../../../types/rtti/data/types.ts'
4
4
  */
5
5
 
6
- import { boolean, number, string, bigint, never, unknown, array, record, or, option } from '../../../types/rtti/module.f.mjs'
6
+ import { boolean, number, string, bigint, never, unknown, array, open, record, or, option } from '../../../types/rtti/module.f.mjs'
7
7
  import { stringify } from '../module.f.mjs'
8
8
  import { dataToJsonSchema, toJsonSchema, unknown as schemaUnknown } from './module.f.mjs'
9
9
  import { unitBit } from '../../../types/rtti/data/module.f.mjs'
@@ -80,37 +80,77 @@ export const proof = {
80
80
  record: eq(record(string), { type: 'object', additionalProperties: { type: 'string' } }),
81
81
  // `anyOf` members follow the canonical kind order, not the operand order
82
82
  or: eq(or(string, number), { anyOf: [{ type: 'number' }, { type: 'string' }] }),
83
- tuple: eq(/** @type {const} */ ([number, string]), {
84
- type: 'array',
85
- prefixItems: [{ type: 'number' }, { type: 'string' }],
86
- minItems: 2,
87
- items: false,
88
- }),
83
+ // A bare tuple is closed, which JSON Schema spells `items: false`; a
84
+ // position admitting `undefined` may be absent instead, which is
85
+ // `minItems`. `open` is what admits what follows the prefix, and prints
86
+ // the unconstrained `items: {}`.
87
+ tuple: {
88
+ allRequired: eq(/** @type {const} */ ([number, string]), {
89
+ type: 'array',
90
+ prefixItems: [{ type: 'number' }, { type: 'string' }],
91
+ minItems: 2,
92
+ items: false,
93
+ }),
94
+ withOptional: eq(/** @type {const} */ ([number, option(string)]), {
95
+ type: 'array',
96
+ prefixItems: [{ type: 'number' }, { type: 'string' }],
97
+ minItems: 1,
98
+ items: false,
99
+ }),
100
+ allOptional: eq(/** @type {const} */ ([option(number)]), {
101
+ type: 'array',
102
+ prefixItems: [{ type: 'number' }],
103
+ items: false,
104
+ }),
105
+ open: eq(open(/** @type {const} */ ([number, string])), {
106
+ type: 'array',
107
+ prefixItems: [{ type: 'number' }, { type: 'string' }],
108
+ minItems: 2,
109
+ items: {},
110
+ }),
111
+ },
112
+ // The same on the other kind: a bare struct names every key it admits,
113
+ // which is `additionalProperties: { not: {} }`.
89
114
  struct: {
90
115
  allRequired: eq(/** @type {const} */ ({ x: number, y: string }), {
91
116
  type: 'object',
92
117
  properties: { x: { type: 'number' }, y: { type: 'string' } },
93
118
  required: ['x', 'y'],
119
+ additionalProperties: { not: {} },
94
120
  }),
95
121
  withOptional: eq(/** @type {const} */ ({ x: number, y: option(string) }), {
96
122
  type: 'object',
97
123
  properties: { x: { type: 'number' }, y: { type: 'string' } },
98
124
  required: ['x'],
125
+ additionalProperties: { not: {} },
99
126
  }),
100
127
  allOptional: eq(/** @type {const} */ ({ x: option(number) }), {
101
128
  type: 'object',
102
129
  properties: { x: { type: 'number' } },
130
+ additionalProperties: { not: {} },
131
+ }),
132
+ // a closed struct declaring nothing is the empty object
133
+ empty: eq(/** @type {const} */ ({}), {
134
+ type: 'object',
135
+ additionalProperties: { not: {} },
103
136
  }),
104
137
  // an unconstrained struct is the whole object kind
105
- empty: eq(/** @type {const} */ ({}), { type: 'object' }),
138
+ openEmpty: eq(open(/** @type {const} */ ({})), { type: 'object' }),
139
+ open: eq(open(/** @type {const} */ ({ x: number, y: string })), {
140
+ type: 'object',
141
+ properties: { x: { type: 'number' }, y: { type: 'string' } },
142
+ required: ['x', 'y'],
143
+ }),
106
144
  orOptional: eq(/** @type {const} */ ({ x: or(string, number, undefined) }), {
107
145
  type: 'object',
108
146
  properties: { x: { anyOf: [{ type: 'number' }, { type: 'string' }] } },
147
+ additionalProperties: { not: {} },
109
148
  }),
110
149
  withConst: eq(/** @type {const} */ ({ x: null, y: string }), {
111
150
  type: 'object',
112
151
  properties: { x: { const: null }, y: { type: 'string' } },
113
152
  required: ['x', 'y'],
153
+ additionalProperties: { not: {} },
114
154
  }),
115
155
  optionalOfEveryKind: eq(/** @type {const} */ ({
116
156
  a: option(number),
@@ -129,6 +169,7 @@ export const proof = {
129
169
  e: { type: 'object', additionalProperties: { type: 'string' } },
130
170
  f: { const: null },
131
171
  },
172
+ additionalProperties: { not: {} },
132
173
  }),
133
174
  },
134
175
  schemaUnknownTag: () => {
@@ -140,7 +181,7 @@ export const proof = {
140
181
  type: 'array',
141
182
  items: { type: 'object', additionalProperties: { type: 'boolean' } },
142
183
  }),
143
- orWithConst: eq(or(null, string, /** @type {const} */ (42)), {
184
+ orWithConst: eq(or(null, string, 42), {
144
185
  anyOf: [{ const: null }, { const: 42 }, { type: 'string' }],
145
186
  }),
146
187
  structWithOr: eq(/** @type {const} */ ({ id: or(string, number), name: option(string) }), {
@@ -150,7 +191,10 @@ export const proof = {
150
191
  name: { type: 'string' },
151
192
  },
152
193
  required: ['id'],
194
+ additionalProperties: { not: {} },
153
195
  }),
196
+ // the top position is required — the array cannot end before the
197
+ // `number` after it — so its `undefined` is not stripped away
154
198
  topInsideTuple: eq(/** @type {const} */ ([unknown, number]), {
155
199
  type: 'array',
156
200
  prefixItems: [{}, { type: 'number' }],
@@ -163,15 +207,41 @@ export const proof = {
163
207
  unitMembers: eq(or(null, undefined, true), {
164
208
  anyOf: [{ const: null }, { not: {} }, { const: true }],
165
209
  }),
166
- literalAbsorbed: eq(or(/** @type {const} */ (42), number), { type: 'number' }),
167
- duplicateLiteral: eq(or(/** @type {const} */ (1), /** @type {const} */ (1)), { const: 1 }),
210
+ literalAbsorbed: eq(or(42, number), { type: 'number' }),
211
+ duplicateLiteral: eq(or(1, 1), { const: 1 }),
168
212
  never: eq(never, { not: {} }),
213
+ // a closed tuple declaring nothing is the empty array
169
214
  emptyTuple: eq(/** @type {const} */ ([]), { type: 'array', items: false }),
170
- // `readonly [number] readonly number[]` the tuple pattern is dropped
171
- coverageCollapse: eq(or(/** @type {const} */ ([number]), array(number)), {
172
- type: 'array',
173
- items: { type: 'number' },
174
- }),
215
+ // an open one declaring nothing is the whole array kind
216
+ openEmptyTuple: eq(open(/** @type {const} */ ([])), { type: 'array' }),
217
+ // a longer tuple pattern is included in a shorter one when both are
218
+ // open; closed, the two lengths are disjoint and both patterns stay
219
+ coverageCollapse: eq(
220
+ or(open([number, number]), open([number])),
221
+ {
222
+ type: 'array',
223
+ prefixItems: [{ type: 'number' }],
224
+ minItems: 1,
225
+ items: {},
226
+ }),
227
+ noCollapseWhenClosed: eq(
228
+ or(/** @type {const} */ ([number, number]), /** @type {const} */ ([number])),
229
+ {
230
+ anyOf: [
231
+ {
232
+ type: 'array',
233
+ prefixItems: [{ type: 'number' }],
234
+ minItems: 1,
235
+ items: false,
236
+ },
237
+ {
238
+ type: 'array',
239
+ prefixItems: [{ type: 'number' }, { type: 'number' }],
240
+ minItems: 2,
241
+ items: false,
242
+ },
243
+ ],
244
+ }),
175
245
  commutative: () => {
176
246
  const a = serialize(toJsonSchema(or(string, number)))
177
247
  const b = serialize(toJsonSchema(or(number, string)))
@@ -193,6 +263,7 @@ export const proof = {
193
263
  optionalRecursiveProperty: eq(/** @type {const} */ ({ p: option(list) }), {
194
264
  type: 'object',
195
265
  properties: { p: { type: 'array', items: listRef } },
266
+ additionalProperties: { not: {} },
196
267
  $defs: { list: listDef },
197
268
  }),
198
269
  revisionLock: eq(lock, {
@@ -215,6 +286,7 @@ export const proof = {
215
286
  type: 'object',
216
287
  properties: { name: { type: 'string' } },
217
288
  required: ['name'],
289
+ additionalProperties: { not: {} },
218
290
  }
219
291
  eq(/** @type {const} */ ([person, person]), {
220
292
  type: 'array',
@@ -24,36 +24,14 @@ import { codePointToString, stringToCodePointList } from '../../../text/utf16/mo
24
24
  import { errorMask } from '../../../text/code_point/module.f.mjs'
25
25
  import { definedEntries, isObject } from '../../../types/object/module.f.mjs'
26
26
  import { compose, fn } from '../../../types/function/module.f.mjs'
27
- import {
28
- backspace,
29
- cr,
30
- ff,
31
- hexDigitCodePoint,
32
- ht,
33
- lf,
34
- quotationMark,
35
- reverseSolidus,
36
- space,
37
- } from '../../../text/ascii/module.f.mjs'
27
+ import { hexDigitCodePoint, space } from '../../../text/ascii/module.f.mjs'
28
+ import { codePointToEscape } from '../../../js/string_escape/module.f.mjs'
29
+ import { map as nullableMap } from '../../../types/nullable/module.f.mjs'
38
30
 
39
31
  const jsonStringify = JSON.stringify
40
32
 
41
33
  const { fromCharCode } = String
42
34
 
43
- /**
44
- * The code points JSON gives a two-character escape. Every other code point
45
- * below `space` has no short form and goes through `unicodeEscape` instead.
46
- */
47
- const escapeTable = /** @type {const} */ ({
48
- [backspace]: '\\b',
49
- [ht]: '\\t',
50
- [lf]: '\\n',
51
- [ff]: '\\f',
52
- [cr]: '\\r',
53
- [quotationMark]: '\\"',
54
- [reverseSolidus]: '\\\\',
55
- })
56
-
57
35
  /** @type {(value: number) => string} */
58
36
  const hexDigit = value => fromCharCode(hexDigitCodePoint(value))
59
37
 
@@ -65,6 +43,16 @@ const hexDigit = value => fromCharCode(hexDigitCodePoint(value))
65
43
  const unicodeEscape = unit =>
66
44
  `\\u${hexDigit(unit >> 12 & 0xf)}${hexDigit(unit >> 8 & 0xf)}${hexDigit(unit >> 4 & 0xf)}${hexDigit(unit & 0xf)}`
67
45
 
46
+ /**
47
+ * The two-character escape a letter spells, projected over the lookup's
48
+ * `null`. Bound here rather than inside `escapeCodePoint`: it depends on
49
+ * nothing that varies per code point, and `escapeCodePoint` runs once per
50
+ * character of every string serialized.
51
+ *
52
+ * @type {(letter: number | null) => string | null}
53
+ */
54
+ const simpleEscape = nullableMap(letter => `\\${codePointToString(letter)}`)
55
+
68
56
  /**
69
57
  * Escapes one decoded code point. A code point tagged with `errorMask` is an
70
58
  * unpaired surrogate, which well-formed JSON stringification (ES2019) emits as
@@ -76,7 +64,7 @@ const unicodeEscape = unit =>
76
64
  const escapeCodePoint = codePoint =>
77
65
  (codePoint & errorMask) !== 0
78
66
  ? unicodeEscape(codePoint & 0xffff)
79
- : escapeTable[codePoint]
67
+ : simpleEscape(codePointToEscape(codePoint))
80
68
  ?? (codePoint < space ? unicodeEscape(codePoint) : codePointToString(codePoint))
81
69
 
82
70
  /**
@@ -5,7 +5,7 @@
5
5
  * `Unknown` is written by hand rather than derived, so that the recursion
6
6
  * reads directly, and is then pinned against the rtti schemas in the sibling
7
7
  * [`./rtti/module.f.mjs`](./rtti/module.f.mjs) with
8
- * `Assert<Equal<Unknown, Ts<typeof unknown>>>`. The pin is what keeps the two
8
+ * `Assert<Check<Unknown, typeof unknown>>`. The pin is what keeps the two
9
9
  * descriptions of the same data model from drifting apart — and it holds the
10
10
  * `Tree<Primitive>` spelling to the same data model the schemas describe.
11
11
  *
@@ -41,7 +41,12 @@ export declare const dialect: 'vnd.fjs.lock';
41
41
  /** The media type derived from {@link dialect}: `application/vnd.fjs.lock+json`. */
42
42
  export declare const mediaType: "application/vnd.fjs.lock+json";
43
43
  /**
44
- * rtti schema for a `lock` BLOB: the dialect tag and the map, nothing else.
44
+ * rtti schema for a `lock` BLOB: the dialect tag and the map.
45
+ *
46
+ * `open`, and deliberately so: a bare struct is closed, so an older reader
47
+ * would reject a blob a newer writer had added a field to, and the versioning
48
+ * rule in `../revision/README.md` — additive extension keeps the tag — needs
49
+ * that older reader to accept it. Do not drop the wrapper.
45
50
  *
46
51
  * `lock` is `fjs/media/revision`'s own schema, so the shared form admits
47
52
  * exactly the maps the inline form does, to the same depth, with the same
@@ -54,10 +59,10 @@ export declare const mediaType: "application/vnd.fjs.lock+json";
54
59
  * [Composition](./README.md#composition)), so following a reference always
55
60
  * terminates in one step.
56
61
  */
57
- export declare const lockSchema: {
62
+ export declare const lockSchema: import("../../types/rtti/types.ts").Rest<{
58
63
  readonly dialect: "vnd.fjs.lock";
59
64
  readonly lock: import("../revision/types.ts").LockSchema;
60
- };
65
+ }, import("../../types/rtti/types.ts").Unknown>;
61
66
  /** Serializes a lock blob canonically, recursively sorting every object's property names.
62
67
  * @type {(lock: Lock) => string}
63
68
  */
@@ -30,6 +30,7 @@
30
30
  * @import { Lock, LockError } from './types.ts'
31
31
  */
32
32
 
33
+ import { open } from '../../types/rtti/module.f.mjs'
33
34
  import { parse as rttiParse } from '../../types/rtti/parse/module.f.mjs'
34
35
  import { parse as parseJson, stringify } from '../json/module.f.mjs'
35
36
  import { error, ok, okThen } from '../../types/result/module.f.mjs'
@@ -47,7 +48,12 @@ export const dialect = /** @type {const} */ ('vnd.fjs.lock')
47
48
  export const mediaType = /** @type {const} */ (`application/${dialect}+json`)
48
49
 
49
50
  /**
50
- * rtti schema for a `lock` BLOB: the dialect tag and the map, nothing else.
51
+ * rtti schema for a `lock` BLOB: the dialect tag and the map.
52
+ *
53
+ * `open`, and deliberately so: a bare struct is closed, so an older reader
54
+ * would reject a blob a newer writer had added a field to, and the versioning
55
+ * rule in `../revision/README.md` — additive extension keeps the tag — needs
56
+ * that older reader to accept it. Do not drop the wrapper.
51
57
  *
52
58
  * `lock` is `fjs/media/revision`'s own schema, so the shared form admits
53
59
  * exactly the maps the inline form does, to the same depth, with the same
@@ -60,10 +66,10 @@ export const mediaType = /** @type {const} */ (`application/${dialect}+json`)
60
66
  * [Composition](./README.md#composition)), so following a reference always
61
67
  * terminates in one step.
62
68
  */
63
- export const lockSchema = /** @type {const} */ ({
69
+ export const lockSchema = open(/** @type {const} */ ({
64
70
  dialect,
65
71
  lock,
66
- })
72
+ }))
67
73
 
68
74
  /** Serializes a lock blob canonically, recursively sorting every object's property names.
69
75
  * @type {(lock: Lock) => string}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @import { Object as JsonObject } from '../json/types.ts'
3
- * @import { LockMap } from './types.ts'
3
+ * @import { LockMap } from '../revision/types.ts'
4
4
  */
5
5
  export declare const proof: {
6
6
  dialectAndMediaType: () => void;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @import { Object as JsonObject } from '../json/types.ts'
3
- * @import { LockMap } from './types.ts'
3
+ * @import { LockMap } from '../revision/types.ts'
4
4
  */
5
5
 
6
6
  import { assert, assertEq } from '../../asserts/module.f.mjs'
@@ -89,7 +89,7 @@ export const proof = {
89
89
  },
90
90
 
91
91
  // No `subject`, `parents`, `generation` or `archived`: this dialect is
92
- // a value, not a step. rtti structs are open, so such fields are
92
+ // a value, not a step. `lockSchema` says `open`, so such fields are
93
93
  // ignored rather than rejected — the additive forward-compatibility
94
94
  // path, the same one the revision dialect relies on.
95
95
  revisionFieldsIgnored: () => {
@@ -2,13 +2,11 @@
2
2
  * Type-level API for `fjs/media/lock/module.f.mjs`: `Lock` and `LockError`.
3
3
  *
4
4
  * The map type itself is not restated here — it is `fjs/media/revision`'s
5
- * `LockMap`, re-exported so a consumer of the shared form needs only one
6
- * import, and so nothing can define a second, drifting spelling of it.
5
+ * `LockMap`; import it from there directly rather than through this module.
7
6
  */
8
7
  import type { ValidationError } from '../../types/rtti/common/types.ts';
9
8
  import type { Ts } from '../../types/rtti/ts/types.ts';
10
9
  import type { lockSchema } from './module.f.mjs';
11
- export type { LockMap } from '../revision/types.ts';
12
10
  /** The TypeScript type derived from `lockSchema` — the single source of truth. */
13
11
  export type Lock = Ts<typeof lockSchema>;
14
12
  /** Either a structural validation error or a semantic (hash) error message. */
@@ -44,14 +44,14 @@
44
44
  *
45
45
  * @import { Vec } from '../types/bit_vec/types.ts'
46
46
  * @import { DetectMeta } from './type/types.ts'
47
- * @import { Struct } from '../types/rtti/types.ts'
47
+ * @import { Rest, Struct, Type } from '../types/rtti/types.ts'
48
48
  * @import { Ts, Unknown } from '../types/rtti/ts/types.ts'
49
- * @import { Validate } from '../types/rtti/common/types.ts'
49
+ * @import { ValidateE } from '../types/rtti/common/types.ts'
50
50
  * @import { DialectEntry } from './types.ts'
51
51
  */
52
52
  import type { Vec } from '../types/bit_vec/types.ts';
53
53
  import type { DetectMeta } from './type/types.ts';
54
- import type { Struct } from '../types/rtti/types.ts';
54
+ import type { Rest, Struct, Type } from '../types/rtti/types.ts';
55
55
  import type { Ts } from '../types/rtti/ts/types.ts';
56
56
  import type { DialectEntry } from './types.ts';
57
57
  /**
@@ -87,21 +87,28 @@ import type { DialectEntry } from './types.ts';
87
87
  * not.) Registering a name that is not a valid RFC 6838 restricted-name yields
88
88
  * a malformed media type in the registrant's own results, and nowhere else.
89
89
  *
90
- * The constraint is `Struct` — every member must be a real rtti `Type`, which
91
- * is what rejects a member like `() => 42` at compile time (rtti would read it
92
- * as a thunk and `match` would *throw* on the first blob rather than return
93
- * `false`). TypeScript cannot also require a direct string `dialect` member
94
- * under that constraint, so that half is asserted here instead: loudly, once,
95
- * when the entry is constructed. A thunk-form `dialect`
90
+ * The container's constraint is `Struct` — every member must be a real rtti
91
+ * `Type`, which is what rejects a member like `() => 42` at compile time (rtti
92
+ * would read it as a thunk and `match` would *throw* on the first blob rather
93
+ * than return `false`). TypeScript cannot also require a direct string
94
+ * `dialect` member under that constraint, so that half is asserted here
95
+ * instead: loudly, once, when the entry is constructed. A thunk-form `dialect`
96
96
  * (`() => ['const', 'x']`) is a perfectly valid rtti schema, it just is not
97
97
  * registerable — write the string directly, as `revisionSchema` does.
98
98
  *
99
- * @template {Struct} T
100
- * @param {T} type
101
- * @param {(_: Ts<T>) => boolean} [extraValidate]
99
+ * The schema is a **container with a stated rest** rather than a bare struct,
100
+ * and every dialect in this repo states `open`. A bare struct is closed, so an
101
+ * older reader would reject a blob a newer writer had added a field to, which
102
+ * is precisely the fail-closed misread the additive-extension rule in
103
+ * `./revision/README.md` exists to avoid. Requiring the wrapper here is what
104
+ * makes that a decision a registrant states rather than one they inherit.
105
+ *
106
+ * @template {Struct} S
107
+ * @param {Rest<S, Type>} type
108
+ * @param {(_: Ts<S>) => boolean} [extraValidate]
102
109
  * @returns {DialectEntry}
103
110
  */
104
- export declare const dialectEntry: <T extends Struct>(type: T, extraValidate?: (_: Ts<T>) => boolean) => DialectEntry;
111
+ export declare const dialectEntry: <S extends Struct>(type: Rest<S, Type>, extraValidate?: (_: Ts<S>) => boolean) => DialectEntry;
105
112
  /**
106
113
  * Classifies a whole buffered `Vec` against `dialects`, returning the same
107
114
  * three-way `{ length, mime_type, type }` shape as `fjs/media/type`
@@ -44,9 +44,9 @@
44
44
  *
45
45
  * @import { Vec } from '../types/bit_vec/types.ts'
46
46
  * @import { DetectMeta } from './type/types.ts'
47
- * @import { Struct } from '../types/rtti/types.ts'
47
+ * @import { Rest, Struct, Type } from '../types/rtti/types.ts'
48
48
  * @import { Ts, Unknown } from '../types/rtti/ts/types.ts'
49
- * @import { Validate } from '../types/rtti/common/types.ts'
49
+ * @import { ValidateE } from '../types/rtti/common/types.ts'
50
50
  * @import { DialectEntry } from './types.ts'
51
51
  */
52
52
 
@@ -61,9 +61,13 @@ const always = () => true
61
61
 
62
62
  /**
63
63
  * Structural validation followed by the dialect's own refinement.
64
- * @template {Struct} T
65
- * @param {Validate<T>} v
66
- * @returns {(extraValidate: (_: Ts<T>) => boolean) => (u: Unknown) => boolean}
64
+ *
65
+ * Typed over the **erased** `ValidateE`: a `T extends Type` template would
66
+ * instantiate `Ts<Type>` here and raise TS2589. {@link dialectEntry} states
67
+ * the precise `extraValidate` type its callers are held to.
68
+ *
69
+ * @param {ValidateE} v
70
+ * @returns {(extraValidate: (_: any) => boolean) => (u: Unknown) => boolean}
67
71
  */
68
72
  const matchWith = v => extraValidate => u => {
69
73
  const [tag, value] = v(u)
@@ -103,24 +107,33 @@ const matchWith = v => extraValidate => u => {
103
107
  * not.) Registering a name that is not a valid RFC 6838 restricted-name yields
104
108
  * a malformed media type in the registrant's own results, and nowhere else.
105
109
  *
106
- * The constraint is `Struct` — every member must be a real rtti `Type`, which
107
- * is what rejects a member like `() => 42` at compile time (rtti would read it
108
- * as a thunk and `match` would *throw* on the first blob rather than return
109
- * `false`). TypeScript cannot also require a direct string `dialect` member
110
- * under that constraint, so that half is asserted here instead: loudly, once,
111
- * when the entry is constructed. A thunk-form `dialect`
110
+ * The container's constraint is `Struct` — every member must be a real rtti
111
+ * `Type`, which is what rejects a member like `() => 42` at compile time (rtti
112
+ * would read it as a thunk and `match` would *throw* on the first blob rather
113
+ * than return `false`). TypeScript cannot also require a direct string
114
+ * `dialect` member under that constraint, so that half is asserted here
115
+ * instead: loudly, once, when the entry is constructed. A thunk-form `dialect`
112
116
  * (`() => ['const', 'x']`) is a perfectly valid rtti schema, it just is not
113
117
  * registerable — write the string directly, as `revisionSchema` does.
114
118
  *
115
- * @template {Struct} T
116
- * @param {T} type
117
- * @param {(_: Ts<T>) => boolean} [extraValidate]
119
+ * The schema is a **container with a stated rest** rather than a bare struct,
120
+ * and every dialect in this repo states `open`. A bare struct is closed, so an
121
+ * older reader would reject a blob a newer writer had added a field to, which
122
+ * is precisely the fail-closed misread the additive-extension rule in
123
+ * `./revision/README.md` exists to avoid. Requiring the wrapper here is what
124
+ * makes that a decision a registrant states rather than one they inherit.
125
+ *
126
+ * @template {Struct} S
127
+ * @param {Rest<S, Type>} type
128
+ * @param {(_: Ts<S>) => boolean} [extraValidate]
118
129
  * @returns {DialectEntry}
119
130
  */
120
131
  export const dialectEntry = (type, extraValidate = always) => {
121
- const { dialect } = type
132
+ const [, container] = type()
133
+ const { dialect } = /** @type {Struct} */ (container)
122
134
  assert(typeof dialect === 'string', 'dialectEntry: schema has no direct string `dialect` member')
123
- return { dialect, match: matchWith(rttiParse(type))(extraValidate) }
135
+ const v = /** @type {ValidateE} */ (rttiParse(type))
136
+ return { dialect, match: matchWith(v)(extraValidate) }
124
137
  }
125
138
 
126
139
  /**
@@ -5,10 +5,10 @@
5
5
  * The format is deliberately minimal: the dialect tag, the text, and
6
6
  * optionally the subjects the item depends on and a priority. Everything
7
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.
8
+ * **optional** field: {@link noteSchema} says `open`, so an older reader
9
+ * accepts a blob a newer writer has added fields to and additive extension
10
+ * keeps the tag (see the versioning rule in `fjs/media/revision/README.md`),
11
+ * and starting minimal is what keeps every extension additive.
12
12
  *
13
13
  * Like `vnd.fjs.lock`, a note is a **value**, not a step: no timestamps, no
14
14
  * author, no history of its own. Edits over time are ordinary
@@ -85,12 +85,19 @@ export declare const mediaType: "application/vnd.fjs.note+json";
85
85
  * rather than misreading it.
86
86
  */
87
87
  export declare const priorities: readonly ['P1', 'P2', 'P3', 'P4', 'P5'];
88
- export declare const noteSchema: {
88
+ /**
89
+ * `open`, and that is the extension strategy rather than a default: a bare
90
+ * struct is closed, so an older reader would reject a blob a newer writer had
91
+ * added a field to, and the versioning rule in
92
+ * `../revision/README.md` — additive extension keeps the tag — depends on the
93
+ * older reader accepting it. Do not drop the wrapper.
94
+ */
95
+ export declare const noteSchema: import("../../types/rtti/types.ts").Rest<{
89
96
  readonly dialect: "vnd.fjs.note";
90
97
  readonly text: import("../../types/rtti/types.ts")._Type0<"string">;
91
98
  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
- };
99
+ readonly priority: import("../../types/rtti/types.ts").Or<readonly [import("../../types/rtti/types.ts").Or<readonly ["P1", "P2", "P3", "P4", "P5"]>, undefined]>;
100
+ }, import("../../types/rtti/types.ts").Unknown>;
94
101
  /** Serializes a note canonically, sorting every object's property names.
95
102
  * @type {(note: Note) => string}
96
103
  */
@@ -5,10 +5,10 @@
5
5
  * The format is deliberately minimal: the dialect tag, the text, and
6
6
  * optionally the subjects the item depends on and a priority. Everything
7
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.
8
+ * **optional** field: {@link noteSchema} says `open`, so an older reader
9
+ * accepts a blob a newer writer has added fields to and additive extension
10
+ * keeps the tag (see the versioning rule in `fjs/media/revision/README.md`),
11
+ * and starting minimal is what keeps every extension additive.
12
12
  *
13
13
  * Like `vnd.fjs.lock`, a note is a **value**, not a step: no timestamps, no
14
14
  * author, no history of its own. Edits over time are ordinary
@@ -32,7 +32,7 @@
32
32
  * @import { Note, NoteError } from './types.ts'
33
33
  */
34
34
 
35
- import { array, option, or, string } from '../../types/rtti/module.f.mjs'
35
+ import { array, open, option, or, string } from '../../types/rtti/module.f.mjs'
36
36
  import { parse as rttiParse } from '../../types/rtti/parse/module.f.mjs'
37
37
  import { parse as parseJson, stringify } from '../json/module.f.mjs'
38
38
  import { okThen } from '../../types/result/module.f.mjs'
@@ -91,12 +91,19 @@ export const mediaType = /** @type {const} */ (`application/${dialect}+json`)
91
91
  */
92
92
  export const priorities = /** @type {const} */ (['P1', 'P2', 'P3', 'P4', 'P5'])
93
93
 
94
- export const noteSchema = /** @type {const} */ ({
94
+ /**
95
+ * `open`, and that is the extension strategy rather than a default: a bare
96
+ * struct is closed, so an older reader would reject a blob a newer writer had
97
+ * added a field to, and the versioning rule in
98
+ * `../revision/README.md` — additive extension keeps the tag — depends on the
99
+ * older reader accepting it. Do not drop the wrapper.
100
+ */
101
+ export const noteSchema = open(/** @type {const} */ ({
95
102
  dialect,
96
103
  text: string,
97
104
  dependencies: option(array(string)),
98
105
  priority: option(or(...priorities)),
99
- })
106
+ }))
100
107
 
101
108
  /** Serializes a note canonically, sorting every object's property names.
102
109
  * @type {(note: Note) => string}
@@ -89,7 +89,7 @@ export const proof = {
89
89
  assertEq(t, 'error')
90
90
  },
91
91
 
92
- // rtti structs are open, so unknown fields are ignored rather than
92
+ // `noteSchema` says `open`, so unknown fields are ignored rather than
93
93
  // rejected — the additive forward-compatibility path every future
94
94
  // extension (title, tags, dates, …) relies on.
95
95
  unknownFieldsIgnored: () => {
@@ -8,7 +8,7 @@ import { detect, dialectEntry } from './module.f.mjs'
8
8
  import { dialect, revisionDialect } from './revision/module.f.mjs'
9
9
  import { dialect as lockDialectName, lockDialect } from './lock/module.f.mjs'
10
10
  import { dialect as noteDialectName, noteDialect } from './note/module.f.mjs'
11
- import { number, string } from '../types/rtti/module.f.mjs'
11
+ import { number, open, string } from '../types/rtti/module.f.mjs'
12
12
 
13
13
  // All test strings here are ASCII, so char code === UTF-8 byte value.
14
14
  /** @type {(s: string) => Vec} */
@@ -24,10 +24,10 @@ const dialects = [revisionDialect, lockDialect, noteDialect]
24
24
  const detectRevision = detect(dialects)
25
25
 
26
26
  /** A dialect name outside `vnd.fjs.*` — registerable, and detected as itself. */
27
- const gadgetSchema = /** @type {const} */ ({
27
+ const gadgetSchema = open(/** @type {const} */ ({
28
28
  dialect: 'application.gadget',
29
29
  size: number,
30
- })
30
+ }))
31
31
 
32
32
  /** @type {DialectEntry} */
33
33
  const gadgetDialect = dialectEntry(gadgetSchema)
@@ -170,9 +170,9 @@ export const proof = {
170
170
  // The `dialect` member must be a direct string const. A thunk-form one
171
171
  // is a valid rtti schema but is not registerable, and `dialectEntry`
172
172
  // says so at registration rather than per blob.
173
- thunkDialectMember: () => dialectEntry({ dialect: string }),
173
+ thunkDialectMember: () => dialectEntry(open({ dialect: string })),
174
174
 
175
175
  // A schema with no `dialect` member at all is likewise refused.
176
- noDialectMember: () => dialectEntry({ text: string }),
176
+ noDialectMember: () => dialectEntry(open({ text: string })),
177
177
  },
178
178
  }