@evolu/common 8.3.2 → 8.4.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.
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
+ import type { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.ts";
6
7
  /**
7
8
  * Ensures a condition is true, throwing an error with the provided message if
8
9
  * not.
@@ -10,9 +11,9 @@
10
11
  * Prevents invalid states from propagating through the system by halting
11
12
  * execution when a condition fails, improving reliability and debuggability.
12
13
  *
13
- * Do not use this instead of {@link Type}. Assertions are intended for
14
- * conditions that are logically guaranteed but not statically known by
15
- * TypeScript, or for catching and signaling developer mistakes eagerly.
14
+ * Do not use this instead of {@link Type}. Assertions are intended when a
15
+ * condition is logically guaranteed to be true but TypeScript cannot prove it,
16
+ * or for catching and signaling developer mistakes eagerly.
16
17
  *
17
18
  * ### Example
18
19
  *
@@ -29,52 +30,94 @@ export declare const assert: (condition: unknown, message: string) => asserts co
29
30
  /**
30
31
  * Asserts that a value is non-nullable.
31
32
  *
32
- * Narrows a nullable value to {@link NonNullable} when null or undefined is
33
- * logically impossible but TypeScript cannot prove it.
33
+ * Following TypeScript's {@link NonNullable}, non-nullable here means neither
34
+ * null nor undefined. Use this when a value is logically guaranteed to be
35
+ * non-nullable but TypeScript cannot prove it.
34
36
  */
35
37
  export declare const assertNonNullable: <T>(value: T, message?: string) => asserts value is NonNullable<T>;
38
+ /**
39
+ * Asserts that a value is not null while preserving undefined.
40
+ *
41
+ * Use this when a value is logically guaranteed not to be null but TypeScript
42
+ * cannot prove it.
43
+ *
44
+ * ### Example
45
+ *
46
+ * ```ts
47
+ * import { assertNotNull } from "@evolu/common";
48
+ *
49
+ * const value = undefined as string | null | undefined;
50
+ * assertNotNull(value);
51
+ * expectTypeOf(value).toEqualTypeOf<string | undefined>();
52
+ * expect(value).toBeUndefined();
53
+ * expect(() => assertNotNull(null)).toThrow(
54
+ * "Expected value not to be null.",
55
+ * );
56
+ * ```
57
+ */
58
+ export declare const assertNotNull: <T>(value: T, message?: string) => asserts value is T & ({} | undefined);
59
+ /**
60
+ * Asserts that a value is not undefined while preserving null.
61
+ *
62
+ * Use this when a value is logically guaranteed not to be undefined but
63
+ * TypeScript cannot prove it.
64
+ *
65
+ * ### Example
66
+ *
67
+ * ```ts
68
+ * import { assertNotUndefined } from "@evolu/common";
69
+ *
70
+ * const value = null as string | null | undefined;
71
+ * assertNotUndefined(value);
72
+ * expectTypeOf(value).toEqualTypeOf<string | null>();
73
+ * expect(value).toBeNull();
74
+ * expect(() => assertNotUndefined(undefined)).toThrow(
75
+ * "Expected value not to be undefined.",
76
+ * );
77
+ * ```
78
+ */
79
+ export declare const assertNotUndefined: <T>(value: T, message?: string) => asserts value is T & ({} | null);
36
80
  /**
37
81
  * Asserts that an array is non-empty.
38
82
  *
39
- * Ensures the provided array has at least one element, helping TypeScript infer
40
- * the array as non-empty when this is logically guaranteed but not statically
41
- * known.
83
+ * Use this when an array is logically guaranteed to be non-empty but TypeScript
84
+ * cannot prove it.
42
85
  *
43
86
  * ### Example
44
87
  *
45
88
  * ```ts
46
- * import { assertNonEmptyArray } from "@evolu/common";
89
+ * import { assertNonEmptyArray, type NonEmptyArray } from "@evolu/common";
47
90
  *
48
91
  * const values = [1, 2, 3];
49
92
  * assertNonEmptyArray(values);
50
- * expectTypeOf(values).toEqualTypeOf<[number, ...Array<number>]>();
93
+ * expectTypeOf(values).toEqualTypeOf<NonEmptyArray<number>>();
51
94
  * expect(values[0]).toBe(1);
52
95
  * expect(() => assertNonEmptyArray([])).toThrow();
53
96
  * ```
54
97
  */
55
- export declare const assertNonEmptyArray: <T>(arr: Array<T>, message?: string) => asserts arr is [T, ...Array<T>];
98
+ export declare const assertNonEmptyArray: <T>(arr: Array<T>, message?: string) => asserts arr is NonEmptyArray<T>;
56
99
  /**
57
100
  * Asserts that a readonly array is non-empty.
58
101
  *
59
- * Ensures the provided readonly array has at least one element, helping
60
- * TypeScript infer non-emptiness when this is logically guaranteed but not
61
- * statically known.
102
+ * Use this when a readonly array is logically guaranteed to be non-empty but
103
+ * TypeScript cannot prove it.
62
104
  *
63
105
  * ### Example
64
106
  *
65
107
  * ```ts
66
- * import { assertNonEmptyReadonlyArray } from "@evolu/common";
108
+ * import {
109
+ * assertNonEmptyReadonlyArray,
110
+ * type NonEmptyReadonlyArray,
111
+ * } from "@evolu/common";
67
112
  *
68
113
  * const values: ReadonlyArray<number> = [1, 2, 3];
69
114
  * assertNonEmptyReadonlyArray(values);
70
- * expectTypeOf(values).toEqualTypeOf<
71
- * readonly [number, ...Array<number>]
72
- * >();
115
+ * expectTypeOf(values).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
73
116
  * expect(values[0]).toBe(1);
74
117
  * expect(() => assertNonEmptyReadonlyArray([])).toThrow();
75
118
  * ```
76
119
  */
77
- export declare const assertNonEmptyReadonlyArray: <T>(arr: ReadonlyArray<T>, message?: string) => asserts arr is readonly [T, ...Array<T>];
120
+ export declare const assertNonEmptyReadonlyArray: <T>(arr: ReadonlyArray<T>, message?: string) => asserts arr is NonEmptyReadonlyArray<T>;
78
121
  /**
79
122
  * Guards synchronous methods on objects that may be called after disposal.
80
123
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,MAAM,EAAE,CACnB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,SAIZ,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,EAChC,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,KAAK,IAAI,WAAW,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,EAClC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,2BAA2B,EAAE,CAAC,CAAC,EAC1C,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAK3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,iBAAiB,UAE1B,eAAe,GAAG,oBAAoB,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,KACxE,IAEF,CAAC"}
1
+ {"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGvE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,MAAM,EAAE,CACnB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,SAIZ,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,EAChC,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,KAAK,IAAI,WAAW,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,EAAE,CAAC,CAAC,EAC5B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,CAKxC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,kBAAkB,EAAE,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAKnC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,EAClC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,2BAA2B,EAAE,CAAC,CAAC,EAC1C,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,qBAAqB,CAAC,CAAC,CAK1C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,iBAAiB,UAE1B,eAAe,GAAG,oBAAoB,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,KACxE,IAEF,CAAC"}
@@ -10,9 +10,9 @@
10
10
  * Prevents invalid states from propagating through the system by halting
11
11
  * execution when a condition fails, improving reliability and debuggability.
12
12
  *
13
- * Do not use this instead of {@link Type}. Assertions are intended for
14
- * conditions that are logically guaranteed but not statically known by
15
- * TypeScript, or for catching and signaling developer mistakes eagerly.
13
+ * Do not use this instead of {@link Type}. Assertions are intended when a
14
+ * condition is logically guaranteed to be true but TypeScript cannot prove it,
15
+ * or for catching and signaling developer mistakes eagerly.
16
16
  *
17
17
  * ### Example
18
18
  *
@@ -33,27 +33,73 @@ export const assert = (condition, message) => {
33
33
  /**
34
34
  * Asserts that a value is non-nullable.
35
35
  *
36
- * Narrows a nullable value to {@link NonNullable} when null or undefined is
37
- * logically impossible but TypeScript cannot prove it.
36
+ * Following TypeScript's {@link NonNullable}, non-nullable here means neither
37
+ * null nor undefined. Use this when a value is logically guaranteed to be
38
+ * non-nullable but TypeScript cannot prove it.
38
39
  */
39
40
  export const assertNonNullable = (value, message = "Expected value to be non-nullable.") => {
40
41
  assert(value != null, message);
41
42
  };
43
+ /**
44
+ * Asserts that a value is not null while preserving undefined.
45
+ *
46
+ * Use this when a value is logically guaranteed not to be null but TypeScript
47
+ * cannot prove it.
48
+ *
49
+ * ### Example
50
+ *
51
+ * ```ts
52
+ * import { assertNotNull } from "@evolu/common";
53
+ *
54
+ * const value = undefined as string | null | undefined;
55
+ * assertNotNull(value);
56
+ * expectTypeOf(value).toEqualTypeOf<string | undefined>();
57
+ * expect(value).toBeUndefined();
58
+ * expect(() => assertNotNull(null)).toThrow(
59
+ * "Expected value not to be null.",
60
+ * );
61
+ * ```
62
+ */
63
+ export const assertNotNull = (value, message = "Expected value not to be null.") => {
64
+ assert(value !== null, message);
65
+ };
66
+ /**
67
+ * Asserts that a value is not undefined while preserving null.
68
+ *
69
+ * Use this when a value is logically guaranteed not to be undefined but
70
+ * TypeScript cannot prove it.
71
+ *
72
+ * ### Example
73
+ *
74
+ * ```ts
75
+ * import { assertNotUndefined } from "@evolu/common";
76
+ *
77
+ * const value = null as string | null | undefined;
78
+ * assertNotUndefined(value);
79
+ * expectTypeOf(value).toEqualTypeOf<string | null>();
80
+ * expect(value).toBeNull();
81
+ * expect(() => assertNotUndefined(undefined)).toThrow(
82
+ * "Expected value not to be undefined.",
83
+ * );
84
+ * ```
85
+ */
86
+ export const assertNotUndefined = (value, message = "Expected value not to be undefined.") => {
87
+ assert(value !== undefined, message);
88
+ };
42
89
  /**
43
90
  * Asserts that an array is non-empty.
44
91
  *
45
- * Ensures the provided array has at least one element, helping TypeScript infer
46
- * the array as non-empty when this is logically guaranteed but not statically
47
- * known.
92
+ * Use this when an array is logically guaranteed to be non-empty but TypeScript
93
+ * cannot prove it.
48
94
  *
49
95
  * ### Example
50
96
  *
51
97
  * ```ts
52
- * import { assertNonEmptyArray } from "@evolu/common";
98
+ * import { assertNonEmptyArray, type NonEmptyArray } from "@evolu/common";
53
99
  *
54
100
  * const values = [1, 2, 3];
55
101
  * assertNonEmptyArray(values);
56
- * expectTypeOf(values).toEqualTypeOf<[number, ...Array<number>]>();
102
+ * expectTypeOf(values).toEqualTypeOf<NonEmptyArray<number>>();
57
103
  * expect(values[0]).toBe(1);
58
104
  * expect(() => assertNonEmptyArray([])).toThrow();
59
105
  * ```
@@ -64,20 +110,20 @@ export const assertNonEmptyArray = (arr, message = "Expected a non-empty array."
64
110
  /**
65
111
  * Asserts that a readonly array is non-empty.
66
112
  *
67
- * Ensures the provided readonly array has at least one element, helping
68
- * TypeScript infer non-emptiness when this is logically guaranteed but not
69
- * statically known.
113
+ * Use this when a readonly array is logically guaranteed to be non-empty but
114
+ * TypeScript cannot prove it.
70
115
  *
71
116
  * ### Example
72
117
  *
73
118
  * ```ts
74
- * import { assertNonEmptyReadonlyArray } from "@evolu/common";
119
+ * import {
120
+ * assertNonEmptyReadonlyArray,
121
+ * type NonEmptyReadonlyArray,
122
+ * } from "@evolu/common";
75
123
  *
76
124
  * const values: ReadonlyArray<number> = [1, 2, 3];
77
125
  * assertNonEmptyReadonlyArray(values);
78
- * expectTypeOf(values).toEqualTypeOf<
79
- * readonly [number, ...Array<number>]
80
- * >();
126
+ * expectTypeOf(values).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
81
127
  * expect(values[0]).toBe(1);
82
128
  * expect(() => assertNonEmptyReadonlyArray([])).toThrow();
83
129
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../../src/local-first/Db.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,aAAa,EAGd,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EACV,qBAAqB,EAGrB,YAAY,EACb,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAsB,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,EAOL,KAAK,IAAI,EAEV,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,MAAM,EACN,UAAU,EACV,UAAU,EACX,MAAM,cAAc,CAAC;AAqBtB,OAAO,KAAK,EAEV,aAAa,EACb,cAAc,EAGf,MAAM,aAAa,CAAC;AA+BrB,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;CACjE;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GACnC,yBAAyB,GACzB,cAAc,GACd,qBAAqB,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,aAAa,SACjB,UAAU,CAAC,YAAY,CAAC,KAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAwM/D,CAAC"}
1
+ {"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../../src/local-first/Db.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,aAAa,EAGd,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EACV,qBAAqB,EAGrB,YAAY,EACb,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAsB,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,EAQL,KAAK,IAAI,EACV,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,MAAM,EACN,UAAU,EACV,UAAU,EACX,MAAM,cAAc,CAAC;AAqBtB,OAAO,KAAK,EAEV,aAAa,EACb,cAAc,EAGf,MAAM,aAAa,CAAC;AA+BrB,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;CACjE;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CACzC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GACnC,yBAAyB,GACzB,cAAc,GACd,qBAAqB,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,aAAa,SACjB,UAAU,CAAC,YAAY,CAAC,KAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAwM/D,CAAC"}
@@ -56,7 +56,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
56
56
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
57
57
  });
58
58
  import { appendToArray, firstInArray, } from "../Array.js";
59
- import { assert, assertNonEmptyReadonlyArray, assertNonNullable, } from "../Assert.js";
59
+ import { assertNonEmptyReadonlyArray, assertNonNullable, assertNotUndefined, } from "../Assert.js";
60
60
  import { EncryptionKey, } from "../Crypto.js";
61
61
  import { constFalse, constVoid } from "../Function.js";
62
62
  import { acquireLeaderLock } from "../LockManager.js";
@@ -414,7 +414,7 @@ const createClientStorage = (deps) => ({ onError, }) => {
414
414
  let encryptionKey = null;
415
415
  let didWriteMessages = false;
416
416
  const getEncryptionKey = () => {
417
- assert(encryptionKey != null, "ClientStorage encryption key must be set");
417
+ assertNonNullable(encryptionKey, "ClientStorage encryption key must be set");
418
418
  return encryptionKey;
419
419
  };
420
420
  return {
@@ -557,7 +557,7 @@ const applyLocalOnlyChange = (deps) => (change) => {
557
557
  const ownerId = change.ownerId;
558
558
  const columns = dbChangeToColumns(change, deps.time.now());
559
559
  for (const [column, value] of columns) {
560
- assertNonNullable(value);
560
+ assertNotUndefined(value);
561
561
  deps.sqlite.exec(sql.prepared `
562
562
  insert into ${sql.identifier(change.table)}
563
563
  ("ownerId", "id", ${sql.identifier(column)})
@@ -577,7 +577,7 @@ const applyMessages = (deps) => (ownerId, messages) => {
577
577
  const idBytes = idToIdBytes(change.id);
578
578
  const timestampBytes = timestampToTimestampBytes(timestamp);
579
579
  for (const [column, value] of columns) {
580
- assertNonNullable(value);
580
+ assertNotUndefined(value);
581
581
  if (validateColumnValue(deps)(change.table, column, value)) {
582
582
  applyColumnChange(deps)(ownerIdBytes, ownerId, change.table, idBytes, change.id, column, value, timestampBytes);
583
583
  }
@@ -884,7 +884,7 @@ const sync = (deps) => (ranges, output, ownerIdBytes) => {
884
884
  nonSkipRangeAdded = true;
885
885
  if (skip) {
886
886
  skip = false;
887
- assert(prevUpperBound != null, "prevUpperBound is null");
887
+ assertNonNullable(prevUpperBound, "prevUpperBound is null");
888
888
  // There is always a space for a skip range before adding.
889
889
  output.addRange({
890
890
  type: RangeType.Skip,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "8.3.2",
3
+ "version": "8.4.0",
4
4
  "description": "TypeScript library and local-first platform",
5
5
  "keywords": [
6
6
  "evolu",
@@ -72,7 +72,7 @@
72
72
  "@standard-schema/spec": "^1.1.0",
73
73
  "kysely": "^0.29.5",
74
74
  "msgpackr": "^2.0.5",
75
- "random": "^5.4.1"
75
+ "random": "5.5.1"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@types/node": "^24.10.9",
package/src/Assert.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * @module
5
5
  */
6
6
 
7
+ import type { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.ts";
7
8
  import type { Type } from "./Type.ts";
8
9
 
9
10
  /**
@@ -13,9 +14,9 @@ import type { Type } from "./Type.ts";
13
14
  * Prevents invalid states from propagating through the system by halting
14
15
  * execution when a condition fails, improving reliability and debuggability.
15
16
  *
16
- * Do not use this instead of {@link Type}. Assertions are intended for
17
- * conditions that are logically guaranteed but not statically known by
18
- * TypeScript, or for catching and signaling developer mistakes eagerly.
17
+ * Do not use this instead of {@link Type}. Assertions are intended when a
18
+ * condition is logically guaranteed to be true but TypeScript cannot prove it,
19
+ * or for catching and signaling developer mistakes eagerly.
19
20
  *
20
21
  * ### Example
21
22
  *
@@ -40,8 +41,9 @@ export const assert: (
40
41
  /**
41
42
  * Asserts that a value is non-nullable.
42
43
  *
43
- * Narrows a nullable value to {@link NonNullable} when null or undefined is
44
- * logically impossible but TypeScript cannot prove it.
44
+ * Following TypeScript's {@link NonNullable}, non-nullable here means neither
45
+ * null nor undefined. Use this when a value is logically guaranteed to be
46
+ * non-nullable but TypeScript cannot prove it.
45
47
  */
46
48
  export const assertNonNullable: <T>(
47
49
  value: T,
@@ -53,21 +55,80 @@ export const assertNonNullable: <T>(
53
55
  assert(value != null, message);
54
56
  };
55
57
 
58
+ /**
59
+ * Asserts that a value is not null while preserving undefined.
60
+ *
61
+ * Use this when a value is logically guaranteed not to be null but TypeScript
62
+ * cannot prove it.
63
+ *
64
+ * ### Example
65
+ *
66
+ * ```ts
67
+ * import { assertNotNull } from "@evolu/common";
68
+ *
69
+ * const value = undefined as string | null | undefined;
70
+ * assertNotNull(value);
71
+ * expectTypeOf(value).toEqualTypeOf<string | undefined>();
72
+ * expect(value).toBeUndefined();
73
+ * expect(() => assertNotNull(null)).toThrow(
74
+ * "Expected value not to be null.",
75
+ * );
76
+ * ```
77
+ */
78
+ export const assertNotNull: <T>(
79
+ value: T,
80
+ message?: string,
81
+ ) => asserts value is T & ({} | undefined) = (
82
+ value,
83
+ message = "Expected value not to be null.",
84
+ ) => {
85
+ assert(value !== null, message);
86
+ };
87
+
88
+ /**
89
+ * Asserts that a value is not undefined while preserving null.
90
+ *
91
+ * Use this when a value is logically guaranteed not to be undefined but
92
+ * TypeScript cannot prove it.
93
+ *
94
+ * ### Example
95
+ *
96
+ * ```ts
97
+ * import { assertNotUndefined } from "@evolu/common";
98
+ *
99
+ * const value = null as string | null | undefined;
100
+ * assertNotUndefined(value);
101
+ * expectTypeOf(value).toEqualTypeOf<string | null>();
102
+ * expect(value).toBeNull();
103
+ * expect(() => assertNotUndefined(undefined)).toThrow(
104
+ * "Expected value not to be undefined.",
105
+ * );
106
+ * ```
107
+ */
108
+ export const assertNotUndefined: <T>(
109
+ value: T,
110
+ message?: string,
111
+ ) => asserts value is T & ({} | null) = (
112
+ value,
113
+ message = "Expected value not to be undefined.",
114
+ ) => {
115
+ assert(value !== undefined, message);
116
+ };
117
+
56
118
  /**
57
119
  * Asserts that an array is non-empty.
58
120
  *
59
- * Ensures the provided array has at least one element, helping TypeScript infer
60
- * the array as non-empty when this is logically guaranteed but not statically
61
- * known.
121
+ * Use this when an array is logically guaranteed to be non-empty but TypeScript
122
+ * cannot prove it.
62
123
  *
63
124
  * ### Example
64
125
  *
65
126
  * ```ts
66
- * import { assertNonEmptyArray } from "@evolu/common";
127
+ * import { assertNonEmptyArray, type NonEmptyArray } from "@evolu/common";
67
128
  *
68
129
  * const values = [1, 2, 3];
69
130
  * assertNonEmptyArray(values);
70
- * expectTypeOf(values).toEqualTypeOf<[number, ...Array<number>]>();
131
+ * expectTypeOf(values).toEqualTypeOf<NonEmptyArray<number>>();
71
132
  * expect(values[0]).toBe(1);
72
133
  * expect(() => assertNonEmptyArray([])).toThrow();
73
134
  * ```
@@ -75,7 +136,7 @@ export const assertNonNullable: <T>(
75
136
  export const assertNonEmptyArray: <T>(
76
137
  arr: Array<T>,
77
138
  message?: string,
78
- ) => asserts arr is [T, ...Array<T>] = (
139
+ ) => asserts arr is NonEmptyArray<T> = (
79
140
  arr,
80
141
  message = "Expected a non-empty array.",
81
142
  ) => {
@@ -85,20 +146,20 @@ export const assertNonEmptyArray: <T>(
85
146
  /**
86
147
  * Asserts that a readonly array is non-empty.
87
148
  *
88
- * Ensures the provided readonly array has at least one element, helping
89
- * TypeScript infer non-emptiness when this is logically guaranteed but not
90
- * statically known.
149
+ * Use this when a readonly array is logically guaranteed to be non-empty but
150
+ * TypeScript cannot prove it.
91
151
  *
92
152
  * ### Example
93
153
  *
94
154
  * ```ts
95
- * import { assertNonEmptyReadonlyArray } from "@evolu/common";
155
+ * import {
156
+ * assertNonEmptyReadonlyArray,
157
+ * type NonEmptyReadonlyArray,
158
+ * } from "@evolu/common";
96
159
  *
97
160
  * const values: ReadonlyArray<number> = [1, 2, 3];
98
161
  * assertNonEmptyReadonlyArray(values);
99
- * expectTypeOf(values).toEqualTypeOf<
100
- * readonly [number, ...Array<number>]
101
- * >();
162
+ * expectTypeOf(values).toEqualTypeOf<NonEmptyReadonlyArray<number>>();
102
163
  * expect(values[0]).toBe(1);
103
164
  * expect(() => assertNonEmptyReadonlyArray([])).toThrow();
104
165
  * ```
@@ -106,7 +167,7 @@ export const assertNonEmptyArray: <T>(
106
167
  export const assertNonEmptyReadonlyArray: <T>(
107
168
  arr: ReadonlyArray<T>,
108
169
  message?: string,
109
- ) => asserts arr is readonly [T, ...Array<T>] = (
170
+ ) => asserts arr is NonEmptyReadonlyArray<T> = (
110
171
  arr,
111
172
  message = "Expected a non-empty readonly array.",
112
173
  ) => {
@@ -11,9 +11,9 @@ import {
11
11
  type NonEmptyReadonlyArray,
12
12
  } from "../Array.ts";
13
13
  import {
14
- assert,
15
14
  assertNonEmptyReadonlyArray,
16
15
  assertNonNullable,
16
+ assertNotUndefined,
17
17
  } from "../Assert.ts";
18
18
  import type { ConsoleLevel } from "../Console.ts";
19
19
  import {
@@ -45,13 +45,13 @@ import { callback, type Run, type Task } from "../Task.ts";
45
45
  import { Millis, millisToDateIso, type TimeDep } from "../Time.ts";
46
46
  import {
47
47
  assertType,
48
- type ExtractTyped,
49
48
  Id,
50
49
  IdBytes,
51
50
  idBytesToId,
52
51
  idToIdBytes,
53
- type Name,
54
52
  onePositiveInt,
53
+ type ExtractTyped,
54
+ type Name,
55
55
  } from "../Type.ts";
56
56
  import type {
57
57
  CreateBroadcastChannelDep,
@@ -634,7 +634,10 @@ const createClientStorage =
634
634
  let didWriteMessages = false;
635
635
 
636
636
  const getEncryptionKey = (): EncryptionKey => {
637
- assert(encryptionKey != null, "ClientStorage encryption key must be set");
637
+ assertNonNullable(
638
+ encryptionKey,
639
+ "ClientStorage encryption key must be set",
640
+ );
638
641
  return encryptionKey;
639
642
  };
640
643
 
@@ -841,7 +844,7 @@ const applyLocalOnlyChange =
841
844
  const columns = dbChangeToColumns(change, deps.time.now());
842
845
 
843
846
  for (const [column, value] of columns) {
844
- assertNonNullable(value);
847
+ assertNotUndefined(value);
845
848
  deps.sqlite.exec(sql.prepared`
846
849
  insert into ${sql.identifier(change.table)}
847
850
  ("ownerId", "id", ${sql.identifier(column)})
@@ -871,7 +874,7 @@ const applyMessages =
871
874
  const timestampBytes = timestampToTimestampBytes(timestamp);
872
875
 
873
876
  for (const [column, value] of columns) {
874
- assertNonNullable(value);
877
+ assertNotUndefined(value);
875
878
  if (validateColumnValue(deps)(change.table, column, value)) {
876
879
  applyColumnChange(deps)(
877
880
  ownerIdBytes,
@@ -1370,7 +1370,7 @@ const sync =
1370
1370
  nonSkipRangeAdded = true;
1371
1371
  if (skip) {
1372
1372
  skip = false;
1373
- assert(prevUpperBound != null, "prevUpperBound is null");
1373
+ assertNonNullable(prevUpperBound, "prevUpperBound is null");
1374
1374
  // There is always a space for a skip range before adding.
1375
1375
  output.addRange({
1376
1376
  type: RangeType.Skip,