@evolu/common 8.8.0 → 8.10.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 (242) hide show
  1. package/dist/src/Assert.d.ts +21 -1
  2. package/dist/src/Assert.d.ts.map +1 -1
  3. package/dist/src/Assert.js +31 -27
  4. package/dist/src/Bytes.d.ts +647 -0
  5. package/dist/src/Bytes.d.ts.map +1 -0
  6. package/dist/src/{Binary.js → Bytes.js} +266 -16
  7. package/dist/src/Config.d.ts +142 -0
  8. package/dist/src/Config.d.ts.map +1 -0
  9. package/dist/src/Config.js +181 -0
  10. package/dist/src/Fs.d.ts +302 -0
  11. package/dist/src/Fs.d.ts.map +1 -0
  12. package/dist/src/Fs.js +111 -0
  13. package/dist/src/Number.d.ts +50 -7
  14. package/dist/src/Number.d.ts.map +1 -1
  15. package/dist/src/Number.js +47 -8
  16. package/dist/src/Object.d.ts +32 -0
  17. package/dist/src/Object.d.ts.map +1 -1
  18. package/dist/src/Object.js +41 -0
  19. package/dist/src/Polyfills.d.ts +6 -0
  20. package/dist/src/Polyfills.d.ts.map +1 -1
  21. package/dist/src/Polyfills.js +6 -0
  22. package/dist/src/Sqlite.js +1 -1
  23. package/dist/src/Time.d.ts +74 -12
  24. package/dist/src/Time.d.ts.map +1 -1
  25. package/dist/src/Time.js +40 -2
  26. package/dist/src/Type.d.ts +1451 -79
  27. package/dist/src/Type.d.ts.map +1 -1
  28. package/dist/src/Type.js +1303 -96
  29. package/dist/src/index.d.ts +3 -1
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/index.js +3 -1
  32. package/dist/src/intl/_en.d.ts +24 -1
  33. package/dist/src/intl/_en.d.ts.map +1 -1
  34. package/dist/src/intl/_en.js +20 -0
  35. package/dist/src/intl/ar.d.ts +24 -1
  36. package/dist/src/intl/ar.d.ts.map +1 -1
  37. package/dist/src/intl/ar.js +20 -0
  38. package/dist/src/intl/bn.d.ts +24 -1
  39. package/dist/src/intl/bn.d.ts.map +1 -1
  40. package/dist/src/intl/bn.js +20 -0
  41. package/dist/src/intl/ca.d.ts +24 -1
  42. package/dist/src/intl/ca.d.ts.map +1 -1
  43. package/dist/src/intl/ca.js +20 -0
  44. package/dist/src/intl/cs.d.ts +24 -1
  45. package/dist/src/intl/cs.d.ts.map +1 -1
  46. package/dist/src/intl/cs.js +20 -0
  47. package/dist/src/intl/da.d.ts +24 -1
  48. package/dist/src/intl/da.d.ts.map +1 -1
  49. package/dist/src/intl/da.js +20 -0
  50. package/dist/src/intl/de.d.ts +24 -1
  51. package/dist/src/intl/de.d.ts.map +1 -1
  52. package/dist/src/intl/de.js +20 -0
  53. package/dist/src/intl/el.d.ts +24 -1
  54. package/dist/src/intl/el.d.ts.map +1 -1
  55. package/dist/src/intl/el.js +20 -0
  56. package/dist/src/intl/es.d.ts +24 -1
  57. package/dist/src/intl/es.d.ts.map +1 -1
  58. package/dist/src/intl/es.js +20 -0
  59. package/dist/src/intl/fa.d.ts +24 -1
  60. package/dist/src/intl/fa.d.ts.map +1 -1
  61. package/dist/src/intl/fa.js +20 -0
  62. package/dist/src/intl/fi.d.ts +24 -1
  63. package/dist/src/intl/fi.d.ts.map +1 -1
  64. package/dist/src/intl/fi.js +20 -0
  65. package/dist/src/intl/fil.d.ts +24 -1
  66. package/dist/src/intl/fil.d.ts.map +1 -1
  67. package/dist/src/intl/fil.js +20 -0
  68. package/dist/src/intl/fr.d.ts +24 -1
  69. package/dist/src/intl/fr.d.ts.map +1 -1
  70. package/dist/src/intl/fr.js +20 -0
  71. package/dist/src/intl/he.d.ts +24 -1
  72. package/dist/src/intl/he.d.ts.map +1 -1
  73. package/dist/src/intl/he.js +20 -0
  74. package/dist/src/intl/hi.d.ts +24 -1
  75. package/dist/src/intl/hi.d.ts.map +1 -1
  76. package/dist/src/intl/hi.js +20 -0
  77. package/dist/src/intl/hr.d.ts +24 -1
  78. package/dist/src/intl/hr.d.ts.map +1 -1
  79. package/dist/src/intl/hr.js +20 -0
  80. package/dist/src/intl/hu.d.ts +22 -1
  81. package/dist/src/intl/hu.d.ts.map +1 -1
  82. package/dist/src/intl/hu.js +18 -0
  83. package/dist/src/intl/id.d.ts +24 -1
  84. package/dist/src/intl/id.d.ts.map +1 -1
  85. package/dist/src/intl/id.js +20 -0
  86. package/dist/src/intl/it.d.ts +24 -1
  87. package/dist/src/intl/it.d.ts.map +1 -1
  88. package/dist/src/intl/it.js +20 -0
  89. package/dist/src/intl/ja.d.ts +24 -1
  90. package/dist/src/intl/ja.d.ts.map +1 -1
  91. package/dist/src/intl/ja.js +20 -0
  92. package/dist/src/intl/ko.d.ts +24 -1
  93. package/dist/src/intl/ko.d.ts.map +1 -1
  94. package/dist/src/intl/ko.js +20 -0
  95. package/dist/src/intl/ml.d.ts +24 -1
  96. package/dist/src/intl/ml.d.ts.map +1 -1
  97. package/dist/src/intl/ml.js +20 -0
  98. package/dist/src/intl/mr.d.ts +24 -1
  99. package/dist/src/intl/mr.d.ts.map +1 -1
  100. package/dist/src/intl/mr.js +20 -0
  101. package/dist/src/intl/ms.d.ts +24 -1
  102. package/dist/src/intl/ms.d.ts.map +1 -1
  103. package/dist/src/intl/ms.js +20 -0
  104. package/dist/src/intl/nb.d.ts +22 -1
  105. package/dist/src/intl/nb.d.ts.map +1 -1
  106. package/dist/src/intl/nb.js +18 -0
  107. package/dist/src/intl/nl.d.ts +24 -1
  108. package/dist/src/intl/nl.d.ts.map +1 -1
  109. package/dist/src/intl/nl.js +20 -0
  110. package/dist/src/intl/pa.d.ts +24 -1
  111. package/dist/src/intl/pa.d.ts.map +1 -1
  112. package/dist/src/intl/pa.js +20 -0
  113. package/dist/src/intl/pl.d.ts +23 -0
  114. package/dist/src/intl/pl.d.ts.map +1 -1
  115. package/dist/src/intl/pl.js +20 -0
  116. package/dist/src/intl/pt-BR.d.ts +24 -1
  117. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  118. package/dist/src/intl/pt-BR.js +20 -0
  119. package/dist/src/intl/pt.d.ts +24 -1
  120. package/dist/src/intl/pt.d.ts.map +1 -1
  121. package/dist/src/intl/pt.js +20 -0
  122. package/dist/src/intl/ro.d.ts +24 -1
  123. package/dist/src/intl/ro.d.ts.map +1 -1
  124. package/dist/src/intl/ro.js +20 -0
  125. package/dist/src/intl/sk.d.ts +24 -1
  126. package/dist/src/intl/sk.d.ts.map +1 -1
  127. package/dist/src/intl/sk.js +20 -0
  128. package/dist/src/intl/sl.d.ts +24 -1
  129. package/dist/src/intl/sl.d.ts.map +1 -1
  130. package/dist/src/intl/sl.js +20 -0
  131. package/dist/src/intl/sv.d.ts +24 -1
  132. package/dist/src/intl/sv.d.ts.map +1 -1
  133. package/dist/src/intl/sv.js +20 -0
  134. package/dist/src/intl/sw.d.ts +21 -0
  135. package/dist/src/intl/sw.d.ts.map +1 -1
  136. package/dist/src/intl/sw.js +18 -0
  137. package/dist/src/intl/ta.d.ts +24 -1
  138. package/dist/src/intl/ta.d.ts.map +1 -1
  139. package/dist/src/intl/ta.js +20 -0
  140. package/dist/src/intl/te.d.ts +24 -1
  141. package/dist/src/intl/te.d.ts.map +1 -1
  142. package/dist/src/intl/te.js +20 -0
  143. package/dist/src/intl/th.d.ts +24 -1
  144. package/dist/src/intl/th.d.ts.map +1 -1
  145. package/dist/src/intl/th.js +20 -0
  146. package/dist/src/intl/tr.d.ts +24 -1
  147. package/dist/src/intl/tr.d.ts.map +1 -1
  148. package/dist/src/intl/tr.js +20 -0
  149. package/dist/src/intl/uk.d.ts +80 -57
  150. package/dist/src/intl/uk.d.ts.map +1 -1
  151. package/dist/src/intl/uk.js +174 -149
  152. package/dist/src/intl/ur.d.ts +24 -1
  153. package/dist/src/intl/ur.d.ts.map +1 -1
  154. package/dist/src/intl/ur.js +20 -0
  155. package/dist/src/intl/vi.d.ts +24 -1
  156. package/dist/src/intl/vi.d.ts.map +1 -1
  157. package/dist/src/intl/vi.js +20 -0
  158. package/dist/src/intl/zh-CN.d.ts +24 -1
  159. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  160. package/dist/src/intl/zh-CN.js +20 -0
  161. package/dist/src/intl/zh-TW.d.ts +24 -1
  162. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  163. package/dist/src/intl/zh-TW.js +20 -0
  164. package/dist/src/local-first/Protocol.d.ts +1 -1
  165. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  166. package/dist/src/local-first/Protocol.js +1 -1
  167. package/dist/src/local-first/Schema.d.ts +10 -0
  168. package/dist/src/local-first/Schema.d.ts.map +1 -1
  169. package/dist/src/local-first/Storage.js +1 -1
  170. package/dist/src/local-first/Timestamp.js +1 -1
  171. package/package.json +1 -1
  172. package/src/Assert.test.ts +51 -0
  173. package/src/Assert.ts +56 -4
  174. package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
  175. package/src/{Binary.ts → Bytes.ts} +652 -21
  176. package/src/Config.test.ts +672 -0
  177. package/src/Config.ts +410 -0
  178. package/src/Fs.test.ts +105 -0
  179. package/src/Fs.ts +414 -0
  180. package/src/Number.test.ts +82 -18
  181. package/src/Number.ts +76 -8
  182. package/src/Object.test.ts +115 -1
  183. package/src/Object.ts +44 -0
  184. package/src/Polyfills.ts +6 -0
  185. package/src/Sqlite.ts +1 -1
  186. package/src/Time.test.ts +82 -11
  187. package/src/Time.ts +141 -16
  188. package/src/Type.test.ts +3114 -157
  189. package/src/Type.ts +2553 -196
  190. package/src/index.ts +3 -1
  191. package/src/intl/_en.ts +70 -0
  192. package/src/intl/ar.ts +71 -0
  193. package/src/intl/bn.ts +70 -0
  194. package/src/intl/ca.ts +70 -0
  195. package/src/intl/cs.ts +70 -0
  196. package/src/intl/da.ts +70 -0
  197. package/src/intl/de.ts +70 -0
  198. package/src/intl/el.ts +70 -0
  199. package/src/intl/es.ts +70 -0
  200. package/src/intl/fa.ts +70 -0
  201. package/src/intl/fi.ts +70 -0
  202. package/src/intl/fil.ts +70 -0
  203. package/src/intl/fr.ts +70 -0
  204. package/src/intl/he.ts +70 -0
  205. package/src/intl/hi.ts +70 -0
  206. package/src/intl/hr.ts +70 -0
  207. package/src/intl/hu.ts +69 -0
  208. package/src/intl/id.ts +70 -0
  209. package/src/intl/intl.test.ts +819 -1
  210. package/src/intl/it.ts +70 -0
  211. package/src/intl/ja.ts +70 -0
  212. package/src/intl/ko.ts +68 -0
  213. package/src/intl/ml.ts +70 -0
  214. package/src/intl/mr.ts +70 -0
  215. package/src/intl/ms.ts +71 -0
  216. package/src/intl/nb.ts +69 -0
  217. package/src/intl/nl.ts +70 -0
  218. package/src/intl/pa.ts +70 -0
  219. package/src/intl/pl.ts +63 -0
  220. package/src/intl/pt-BR.ts +70 -0
  221. package/src/intl/pt.ts +71 -0
  222. package/src/intl/ro.ts +70 -0
  223. package/src/intl/sk.ts +71 -0
  224. package/src/intl/sl.ts +70 -0
  225. package/src/intl/sv.ts +70 -0
  226. package/src/intl/sw.ts +62 -0
  227. package/src/intl/ta.ts +70 -0
  228. package/src/intl/te.ts +70 -0
  229. package/src/intl/th.ts +68 -0
  230. package/src/intl/tr.ts +70 -0
  231. package/src/intl/uk.ts +228 -155
  232. package/src/intl/ur.ts +70 -0
  233. package/src/intl/vi.ts +70 -0
  234. package/src/intl/zh-CN.ts +68 -0
  235. package/src/intl/zh-TW.ts +68 -0
  236. package/src/local-first/Protocol.test.ts +1 -1
  237. package/src/local-first/Protocol.ts +1 -1
  238. package/src/local-first/Schema.ts +11 -0
  239. package/src/local-first/Storage.ts +1 -1
  240. package/src/local-first/Timestamp.ts +1 -1
  241. package/dist/src/Binary.d.ts +0 -254
  242. package/dist/src/Binary.d.ts.map +0 -1
