@evolu/common 6.0.1-preview.32 → 6.0.1-preview.34

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 (49) hide show
  1. package/README.md +2 -2
  2. package/dist/src/Array.d.ts +193 -41
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +155 -48
  5. package/dist/src/Crypto.d.ts +6 -8
  6. package/dist/src/Crypto.d.ts.map +1 -1
  7. package/dist/src/Crypto.js +9 -9
  8. package/dist/src/Evolu/Db.d.ts.map +1 -1
  9. package/dist/src/Evolu/Db.js +4 -4
  10. package/dist/src/Evolu/Evolu.d.ts +9 -4
  11. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  12. package/dist/src/Evolu/Evolu.js +11 -11
  13. package/dist/src/Evolu/Protocol.d.ts +24 -1
  14. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  15. package/dist/src/Evolu/Protocol.js +59 -30
  16. package/dist/src/Evolu/Query.d.ts +1 -1
  17. package/dist/src/Evolu/Query.d.ts.map +1 -1
  18. package/dist/src/Evolu/Query.js +1 -1
  19. package/dist/src/Evolu/Schema.d.ts +24 -20
  20. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  21. package/dist/src/Evolu/Schema.js +36 -27
  22. package/dist/src/Evolu/Storage.d.ts +10 -2
  23. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  24. package/dist/src/Evolu/Storage.js +18 -4
  25. package/dist/src/Evolu/Sync.d.ts +2 -1
  26. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  27. package/dist/src/Evolu/Sync.js +45 -33
  28. package/dist/src/Evolu/Timestamp.d.ts +12 -17
  29. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  30. package/dist/src/Evolu/Timestamp.js +5 -19
  31. package/dist/src/Object.d.ts +10 -4
  32. package/dist/src/Object.d.ts.map +1 -1
  33. package/dist/src/Object.js +9 -3
  34. package/dist/src/Type.d.ts +57 -11
  35. package/dist/src/Type.d.ts.map +1 -1
  36. package/dist/src/Type.js +58 -28
  37. package/package.json +2 -2
  38. package/src/Array.ts +213 -55
  39. package/src/Crypto.ts +11 -9
  40. package/src/Evolu/Db.ts +7 -7
  41. package/src/Evolu/Evolu.ts +21 -20
  42. package/src/Evolu/Protocol.ts +70 -35
  43. package/src/Evolu/Query.ts +2 -2
  44. package/src/Evolu/Schema.ts +44 -32
  45. package/src/Evolu/Storage.ts +40 -2
  46. package/src/Evolu/Sync.ts +54 -32
  47. package/src/Evolu/Timestamp.ts +7 -28
  48. package/src/Object.ts +13 -5
  49. package/src/Type.ts +58 -32
