@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.
- package/dist/src/Assert.d.ts +21 -1
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +31 -27
- package/dist/src/Bytes.d.ts +647 -0
- package/dist/src/Bytes.d.ts.map +1 -0
- package/dist/src/{Binary.js → Bytes.js} +266 -16
- package/dist/src/Config.d.ts +142 -0
- package/dist/src/Config.d.ts.map +1 -0
- package/dist/src/Config.js +181 -0
- package/dist/src/Fs.d.ts +302 -0
- package/dist/src/Fs.d.ts.map +1 -0
- package/dist/src/Fs.js +111 -0
- package/dist/src/Number.d.ts +50 -7
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +47 -8
- package/dist/src/Object.d.ts +32 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +41 -0
- package/dist/src/Polyfills.d.ts +6 -0
- package/dist/src/Polyfills.d.ts.map +1 -1
- package/dist/src/Polyfills.js +6 -0
- package/dist/src/Sqlite.js +1 -1
- package/dist/src/Time.d.ts +74 -12
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +40 -2
- package/dist/src/Type.d.ts +1451 -79
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1303 -96
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/intl/_en.d.ts +24 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +20 -0
- package/dist/src/intl/ar.d.ts +24 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +20 -0
- package/dist/src/intl/bn.d.ts +24 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +20 -0
- package/dist/src/intl/ca.d.ts +24 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +20 -0
- package/dist/src/intl/cs.d.ts +24 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +20 -0
- package/dist/src/intl/da.d.ts +24 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +20 -0
- package/dist/src/intl/de.d.ts +24 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +20 -0
- package/dist/src/intl/el.d.ts +24 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +20 -0
- package/dist/src/intl/es.d.ts +24 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +20 -0
- package/dist/src/intl/fa.d.ts +24 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +20 -0
- package/dist/src/intl/fi.d.ts +24 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +20 -0
- package/dist/src/intl/fil.d.ts +24 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +20 -0
- package/dist/src/intl/fr.d.ts +24 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +20 -0
- package/dist/src/intl/he.d.ts +24 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +20 -0
- package/dist/src/intl/hi.d.ts +24 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +20 -0
- package/dist/src/intl/hr.d.ts +24 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +20 -0
- package/dist/src/intl/hu.d.ts +22 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +18 -0
- package/dist/src/intl/id.d.ts +24 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +20 -0
- package/dist/src/intl/it.d.ts +24 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +20 -0
- package/dist/src/intl/ja.d.ts +24 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +20 -0
- package/dist/src/intl/ko.d.ts +24 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +20 -0
- package/dist/src/intl/ml.d.ts +24 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +20 -0
- package/dist/src/intl/mr.d.ts +24 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +20 -0
- package/dist/src/intl/ms.d.ts +24 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +20 -0
- package/dist/src/intl/nb.d.ts +22 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +18 -0
- package/dist/src/intl/nl.d.ts +24 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +20 -0
- package/dist/src/intl/pa.d.ts +24 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +20 -0
- package/dist/src/intl/pl.d.ts +23 -0
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +20 -0
- package/dist/src/intl/pt-BR.d.ts +24 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +20 -0
- package/dist/src/intl/pt.d.ts +24 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +20 -0
- package/dist/src/intl/ro.d.ts +24 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +20 -0
- package/dist/src/intl/sk.d.ts +24 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +20 -0
- package/dist/src/intl/sl.d.ts +24 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +20 -0
- package/dist/src/intl/sv.d.ts +24 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +20 -0
- package/dist/src/intl/sw.d.ts +21 -0
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +18 -0
- package/dist/src/intl/ta.d.ts +24 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +20 -0
- package/dist/src/intl/te.d.ts +24 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +20 -0
- package/dist/src/intl/th.d.ts +24 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +20 -0
- package/dist/src/intl/tr.d.ts +24 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +20 -0
- package/dist/src/intl/uk.d.ts +80 -57
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +174 -149
- package/dist/src/intl/ur.d.ts +24 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +20 -0
- package/dist/src/intl/vi.d.ts +24 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +20 -0
- package/dist/src/intl/zh-CN.d.ts +24 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +20 -0
- package/dist/src/intl/zh-TW.d.ts +24 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +20 -0
- package/dist/src/local-first/Protocol.d.ts +1 -1
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +1 -1
- package/dist/src/local-first/Schema.d.ts +10 -0
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +1 -1
- package/dist/src/local-first/Timestamp.js +1 -1
- package/package.json +1 -1
- package/src/Assert.test.ts +51 -0
- package/src/Assert.ts +56 -4
- package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
- package/src/{Binary.ts → Bytes.ts} +652 -21
- package/src/Config.test.ts +672 -0
- package/src/Config.ts +410 -0
- package/src/Fs.test.ts +105 -0
- package/src/Fs.ts +414 -0
- package/src/Number.test.ts +82 -18
- package/src/Number.ts +76 -8
- package/src/Object.test.ts +115 -1
- package/src/Object.ts +44 -0
- package/src/Polyfills.ts +6 -0
- package/src/Sqlite.ts +1 -1
- package/src/Time.test.ts +82 -11
- package/src/Time.ts +141 -16
- package/src/Type.test.ts +3114 -157
- package/src/Type.ts +2553 -196
- package/src/index.ts +3 -1
- package/src/intl/_en.ts +70 -0
- package/src/intl/ar.ts +71 -0
- package/src/intl/bn.ts +70 -0
- package/src/intl/ca.ts +70 -0
- package/src/intl/cs.ts +70 -0
- package/src/intl/da.ts +70 -0
- package/src/intl/de.ts +70 -0
- package/src/intl/el.ts +70 -0
- package/src/intl/es.ts +70 -0
- package/src/intl/fa.ts +70 -0
- package/src/intl/fi.ts +70 -0
- package/src/intl/fil.ts +70 -0
- package/src/intl/fr.ts +70 -0
- package/src/intl/he.ts +70 -0
- package/src/intl/hi.ts +70 -0
- package/src/intl/hr.ts +70 -0
- package/src/intl/hu.ts +69 -0
- package/src/intl/id.ts +70 -0
- package/src/intl/intl.test.ts +819 -1
- package/src/intl/it.ts +70 -0
- package/src/intl/ja.ts +70 -0
- package/src/intl/ko.ts +68 -0
- package/src/intl/ml.ts +70 -0
- package/src/intl/mr.ts +70 -0
- package/src/intl/ms.ts +71 -0
- package/src/intl/nb.ts +69 -0
- package/src/intl/nl.ts +70 -0
- package/src/intl/pa.ts +70 -0
- package/src/intl/pl.ts +63 -0
- package/src/intl/pt-BR.ts +70 -0
- package/src/intl/pt.ts +71 -0
- package/src/intl/ro.ts +70 -0
- package/src/intl/sk.ts +71 -0
- package/src/intl/sl.ts +70 -0
- package/src/intl/sv.ts +70 -0
- package/src/intl/sw.ts +62 -0
- package/src/intl/ta.ts +70 -0
- package/src/intl/te.ts +70 -0
- package/src/intl/th.ts +68 -0
- package/src/intl/tr.ts +70 -0
- package/src/intl/uk.ts +228 -155
- package/src/intl/ur.ts +70 -0
- package/src/intl/vi.ts +70 -0
- package/src/intl/zh-CN.ts +68 -0
- package/src/intl/zh-TW.ts +68 -0
- package/src/local-first/Protocol.test.ts +1 -1
- package/src/local-first/Protocol.ts +1 -1
- package/src/local-first/Schema.ts +11 -0
- package/src/local-first/Storage.ts +1 -1
- package/src/local-first/Timestamp.ts +1 -1
- package/dist/src/Binary.d.ts +0 -254
- package/dist/src/Binary.d.ts.map +0 -1
package/src/Assert.test.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 "./
|
|
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
|
+
});
|