@@ -5,6 +5,7 @@ import type { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.ts";
5
5
  import {
6
6
  assert,
7
7
  assertConditionAfterMicrotasks,
8
+ assertContinuationAfterMicrotasks,
8
9
  assertEqual,
9
10
  assertEqualBytes,
10
11
  assertErr,
@@ -233,6 +234,13 @@ describe("assertConditionAfterMicrotasks", () => {
233
234
  nodeAssert.equal(callCount, 3);
234
235
  });
235
236
 
237
+ it("resolves without adding another microtask", async () => {
238
+ await assertContinuationAfterMicrotasks(
239
+ assertConditionAfterMicrotasks(() => true, 0),
240
+ 1,
241
+ );
242
+ });
243
+
236
244
  it("rejects when the condition becomes true too early", async () => {
237
245
  await nodeAssert.rejects(
238
246
  assertConditionAfterMicrotasks(() => true, 1),
@@ -248,6 +256,49 @@ describe("assertConditionAfterMicrotasks", () => {
248
256
  });
249
257
  });
250
258
 
259
+ describe("assertContinuationAfterMicrotasks", () => {
260
+ it("resolves when a fulfillment continuation runs after the exact count", async () => {
261
+ await assertContinuationAfterMicrotasks(Promise.resolve("done"), 1);
262
+ });
263
+
264
+ it("resolves when a rejection continuation runs after the exact count", async () => {
265
+ await assertContinuationAfterMicrotasks(
266
+ Promise.reject(new Error("failure")),
267
+ 1,
268
+ );
269
+ });
270
+
271
+ it("rejects when the continuation runs too early", async () => {
272
+ await nodeAssert.rejects(
273
+ assertContinuationAfterMicrotasks(Promise.resolve("done"), 2),
274
+ (error: unknown) => {
275
+ nodeAssert.ok(error instanceof nodeAssert.AssertionError);
276
+ nodeAssert.match(
277
+ error.message,
278
+ /Expected condition to be false after 1 microtasks\./u,
279
+ );
280
+ nodeAssert.ok(error.stack);
281
+ nodeAssert.equal(
282
+ error.stack.includes("at assertContinuationAfterMicrotasks "),
283
+ false,
284
+ );
285
+ return true;
286
+ },
287
+ );
288
+ });
289
+
290
+ it("rejects when the continuation has not run after the exact count", async () => {
291
+ const promise = new Promise<void>((resolve) => {
292
+ queueMicrotask(resolve);
293
+ });
294
+
295
+ await nodeAssert.rejects(
296
+ assertContinuationAfterMicrotasks(promise, 1),
297
+ /Expected condition to be true after exactly 1 microtasks\./u,
298
+ );
299
+ });
300
+ });
301
+
251
302
  describe("assertThrows", () => {
252
303
  it("compares the thrown value", () => {
253
304
  assertThrows(
package/src/Assert.ts CHANGED
@@ -252,10 +252,17 @@ export const assertFalse: (value: unknown) => asserts value is false = (
252
252
  *
253
253
  * @group Assertions
254
254
  */
255
- export const assertConditionAfterMicrotasks = async (
255
+ export function assertConditionAfterMicrotasks(
256
256
  condition: () => boolean,
257
257
  expectedMicrotaskCount: number,
258
- ): Promise<void> => {
258
+ ): Promise<void>;
259
+ export async function assertConditionAfterMicrotasks(
260
+ condition: () => boolean,
261
+ expectedMicrotaskCount: number,
262
+ stackStartFn: (
263
+ ...args: Array<never>
264
+ ) => unknown = assertConditionAfterMicrotasks,
265
+ ): Promise<void> {
259
266
  for (
260
267
  let microtaskCount = 0;
261
268
  microtaskCount < expectedMicrotaskCount;
@@ -269,7 +276,7 @@ export const assertConditionAfterMicrotasks = async (
269
276
  actual,
270
277
  expected: false,
271
278
  operator: "strictEqual",
272
- stackStartFn: assertConditionAfterMicrotasks,
279
+ stackStartFn,
273
280
  },
274
281
  );
275
282
  await Promise.resolve();
@@ -283,11 +290,56 @@ export const assertConditionAfterMicrotasks = async (
283
290
  actual,
284
291
  expected: true,
285
292
  operator: "strictEqual",
286
- stackStartFn: assertConditionAfterMicrotasks,
293
+ stackStartFn,
287
294
  },
288
295
  );
296
+ }
297
+
298
+ /**
299
+ * Asserts that a Promise continuation runs after exactly the specified number
300
+ * of microtasks.
301
+ *
302
+ * Both fulfillment and rejection count as running the continuation. Use this in
303
+ * tests that intentionally specify async scheduling behavior. Application code
304
+ * must not depend on exact microtask counts. Maintainers should review count
305
+ * changes because they indicate that an async pipeline changed.
306
+ *
307
+ * ### Example
308
+ *
309
+ * ```ts
310
+ * import { assertContinuationAfterMicrotasks } from "@evolu/common";
311
+ *
312
+ * await assertContinuationAfterMicrotasks(Promise.resolve("ready"), 1);
313
+ * ```
314
+ *
315
+ * @group Assertions
316
+ */
317
+ export const assertContinuationAfterMicrotasks = async (
318
+ promise: Promise<unknown>,
319
+ expectedMicrotaskCount: number,
320
+ ): Promise<void> => {
321
+ let continuationCalled = false;
322
+
323
+ const markContinuationCalled = (): void => {
324
+ continuationCalled = true;
325
+ };
326
+
327
+ void promise.then(markContinuationCalled, markContinuationCalled);
328
+
329
+ await assertConditionAfterMicrotasksWithStackStartFn(
330
+ () => continuationCalled,
331
+ expectedMicrotaskCount,
332
+ assertContinuationAfterMicrotasks,
333
+ );
289
334
  };
290
335
 
336
+ const assertConditionAfterMicrotasksWithStackStartFn =
337
+ assertConditionAfterMicrotasks as (
338
+ condition: () => boolean,
339
+ expectedMicrotaskCount: number,
340
+ stackStartFn: (...args: Array<never>) => unknown,
341
+ ) => Promise<void>;
342
+
291
343
  /**
292
344
  * Asserts that a function throws the expected value.
293
345
  *
@@ -2,7 +2,10 @@ import { describe, it, mock } from "node:test";
2
2
  import {
3
3
  assertEqual,
4
4
  assertEqualBytes,
5
+ assertErr,
6
+ assertFalse,
5
7
  assertInstanceOf,
8
+ assertOk,
6
9
  assertSame,
7
10
  assertThrowsInstanceOf,
8
11
  assertTrue,
@@ -10,6 +13,16 @@ import {
10
13
 
11
14
  import {
12
15
  BufferError,
16
+ ByteLength,
17
+ ByteLengthFromString,
18
+ ByteSizeLiteral,
19
+ ByteSizeLiteralBytes,
20
+ ByteSizeLiteralGiB,
21
+ ByteSizeLiteralKiB,
22
+ ByteSizeLiteralMiB,
23
+ ByteSizeLiteralTiB,
24
+ byteSizeToByteLength,
25
+ type ByteSize,
13
26
  createBuffer,
14
27
  createRunLengthEncoder,
15
28
  decodeFlags,
@@ -25,7 +38,7 @@ import {
25
38
  encodeNonNegativeInt,
26
39
  encodeNumber,
27
40
  encodeString,
28
- } from "./Binary.ts";
41
+ } from "./Bytes.ts";
29
42
  import {
30
43
  assertType,
31
44
  FiniteNumber,
@@ -34,7 +47,19 @@ import {
34
47
  Object,
35
48
  PositiveInt,
36
49
  zeroNonNegativeInt,
50
+ localizeTypes,
51
+ optional,
52
+ typeErrorToIssues,
53
+ withDefault,
54
+ type InferErrors,
37
55
  } from "./Type.ts";
56
+ import { env } from "./Config.ts";
57
+ import {
58
+ formatByteSizeLiteralError,
59
+ formatObjectError,
60
+ formatStringError,
61
+ formatIdentifierError,
62
+ } from "./intl/cs.ts";
38
63
 
39
64
  const encodeJson = (value: unknown): Uint8Array => {
40
65
  const result = JsonValue.fromUnknown(value);
@@ -1126,3 +1151,263 @@ describe("JSON binary codec", () => {
1126
1151
  assertTrue(error.message.includes("Invalid MessagePack data"));
1127
1152
  });
1128
1153
  });
1154
+
1155
+ describe("ByteLength", () => {
1156
+ it("accepts non-negative safe integers", () => {
1157
+ assertOk(ByteLength.fromUnknown(0), 0);
1158
+ assertOk(ByteLength.fromUnknown(1024), 1024);
1159
+
1160
+ const negativeZero = ByteLength.fromUnknown(-0);
1161
+ assertErr(negativeZero, { type: "ByteLength", value: -0 });
1162
+ assertEqual(
1163
+ ByteLength.formatError(negativeZero.error),
1164
+ "The value -0 is not a byte length. Use 0 instead.",
1165
+ );
1166
+ assertFalse(ByteLength.is(-0));
1167
+
1168
+ assertErr(ByteLength.fromUnknown(-1));
1169
+ assertErr(ByteLength.fromUnknown(1.5));
1170
+ assertErr(ByteLength.fromUnknown("1024"));
1171
+ });
1172
+ });
1173
+
1174
+ describe("ByteSizeLiteral", () => {
1175
+ it("reports its own error and retains the underlying union failure", () => {
1176
+ for (const value of ["1MB", "", "1024KiB", 1024, null, undefined]) {
1177
+ const result = ByteSizeLiteral.fromUnknown(value);
1178
+ assertErr(result);
1179
+ assertType<typeof result.error, InferErrors<typeof ByteSizeLiteral>>();
1180
+ assertEqual(result.error.type, "ByteSizeLiteral");
1181
+ assertSame(result.error.value, value);
1182
+ assertEqual(result.error.cause.type, "Union");
1183
+ assertEqual(result.error.cause.errors.length, 1);
1184
+ assertEqual(result.error.cause.errors[0].index, 0);
1185
+ assertEqual(result.error.cause.errors[0].error.type, "Union");
1186
+ }
1187
+
1188
+ const result = ByteSizeLiteral.fromUnknown("1MB", { errors: "all" });
1189
+ assertErr(result);
1190
+ assertEqual(result.error.cause.errors.length, 5);
1191
+ const message =
1192
+ 'The value "1MB" is not a byte-size literal. Use a value such as "512KiB" or "1MiB".';
1193
+ assertEqual(ByteSizeLiteral.formatError(result.error), message);
1194
+ assertEqual(typeErrorToIssues(ByteSizeLiteral, result.error), [
1195
+ { path: [], message },
1196
+ ]);
1197
+ });
1198
+
1199
+ it("preserves literal types and identity encoding", () => {
1200
+ // @ts-expect-error ByteSizeLiteral Input accepts binary-unit literals, not "1MB".
1201
+ const _invalidInput: typeof ByteSizeLiteral.Input = "1MB";
1202
+ assertType<
1203
+ InferErrors<typeof ByteSizeLiteral>["type"],
1204
+ "ByteSizeLiteral"
1205
+ >();
1206
+ assertEqual(ByteSizeLiteral.name, "ByteSizeLiteral");
1207
+ assertOk(ByteSizeLiteral.from("512KiB"), "512KiB");
1208
+ assertEqual(ByteSizeLiteral.to("512KiB"), "512KiB");
1209
+ });
1210
+
1211
+ it("preserves localized messages and ENV paths through composition", async () => {
1212
+ const { custom } = localizeTypes(
1213
+ { Size: ByteSizeLiteral },
1214
+ { custom: { ByteSizeLiteral: () => "Custom byte size." } },
1215
+ );
1216
+ const Env = env({
1217
+ APP: { quota: withDefault(optional(custom.Size), "1MiB") },
1218
+ });
1219
+ const { cs } = localizeTypes(
1220
+ { Env },
1221
+ {
1222
+ cs: {
1223
+ ByteSizeLiteral: formatByteSizeLiteralError,
1224
+ Object: formatObjectError,
1225
+ String: formatStringError,
1226
+ CamelCaseIdentifier: formatIdentifierError,
1227
+ },
1228
+ },
1229
+ );
1230
+ assertEqual(cs.Env.orThrow({}).quota, "1MiB");
1231
+ const supplied = cs.Env.orThrow({ APP_QUOTA: "512KiB" });
1232
+ assertEqual({ ...cs.Env.to(supplied) }, { APP_QUOTA: "512KiB" });
1233
+ const result = cs.Env.fromUnknown({ APP_QUOTA: "1MB" });
1234
+ assertErr(result);
1235
+ const message =
1236
+ 'Hodnota "1MB" není literál velikosti v bajtech. Použijte hodnotu jako "512KiB" nebo "1MiB".';
1237
+ assertEqual(cs.Env.formatError(result.error), message);
1238
+ assertEqual(typeErrorToIssues(cs.Env, result.error), [
1239
+ { path: ["APP_QUOTA"], message },
1240
+ ]);
1241
+ assertEqual(await cs.Env["~standard"].validate({ APP_QUOTA: "1MB" }), {
1242
+ issues: [{ path: ["APP_QUOTA"], message }],
1243
+ });
1244
+ assertEqual(Env.formatError(result.error), "Custom byte size.");
1245
+ });
1246
+
1247
+ it("accepts each unit within its bounds", () => {
1248
+ for (const value of [
1249
+ "0B",
1250
+ "9B",
1251
+ "10B",
1252
+ "999B",
1253
+ "1000B",
1254
+ "1019B",
1255
+ "1020B",
1256
+ "1023B",
1257
+ ])
1258
+ assertOk(ByteSizeLiteralBytes.fromUnknown(value), value);
1259
+
1260
+ const units = [
1261
+ ["KiB", ByteSizeLiteralKiB],
1262
+ ["MiB", ByteSizeLiteralMiB],
1263
+ ["GiB", ByteSizeLiteralGiB],
1264
+ ["TiB", ByteSizeLiteralTiB],
1265
+ ] as const;
1266
+
1267
+ for (const [unit, Type] of units) {
1268
+ for (const number of [
1269
+ "1",
1270
+ "99",
1271
+ "999",
1272
+ "1000",
1273
+ "1019",
1274
+ "1020",
1275
+ "1023",
1276
+ "1.5",
1277
+ "99.5",
1278
+ "1023.5",
1279
+ ]) {
1280
+ assertOk(Type.fromUnknown(`${number}${unit}`), `${number}${unit}`);
1281
+ assertTrue(ByteSizeLiteral.is(`${number}${unit}`));
1282
+ }
1283
+ for (const number of ["0", "0.5", "1.1", "1.0", "1024", "1024.5", "01"])
1284
+ assertFalse(ByteSizeLiteral.is(`${number}${unit}`));
1285
+ }
1286
+ });
1287
+
1288
+ it("rejects other units, spacing, letter case, and redundant forms", () => {
1289
+ for (const value of [
1290
+ "1MB",
1291
+ "1kib",
1292
+ "1 KiB",
1293
+ "1KiB ",
1294
+ "1024B",
1295
+ "0.5B",
1296
+ "-1B",
1297
+ "1",
1298
+ "",
1299
+ ])
1300
+ assertFalse(ByteSizeLiteral.is(value));
1301
+ });
1302
+
1303
+ it("is checked at compile time", () => {
1304
+ const literal: ByteSizeLiteral = "1023TiB";
1305
+ assertEqual(literal, "1023TiB");
1306
+ assertType<
1307
+ Extract<ByteSizeLiteral, "1023TiB" | "1000B" | "0B">,
1308
+ "1023TiB" | "1000B" | "0B"
1309
+ >();
1310
+ assertType<Extract<ByteSizeLiteral, "1MB" | "1.1KiB" | "1024KiB">, never>();
1311
+
1312
+ // @ts-expect-error Type '"1MB"' is not assignable to type 'ByteSizeLiteral'.
1313
+ const _decimalUnit: ByteSizeLiteral = "1MB";
1314
+ // @ts-expect-error Type '"1.1KiB"' is not assignable to type 'ByteSizeLiteral'.
1315
+ const _fraction: ByteSizeLiteral = "1.1KiB";
1316
+ // @ts-expect-error Type '"1024KiB"' is not assignable to type 'ByteSizeLiteral'.
1317
+ const _redundantUnit: ByteSizeLiteral = "1024KiB";
1318
+ // @ts-expect-error Type 'number' is not assignable to type 'ByteSize'.
1319
+ const _unbranded: ByteSize = 1024;
1320
+ });
1321
+ });
1322
+
1323
+ describe("byteSizeToByteLength", () => {
1324
+ it("converts exactly and passes a ByteLength through", () => {
1325
+ const cases: ReadonlyArray<readonly [ByteSizeLiteral, number]> = [
1326
+ ["0B", 0],
1327
+ ["1023B", 1023],
1328
+ ["1KiB", 1024],
1329
+ ["1.5KiB", 1536],
1330
+ ["1023.5KiB", 1048064],
1331
+ ["1000KiB", 1024000],
1332
+ ["1023KiB", 1047552],
1333
+ ["1MiB", 1048576],
1334
+ ["1023MiB", 1072693248],
1335
+ ["1GiB", 1073741824],
1336
+ ["1.5GiB", 1610612736],
1337
+ ["1023GiB", 1098437885952],
1338
+ ["1TiB", 1099511627776],
1339
+ ["1023TiB", 1124800395214848],
1340
+ ];
1341
+ for (const [literal, bytes] of cases)
1342
+ assertEqual(byteSizeToByteLength(literal), bytes);
1343
+
1344
+ const result = byteSizeToByteLength("1MiB");
1345
+ assertType<typeof result, ByteLength>();
1346
+
1347
+ const validated = ByteLength.orThrow(1000);
1348
+ assertSame(byteSizeToByteLength(validated), validated);
1349
+ });
1350
+
1351
+ it("asserts that a literal has a supported unit", () => {
1352
+ const error = assertThrowsInstanceOf(
1353
+ () => byteSizeToByteLength("1PiB" as ByteSize),
1354
+ Error,
1355
+ );
1356
+ assertEqual(error.message, "Unknown byte length unit: PiB");
1357
+ });
1358
+ });
1359
+
1360
+ describe("ByteLengthFromString", () => {
1361
+ it("parses a number of bytes or a literal", () => {
1362
+ assertOk(ByteLengthFromString.fromUnknown("0"), 0);
1363
+ assertOk(ByteLengthFromString.fromUnknown("1048576"), 1048576);
1364
+ assertOk(ByteLengthFromString.fromUnknown("007"), 7);
1365
+ assertOk(
1366
+ ByteLengthFromString.fromUnknown("9007199254740991"),
1367
+ Number.MAX_SAFE_INTEGER,
1368
+ );
1369
+ assertOk(ByteLengthFromString.fromUnknown("10MiB"), 10485760);
1370
+ assertOk(ByteLengthFromString.fromUnknown("1023MiB"), 1072693248);
1371
+ assertOk(ByteLengthFromString.fromUnknown("0B"), 0);
1372
+ assertEqual(
1373
+ ByteLengthFromString.to(ByteLengthFromString.orThrow("1KiB")),
1374
+ "1024",
1375
+ );
1376
+ assertSame(
1377
+ ByteLengthFromString.orThrow(
1378
+ ByteLengthFromString.to(ByteLength.orThrow(0)),
1379
+ ),
1380
+ 0,
1381
+ );
1382
+ });
1383
+
1384
+ it("rejects other text and unsafe numbers", () => {
1385
+ for (const value of [
1386
+ "",
1387
+ "10MB",
1388
+ "1.1KiB",
1389
+ "1024KiB",
1390
+ "-0",
1391
+ "-1",
1392
+ "1.5",
1393
+ "1e3",
1394
+ " 1",
1395
+ "1KiB ",
1396
+ "9007199254740992",
1397
+ "9007199254740993",
1398
+ "9".repeat(309),
1399
+ ]) {
1400
+ const invalid = ByteLengthFromString.fromUnknown(value);
1401
+ assertErr(invalid, { type: "ByteLengthFromString", value });
1402
+ assertEqual(
1403
+ ByteLengthFromString.formatError(invalid.error),
1404
+ `The value ${JSON.stringify(value)} is not a byte length. Use a number of bytes or a literal such as 10MiB.`,
1405
+ );
1406
+ }
1407
+ assertErr(ByteLengthFromString.fromUnknown(1024), {
1408
+ type: "TypeOf",
1409
+ expected: "String",
1410
+ value: 1024,
1411
+ });
1412
+ });
1413
+ });