package/src/Array.ts CHANGED
@@ -1,44 +1,121 @@
1
1
  /**
2
- * 🔒 Immutable, type-safe array operations
2
+ * 🔒 Immutable, type-safe array helpers
3
3
  *
4
- * Helpers that preserve immutability through the type system. Native array
5
- * methods return mutable arrays even when called on readonly arrays. These
6
- * helpers ensure transformations return readonly types.
4
+ * Array types, guards, operations, transformations, accessors, and mutations.
7
5
  *
8
- * {@link NonEmptyArray} and {@link NonEmptyReadonlyArray} types represent arrays
9
- * with at least one element, eliminating runtime length checks and making
10
- * function requirements explicit.
6
+ * Prepared for TC39 Hack pipes:
7
+ *
8
+ * ```ts
9
+ * // Problem: nested functions can be hard to follow
10
+ * const result = firstInArray(
11
+ * mapArray(dedupeArray(appendToArray(value, 2)), (x) => x * 2),
12
+ * );
13
+ *
14
+ * // Ideal solution: TC39 Hack pipes (when available)
15
+ * // const result = value
16
+ * // |> appendToArray(%, 2)
17
+ * // |> dedupeArray(%)
18
+ * // |> mapArray(%, (x) => x * 2)
19
+ * // |> firstInArray(%);
20
+ *
21
+ * // Current solution: name each step (or use p1, p2 if lazy)
22
+ * const p1 = appendToArray(value, 2);
23
+ * const p2 = dedupeArray(p1);
24
+ * const p3 = mapArray(p2, (x) => x * 2);
25
+ * const p4 = firstInArray(p3);
26
+ * ```
27
+ *
28
+ * Of course it's possible to use array instance methods, but they mutate and do
29
+ * not preserve {@link NonEmptyArray} and {@link NonEmptyReadonlyArray} types.
11
30
  *
12
31
  * ### Example
13
32
  *
14
33
  * ```ts
15
- * // Native methods return mutable arrays
34
+ * // Types - compile-time guarantee of at least one element
35
+ * const _valid: NonEmptyReadonlyArray<number> = [1, 2, 3];
36
+ * // ts-expect-error - empty array is not a valid NonEmptyReadonlyArray
37
+ * const _invalid: NonEmptyReadonlyArray<number> = [];
38
+ *
39
+ * // Guards
40
+ * const arr: ReadonlyArray<number> = [1, 2, 3];
41
+ * if (isNonEmptyReadonlyArray(arr)) {
42
+ * firstInArray(arr);
43
+ * }
44
+ *
45
+ * // Operations
46
+ * const appended = appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
47
+ * const prepended = prependToArray([2, 3], 1); // [1, 2, 3]
48
+ *
49
+ * // Transformations
16
50
  * const readonly: ReadonlyArray<number> = [1, 2, 3];
17
- * const mapped = readonly.map((x) => x * 2); // Array<number> (mutable!)
51
+ * const mapped = mapArray(readonly, (x) => x * 2); // [2, 4, 6]
52
+ * const filtered = filterArray(readonly, (x) => x > 1); // [2, 3]
53
+ * const deduped = dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
18
54
  *
19
- * // ✅ Helpers preserve immutability
20
- * const filtered = filterArray(readonly, (x) => x > 1); // ReadonlyArray<number>
55
+ * // Accessors
56
+ * const first = firstInArray(["a", "b", "c"]); // "a"
57
+ * const last = lastInArray(["a", "b", "c"]); // "c"
21
58
  *
22
- * // ✅ NonEmptyArray enforces non-emptiness
23
- * const value = firstInArray(["a", "b"]); // "a"
24
- * firstInArray([]); // Compiler error
59
+ * // Mutations
60
+ * const mutable: NonEmptyArray<number> = [1, 2, 3];
61
+ * shiftArray(mutable); // 1 (guaranteed to exist)
62
+ * mutable; // [2, 3]
25
63
  * ```
26
64
  *
27
65
  * @module
28
66
  */
29
67
 
30
- /** An array with at least one element. */
68
+ /**
69
+ * An array with at least one element.
70
+ *
71
+ * @category Types
72
+ */
31
73
  export type NonEmptyArray<T> = [T, ...Array<T>];
32
74
 
33
- /** Checks if an array is non-empty. */
75
+ /**
76
+ * A readonly array with at least one element.
77
+ *
78
+ * @category Types
79
+ */
80
+ export type NonEmptyReadonlyArray<T> = readonly [T, ...ReadonlyArray<T>];
81
+
82
+ /**
83
+ * Checks if an array is non-empty and narrows its type to {@link NonEmptyArray}.
84
+ *
85
+ * Use `if (!isNonEmptyArray(arr))` for empty checks.
86
+ *
87
+ * ### Example
88
+ *
89
+ * ```ts
90
+ * const arr: Array<number> = [1, 2, 3];
91
+ * if (isNonEmptyArray(arr)) {
92
+ * firstInArray(arr); // arr is NonEmptyArray<number>
93
+ * }
94
+ * ```
95
+ *
96
+ * @category Type Guards
97
+ */
34
98
  export const isNonEmptyArray = <T>(
35
99
  array: Array<T>,
36
100
  ): array is NonEmptyArray<T> => array.length > 0;
37
101
 
38
- /** A readonly array with at least one element. */
39
- export type NonEmptyReadonlyArray<T> = readonly [T, ...ReadonlyArray<T>];
40
-
41
- /** Checks if an array is non-empty. */
102
+ /**
103
+ * Checks if a readonly array is non-empty and narrows its type to
104
+ * {@link NonEmptyReadonlyArray}.
105
+ *
106
+ * Use `if (!isNonEmptyReadonlyArray(arr))` for empty checks.
107
+ *
108
+ * ### Example
109
+ *
110
+ * ```ts
111
+ * const arr: ReadonlyArray<number> = [1, 2, 3];
112
+ * if (isNonEmptyReadonlyArray(arr)) {
113
+ * firstInArray(arr); // arr is NonEmptyReadonlyArray<number>
114
+ * }
115
+ * ```
116
+ *
117
+ * @category Type Guards
118
+ */
42
119
  export const isNonEmptyReadonlyArray = <T>(
43
120
  array: ReadonlyArray<T>,
44
121
  ): array is NonEmptyReadonlyArray<T> => array.length > 0;
@@ -47,10 +124,18 @@ export const isNonEmptyReadonlyArray = <T>(
47
124
  * Appends an item to an array, returning a new non-empty readonly array.
48
125
  *
49
126
  * Accepts both mutable and readonly arrays. Does not mutate the original array.
127
+ *
128
+ * ### Example
129
+ *
130
+ * ```ts
131
+ * appendToArray([1, 2, 3], 4); // [1, 2, 3, 4]
132
+ * ```
133
+ *
134
+ * @category Operations
50
135
  */
51
136
  export const appendToArray = <T>(
52
- item: T,
53
137
  array: ReadonlyArray<T>,
138
+ item: T,
54
139
  ): NonEmptyReadonlyArray<T> =>
55
140
  [...array, item] as ReadonlyArray<T> as NonEmptyReadonlyArray<T>;
56
141
 
@@ -58,17 +143,33 @@ export const appendToArray = <T>(
58
143
  * Prepends an item to an array, returning a new non-empty readonly array.
59
144
  *
60
145
  * Accepts both mutable and readonly arrays. Does not mutate the original array.
146
+ *
147
+ * ### Example
148
+ *
149
+ * ```ts
150
+ * prependToArray([2, 3], 1); // [1, 2, 3]
151
+ * ```
152
+ *
153
+ * @category Operations
61
154
  */
62
155
  export const prependToArray = <T>(
63
- item: T,
64
156
  array: ReadonlyArray<T>,
157
+ item: T,
65
158
  ): NonEmptyReadonlyArray<T> => [item, ...array] as NonEmptyReadonlyArray<T>;
66
159
 
67
160
  /**
68
- * Maps an array using a mapper function, preserving non-emptiness when
69
- * applicable.
161
+ * Maps an array using a mapper function.
70
162
  *
71
163
  * Accepts both mutable and readonly arrays. Does not mutate the original array.
164
+ * Preserves non-empty type.
165
+ *
166
+ * ### Example
167
+ *
168
+ * ```ts
169
+ * mapArray([1, 2, 3], (x) => x * 2); // [2, 4, 6]
170
+ * ```
171
+ *
172
+ * @category Transformations
72
173
  */
73
174
  export function mapArray<T, U>(
74
175
  array: NonEmptyReadonlyArray<T> | NonEmptyArray<T>,
@@ -89,6 +190,14 @@ export function mapArray<T, U>(
89
190
  * Filters an array using a predicate function, returning a new readonly array.
90
191
  *
91
192
  * Accepts both mutable and readonly arrays. Does not mutate the original array.
193
+ *
194
+ * ### Example
195
+ *
196
+ * ```ts
197
+ * filterArray([1, 2, 3, 4, 5], (x) => x % 2 === 0); // [2, 4]
198
+ * ```
199
+ *
200
+ * @category Transformations
92
201
  */
93
202
  export const filterArray = <T>(
94
203
  array: ReadonlyArray<T>,
@@ -96,44 +205,45 @@ export const filterArray = <T>(
96
205
  ): ReadonlyArray<T> => array.filter(predicate) as ReadonlyArray<T>;
97
206
 
98
207
  /**
99
- * Shifts an item from a non-empty mutable array, guaranteed to return T.
208
+ * Returns a new readonly array with duplicate items removed. If `by` is
209
+ * provided, it will be used to derive the key for uniqueness; otherwise values
210
+ * are used directly. Dedupes by reference equality of values (or extracted keys
211
+ * when `by` is used).
100
212
  *
101
- * **Mutates** the original array. Use only with mutable arrays.
102
- */
103
- export const shiftArray = <T>(array: NonEmptyArray<T>): T => array.shift() as T;
104
-
105
- /**
106
- * Returns the first element of a non-empty readonly array.
213
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
214
+ * Preserves non-empty type.
107
215
  *
108
- * Does not mutate the original array.
109
- */
110
- export const firstInArray = <T>(array: NonEmptyReadonlyArray<T>): T => array[0];
111
-
112
- /**
113
- * Returns the last element of a non-empty readonly array.
216
+ * ### Example
114
217
  *
115
- * Does not mutate the original array.
116
- */
117
- export const lastInArray = <T>(array: NonEmptyReadonlyArray<T>): T =>
118
- array[array.length - 1];
119
-
120
- /**
121
- * Returns a new readonly array with duplicate items removed based on a key
122
- * extractor function. Preserves the first occurrence of each distinct key.
218
+ * ```ts
219
+ * // Dedupe primitives by value
220
+ * dedupeArray([1, 2, 1, 3, 2]); // [1, 2, 3]
123
221
  *
124
- * Accepts both mutable and readonly arrays. Does not mutate the original array.
125
- */
126
-
127
- /**
128
- * Deduplicates items in an array. If `by` is provided, it will be used to
129
- * derive the key for uniqueness; otherwise values are used directly.
222
+ * // Dedupe objects by property
223
+ * dedupeArray(
224
+ * [
225
+ * { id: 1, name: "Alice" },
226
+ * { id: 2, name: "Bob" },
227
+ * { id: 1, name: "Alice 2" },
228
+ * ],
229
+ * (item) => item.id,
230
+ * ); // [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
231
+ * ```
130
232
  *
131
- * Returns a new readonly array and does not mutate the input.
233
+ * @category Transformations
132
234
  */
133
- export const dedupeArray = <T>(
235
+ export function dedupeArray<T>(
236
+ array: NonEmptyReadonlyArray<T> | NonEmptyArray<T>,
237
+ by?: (item: T) => unknown,
238
+ ): NonEmptyReadonlyArray<T>;
239
+ export function dedupeArray<T>(
240
+ array: ReadonlyArray<T> | Array<T>,
241
+ by?: (item: T) => unknown,
242
+ ): ReadonlyArray<T>;
243
+ export function dedupeArray<T>(
134
244
  array: ReadonlyArray<T>,
135
245
  by?: (item: T) => unknown,
136
- ): ReadonlyArray<T> => {
246
+ ): ReadonlyArray<T> {
137
247
  if (by == null) {
138
248
  return Array.from(new Set(array)) as ReadonlyArray<T>;
139
249
  }
@@ -145,4 +255,52 @@ export const dedupeArray = <T>(
145
255
  seen.add(key);
146
256
  return true;
147
257
  }) as ReadonlyArray<T>;
148
- };
258
+ }
259
+
260
+ /**
261
+ * Returns the first element of a non-empty array.
262
+ *
263
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
264
+ *
265
+ * ### Example
266
+ *
267
+ * ```ts
268
+ * firstInArray(["a", "b", "c"]); // "a"
269
+ * ```
270
+ *
271
+ * @category Accessors
272
+ */
273
+ export const firstInArray = <T>(array: NonEmptyReadonlyArray<T>): T => array[0];
274
+
275
+ /**
276
+ * Returns the last element of a non-empty array.
277
+ *
278
+ * Accepts both mutable and readonly arrays. Does not mutate the original array.
279
+ *
280
+ * ### Example
281
+ *
282
+ * ```ts
283
+ * lastInArray(["a", "b", "c"]); // "c"
284
+ * ```
285
+ *
286
+ * @category Accessors
287
+ */
288
+ export const lastInArray = <T>(array: NonEmptyReadonlyArray<T>): T =>
289
+ array[array.length - 1];
290
+
291
+ /**
292
+ * Shifts an item from a non-empty mutable array, guaranteed to return T.
293
+ *
294
+ * **Mutates** the original array.
295
+ *
296
+ * ### Example
297
+ *
298
+ * ```ts
299
+ * const arr: NonEmptyArray<number> = [1, 2, 3];
300
+ * shiftArray(arr); // 1
301
+ * arr; // [2, 3]
302
+ * ```
303
+ *
304
+ * @category Mutations
305
+ */
306
+ export const shiftArray = <T>(array: NonEmptyArray<T>): T => array.shift() as T;
package/src/Crypto.ts CHANGED
@@ -172,10 +172,13 @@ export const createSymmetricCrypto = (
172
172
  * Returns the PADMÉ padded length for a given input length.
173
173
  *
174
174
  * PADMÉ limits information leakage about the length of the plain-text for a
175
- * wide range of encrypted data sizes. See the PURBs paper for details:
176
- * https://bford.info/pub/sec/purb.pdf
175
+ * wide range of encrypted data sizes.
176
+ *
177
+ * See the PURBs paper for details: https://bford.info/pub/sec/purb.pdf
177
178
  */
178
- export const padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
179
+ export const createPadmePaddedLength = (
180
+ length: NonNegativeInt,
181
+ ): NonNegativeInt => {
179
182
  if (length <= 0) return NonNegativeInt.orThrow(0);
180
183
  const e = 31 - Math.clz32(length >>> 0);
181
184
  const s = 32 - Math.clz32(e >>> 0);
@@ -184,12 +187,11 @@ export const padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
184
187
  return NonNegativeInt.orThrow((length + mask) & ~mask);
185
188
  };
186
189
 
187
- /**
188
- * Returns the PADMÉ padding length for a given input length. Uses
189
- * {@link padmePaddedLength}.
190
- */
191
- export const padmePaddingLength = (length: NonNegativeInt): NonNegativeInt => {
192
- return NonNegativeInt.orThrow(padmePaddedLength(length) - length);
190
+ /** Creates a PADMÉ padding array of zeros for the given input length. */
191
+ export const createPadmePadding = (length: NonNegativeInt): Uint8Array => {
192
+ const paddedLength = createPadmePaddedLength(length);
193
+ const paddingLength = NonNegativeInt.orThrow(paddedLength - length);
194
+ return new globalThis.Uint8Array(paddingLength);
193
195
  };
194
196
 
195
197
  /**
package/src/Evolu/Db.ts CHANGED
@@ -63,11 +63,11 @@ import {
63
63
  } from "./Sync.js";
64
64
  import {
65
65
  Timestamp,
66
+ TimestampBytes,
67
+ timestampBytesToTimestamp,
66
68
  TimestampConfig,
67
69
  TimestampError,
68
- TimestampString,
69
- timestampStringToTimestamp,
70
- timestampToTimestampString,
70
+ timestampToTimestampBytes,
71
71
  } from "./Timestamp.js";
72
72
 
73
73
  export interface DbConfig extends ConsoleConfig, TimestampConfig {
@@ -387,7 +387,7 @@ const createDbWorkerDeps =
387
387
  // }
388
388
 
389
389
  const configResult = sqlite.exec<{
390
- clock: TimestampString;
390
+ clock: TimestampBytes;
391
391
  appOwnerId: OwnerId;
392
392
  appOwnerEncryptionKey: OwnerEncryptionKey;
393
393
  appOwnerWriteKey: OwnerWriteKey;
@@ -415,7 +415,7 @@ const createDbWorkerDeps =
415
415
  };
416
416
 
417
417
  clock = createClock({ ...platformDeps, sqlite })(
418
- timestampStringToTimestamp(config.clock),
418
+ timestampBytesToTimestamp(config.clock),
419
419
  );
420
420
  } else {
421
421
  appOwner =
@@ -507,7 +507,7 @@ const initializeDb =
507
507
 
508
508
  sql`
509
509
  create table evolu_config (
510
- "clock" text not null,
510
+ "clock" blob not null,
511
511
  "appOwnerId" text not null,
512
512
  "appOwnerEncryptionKey" blob not null,
513
513
  "appOwnerWriteKey" blob not null,
@@ -527,7 +527,7 @@ const initializeDb =
527
527
  )
528
528
  values
529
529
  (
530
- ${timestampToTimestampString(initialClock)},
530
+ ${timestampToTimestampBytes(initialClock)},
531
531
  ${initialAppOwner.id},
532
532
  ${initialAppOwner.encryptionKey},
533
533
  ${initialAppOwner.writeKey},
@@ -4,7 +4,7 @@ import {
4
4
  isNonEmptyArray,
5
5
  isNonEmptyReadonlyArray,
6
6
  } from "../Array.js";
7
- import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
7
+ import { assertNonEmptyReadonlyArray } from "../Assert.js";
8
8
  import { createCallbacks } from "../Callbacks.js";
9
9
  import { ConsoleDep } from "../Console.js";
10
10
  import { RandomBytesDep, SymmetricCryptoDecryptError } from "../Crypto.js";
@@ -48,7 +48,6 @@ import {
48
48
  } from "./Query.js";
49
49
  import {
50
50
  CreateQuery,
51
- DefaultColumns,
52
51
  EvoluSchema,
53
52
  evoluSchemaToDbSchema,
54
53
  IndexesConfig,
@@ -59,6 +58,7 @@ import {
59
58
  MutationKind,
60
59
  MutationMapping,
61
60
  MutationOptions,
61
+ SystemColumns,
62
62
  updateable,
63
63
  upsertable,
64
64
  ValidateSchema,
@@ -239,7 +239,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
239
239
  *
240
240
  * Evolu does not use SQL for mutations to ensure data can be safely and
241
241
  * predictably merged without conflicts. Explicit mutations also allow Evolu
242
- * to automatically add and update {@link DefaultColumns}.
242
+ * to automatically add and update {@link SystemColumns}.
243
243
  *
244
244
  * ### Example
245
245
  *
@@ -284,7 +284,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
284
284
  *
285
285
  * Evolu does not use SQL for mutations to ensure data can be safely and
286
286
  * predictably merged without conflicts. Explicit mutations also allow Evolu
287
- * to automatically add and update {@link DefaultColumns}.
287
+ * to automatically add and update {@link SystemColumns}.
288
288
  *
289
289
  * ### Example
290
290
  *
@@ -337,7 +337,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
337
337
  *
338
338
  * Evolu does not use SQL for mutations to ensure data can be safely and
339
339
  * predictably merged without conflicts. Explicit mutations also allow Evolu
340
- * to automatically add and update {@link DefaultColumns}.
340
+ * to automatically add and update {@link SystemColumns}.
341
341
  *
342
342
  * ### Example
343
343
  *
@@ -408,7 +408,12 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
408
408
  */
409
409
  readonly reloadApp: () => void;
410
410
 
411
- /** Export SQLite database file as Uint8Array. */
411
+ /**
412
+ * Export SQLite database file as Uint8Array.
413
+ *
414
+ * In the future, it will be possible to import a database and export/import
415
+ * history for 1:1 migrations across owners.
416
+ */
412
417
  readonly exportDatabase: () => Promise<Uint8Array<ArrayBuffer>>;
413
418
 
414
419
  /**
@@ -716,21 +721,17 @@ const createEvoluInstance =
716
721
  const values = { ...result.value };
717
722
  delete values.id;
718
723
 
719
- if (kind === "insert" || kind === "upsert") {
720
- // Only set createdAt if not provided by user
721
- if (!("createdAt" in values)) {
722
- values.createdAt = new Date(deps.time.now()).toISOString();
723
- }
724
- }
725
-
726
- const dbChange = { table, id, values };
727
- assert(
728
- DbChange.is(dbChange),
729
- `Failed to create DbChange for table "${dbChange.table}"`,
730
- );
724
+ const dbChange = DbChange.orThrow({
725
+ table,
726
+ id,
727
+ values,
728
+ isInsert: kind === "insert" || kind === "upsert",
729
+ });
731
730
 
732
- const mutationChange = { ...dbChange, ownerId: options?.ownerId };
733
- mutateMicrotaskQueue.push([mutationChange, options?.onComplete]);
731
+ mutateMicrotaskQueue.push([
732
+ { ...dbChange, ownerId: options?.ownerId },
733
+ options?.onComplete,
734
+ ]);
734
735
  }
735
736
 
736
737
  if (mutateMicrotaskQueue.length === 1) {