@evolu/common 6.0.1-preview.16 → 6.0.1-preview.18

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 (57) hide show
  1. package/dist/src/Brand.d.ts +75 -0
  2. package/dist/src/Brand.d.ts.map +1 -0
  3. package/dist/src/Brand.js +1 -0
  4. package/dist/src/Callbacks.d.ts +1 -1
  5. package/dist/src/Crypto.d.ts +1 -1
  6. package/dist/src/Evolu/Db.d.ts +5 -5
  7. package/dist/src/Evolu/Db.d.ts.map +1 -1
  8. package/dist/src/Evolu/Db.js +8 -10
  9. package/dist/src/Evolu/Evolu.d.ts +21 -19
  10. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  11. package/dist/src/Evolu/Evolu.js +5 -28
  12. package/dist/src/Evolu/Owner.d.ts +1 -1
  13. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  14. package/dist/src/Evolu/Owner.js +1 -0
  15. package/dist/src/Evolu/Protocol.d.ts +1 -1
  16. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  17. package/dist/src/Evolu/Protocol.js +1 -0
  18. package/dist/src/Evolu/Public.d.ts +1 -1
  19. package/dist/src/Evolu/Public.d.ts.map +1 -1
  20. package/dist/src/Evolu/Query.d.ts +2 -1
  21. package/dist/src/Evolu/Query.d.ts.map +1 -1
  22. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  23. package/dist/src/Evolu/Timestamp.d.ts +1 -1
  24. package/dist/src/Number.d.ts +2 -1
  25. package/dist/src/Number.d.ts.map +1 -1
  26. package/dist/src/Result.d.ts +11 -37
  27. package/dist/src/Result.d.ts.map +1 -1
  28. package/dist/src/Result.js +3 -240
  29. package/dist/src/Sqlite.d.ts +2 -1
  30. package/dist/src/Sqlite.d.ts.map +1 -1
  31. package/dist/src/Type.d.ts +2 -1
  32. package/dist/src/Type.d.ts.map +1 -1
  33. package/dist/src/Type.js +1 -0
  34. package/dist/src/Types.d.ts +0 -74
  35. package/dist/src/Types.d.ts.map +1 -1
  36. package/dist/src/index.d.ts +1 -0
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/index.js +1 -0
  39. package/package.json +5 -5
  40. package/src/Brand.ts +75 -0
  41. package/src/Callbacks.ts +1 -1
  42. package/src/Crypto.ts +1 -1
  43. package/src/Evolu/Db.ts +9 -17
  44. package/src/Evolu/Evolu.ts +26 -54
  45. package/src/Evolu/Owner.ts +1 -0
  46. package/src/Evolu/Protocol.ts +3 -1
  47. package/src/Evolu/Public.ts +1 -6
  48. package/src/Evolu/Query.ts +2 -1
  49. package/src/Evolu/Schema.ts +1 -8
  50. package/src/Evolu/Storage.ts +1 -1
  51. package/src/Evolu/Timestamp.ts +1 -1
  52. package/src/Number.ts +2 -6
  53. package/src/Result.ts +12 -39
  54. package/src/Sqlite.ts +2 -1
  55. package/src/Type.ts +3 -1
  56. package/src/Types.ts +0 -76
  57. package/src/index.ts +1 -0
@@ -1,6 +1,7 @@
1
1
  export * from "./Array.js";
2
2
  export * from "./Assert.js";
3
3
  export * from "./BigInt.js";
4
+ export * from "./Brand.js";
4
5
  export * from "./Buffer.js";
5
6
  export * from "./Callbacks.js";
6
7
  export * from "./Console.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
package/dist/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./Array.js";
2
2
  export * from "./Assert.js";
3
3
  export * from "./BigInt.js";
4
+ export * from "./Brand.js";
4
5
  export * from "./Buffer.js";
5
6
  export * from "./Callbacks.js";
6
7
  export * from "./Console.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/common",
3
- "version": "6.0.1-preview.16",
3
+ "version": "6.0.1-preview.18",
4
4
  "description": "TypeScript library and local-first framework",
5
5
  "keywords": [
6
6
  "evolu",
@@ -45,10 +45,10 @@
45
45
  "@noble/ciphers": "^1.3.0",
46
46
  "@noble/hashes": "^1.8.0",
47
47
  "@scure/bip39": "^1.6.0",
48
- "kysely": "^0.28.2",
49
- "msgpackr": "^1.11.4",
48
+ "kysely": "^0.28.4",
49
+ "msgpackr": "^1.11.5",
50
50
  "nanoid": "^3.3.11",
51
- "random": "^5.4.0"
51
+ "random": "^5.4.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@bokuweb/zstd-wasm": "0.0.27",
@@ -56,7 +56,7 @@
56
56
  "@types/ws": "^8.18.1",
57
57
  "better-sqlite3": "^12.1.1",
58
58
  "shx": "^0.3.4",
59
- "typescript": "^5.8.3",
59
+ "typescript": "^5.9.2",
60
60
  "vitest": "^3.2.3",
61
61
  "ws": "^8.18.2",
62
62
  "@evolu/tsconfig": "0.0.2"
package/src/Brand.ts ADDED
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A utility interface for creating branded types.
3
+ *
4
+ * Branded types enhance type safety by differentiating otherwise identical base
5
+ * types, such as `number` or `string`, to enforce stricter type checks.
6
+ *
7
+ * Supports multiple brands, allowing types to act like flags.
8
+ *
9
+ * ### Example 1: Single Brand
10
+ *
11
+ * ```ts
12
+ * // A branded type definition
13
+ * type UserId = number & Brand<"UserId">;
14
+ *
15
+ * // A function that creates `UserId` values.
16
+ * // Casting with `as UserId` is unsafe, so `createUserId` must be unit-tested.
17
+ * const createUserId = (): UserId => {
18
+ * return 123 as UserId; // Unsafe casting
19
+ * };
20
+ *
21
+ * const userId = createUserId();
22
+ *
23
+ * // A function that accepts only `UserId`.
24
+ * const getUser = (id: UserId) => {
25
+ * // Implementation
26
+ * };
27
+ *
28
+ * getUser(userId); // ✅ Valid
29
+ * getUser(123); // ❌ TypeScript error
30
+ * getUser("123"); // ❌ TypeScript error
31
+ * ```
32
+ *
33
+ * ### Example 2: Multiple Brands
34
+ *
35
+ * ```ts
36
+ * // Define branded types
37
+ * type Min1 = string & Brand<"Min1">;
38
+ * type Max100 = string & Brand<"Max100">;
39
+ * type Min1Max100 = string & Brand<"Min1" | "Max100">;
40
+ *
41
+ * // Functions requiring specific brands
42
+ * const requiresMin1 = (value: Min1): void => {};
43
+ * const requiresMax100 = (value: Max100): void => {};
44
+ *
45
+ * // Values with single brands
46
+ * const min1Value: Min1 = "hello" as Min1;
47
+ * const max100Value: Max100 = "world" as Max100;
48
+ *
49
+ * // Value with multiple brands
50
+ * const min1Max100Value: Min1Max100 = "typescript" as Min1Max100;
51
+ *
52
+ * // Valid cases
53
+ * requiresMin1(min1Value); // ✅ Valid
54
+ * requiresMax100(max100Value); // ✅ Valid
55
+ * requiresMin1(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Min1
56
+ * requiresMax100(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Max100
57
+ * ```
58
+ */
59
+ export interface Brand<B extends string> {
60
+ readonly [__brand]: Readonly<Record<B, true>>;
61
+ }
62
+
63
+ declare const __brand: unique symbol;
64
+
65
+ /**
66
+ * Determines whether a type `T` is a branded type.
67
+ *
68
+ * Works with any base type intersected with a `Brand`.
69
+ *
70
+ * ### Examples
71
+ *
72
+ * - `IsBranded<string>` -> false
73
+ * - `IsBranded<string & Brand<"X">>` -> true
74
+ */
75
+ export type IsBranded<T> = T extends Brand<string> ? true : false;
package/src/Callbacks.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { NanoIdLibDep } from "./NanoId.js";
2
- import { Brand } from "./Types.js";
2
+ import { Brand } from "./Brand.js";
3
3
 
4
4
  /**
5
5
  * Manages one-time callback functions.
package/src/Crypto.ts CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  NonNegativeInt,
21
21
  Uint8Array,
22
22
  } from "./Type.js";
23
- import { Brand } from "./Types.js";
23
+ import { Brand } from "./Brand.js";
24
24
  import { assert } from "./Assert.js";
25
25
 
26
26
  /** `Uint8Array` created by {@link createRandomBytes}. */
package/src/Evolu/Db.ts CHANGED
@@ -1,8 +1,4 @@
1
- import {
2
- isNonEmptyArray,
3
- isNonEmptyReadonlyArray,
4
- NonEmptyReadonlyArray,
5
- } from "../Array.js";
1
+ import { isNonEmptyArray, NonEmptyReadonlyArray } from "../Array.js";
6
2
  import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
7
3
  import { CallbackId } from "../Callbacks.js";
8
4
  import { ConsoleDep } from "../Console.js";
@@ -121,7 +117,6 @@ export type DbWorkerInput =
121
117
  readonly type: "init";
122
118
  readonly config: Config;
123
119
  readonly dbSchema: DbSchema;
124
- readonly initialData: ReadonlyArray<DbChange>;
125
120
  }
126
121
  | {
127
122
  readonly type: "mutate";
@@ -157,6 +152,7 @@ export type DbWorkerOutput =
157
152
  | {
158
153
  readonly type: "onInit";
159
154
  readonly appOwner: AppOwner;
155
+ readonly isFirst: boolean;
160
156
  }
161
157
  | {
162
158
  readonly type: "onError";
@@ -289,11 +285,11 @@ export const createDbWorkerForPlatform = (
289
285
  let appOwner: AppOwner;
290
286
  let clock: Clock;
291
287
 
292
- const versionTableExists = currentDbSchema.value.tables.some(
288
+ const dbIsInitialized = currentDbSchema.value.tables.some(
293
289
  (table) => table.name === "evolu_version",
294
290
  );
295
291
 
296
- if (versionTableExists) {
292
+ if (dbIsInitialized) {
297
293
  const versionResult = sqlite.exec<{
298
294
  protocolVersion: number;
299
295
  }>(sql`select protocolVersion from evolu_version limit 1;`);
@@ -383,15 +379,11 @@ export const createDbWorkerForPlatform = (
383
379
  storage: storage.value,
384
380
  };
385
381
 
386
- if (
387
- !versionTableExists &&
388
- isNonEmptyReadonlyArray(initMessage.initialData)
389
- ) {
390
- const result = applyChanges(depsWithoutSync)(initMessage.initialData);
391
- if (!result.ok) return result;
392
- }
393
-
394
- postMessage({ type: "onInit", appOwner });
382
+ postMessage({
383
+ type: "onInit",
384
+ appOwner,
385
+ isFirst: !dbIsInitialized,
386
+ });
395
387
 
396
388
  const sync = platformDeps.createSync(platformDeps)({
397
389
  ...initMessage.config,
@@ -465,36 +465,36 @@ export type EvoluDeps = CreateDbWorkerDep &
465
465
  ConsoleDep &
466
466
  CreateAppStateDep;
467
467
 
468
- export interface EvoluConfigWithInitialData<S extends EvoluSchema = EvoluSchema>
469
- extends Config {
468
+ export interface EvoluConfigWithFunctions extends Config {
470
469
  /**
471
- * Use this option to create initial data (fixtures).
470
+ * Callback invoked when the database is initialized. Use `isFirst` to perform
471
+ * one-time setup like initial data seeding.
472
472
  *
473
473
  * ### Example
474
474
  *
475
475
  * ```ts
476
476
  * const evolu = createEvolu(evoluReactWebDeps)(Schema, {
477
- * initialData: (evolu) => {
478
- * const todoCategory = evolu.insert("todoCategory", {
479
- * name: "Not Urgent",
480
- * });
477
+ * onInit: ({ appOwner, isFirst }) => {
478
+ * if (isFirst) {
479
+ * const todoCategoryId = getOrThrow(
480
+ * evolu.insert("todoCategory", {
481
+ * name: "Not Urgent",
482
+ * }),
483
+ * );
481
484
  *
482
- * // This is a developer error, which should be fixed immediately.
483
- * assert(todoCategory.ok, "invalid initial data");
484
- *
485
- * evolu.insert("todo", {
486
- * title: "Try React Suspense",
487
- * categoryId: todoCategory.value.id,
488
- * });
485
+ * evolu.insert("todo", {
486
+ * title: "Try React Suspense",
487
+ * categoryId: todoCategoryId.id,
488
+ * });
489
+ * }
489
490
  * },
490
491
  * });
491
492
  * ```
492
493
  */
493
- initialData?: (evolu: EvoluForInitialData<S>) => void;
494
- }
495
-
496
- export interface EvoluForInitialData<S extends EvoluSchema = EvoluSchema> {
497
- insert: Mutation<S, "insert">;
494
+ readonly onInit?: (params: {
495
+ readonly appOwner: AppOwner;
496
+ readonly isFirst: boolean;
497
+ }) => void;
498
498
  }
499
499
 
500
500
  // For hot reloading and Evolu multitenancy.
@@ -553,7 +553,7 @@ export const createEvolu =
553
553
  (deps: EvoluDeps) =>
554
554
  <S extends EvoluSchema>(
555
555
  schema: ValidateSchema<S> extends never ? S : ValidateSchema<S>,
556
- partialConfig: Partial<EvoluConfigWithInitialData<S>> = {},
556
+ partialConfig: Partial<EvoluConfigWithFunctions> = {},
557
557
  ): Evolu<S> => {
558
558
  const config = { ...defaultConfig, ...partialConfig };
559
559
 
@@ -577,15 +577,13 @@ const createEvoluInstance =
577
577
  (deps: EvoluDeps) =>
578
578
  (
579
579
  schema: EvoluSchema,
580
- evoluConfig: EvoluConfigWithInitialData,
580
+ evoluConfig: EvoluConfigWithFunctions,
581
581
  ): InternalEvoluInstance => {
582
582
  deps.console.enabled = evoluConfig.enableLogging ?? false;
583
583
 
584
584
  deps.console.log("[evolu]", "createEvoluInstance");
585
585
 
586
- // evoluConfig.mnemonic
587
-
588
- const { initialData, indexes, ...config } = evoluConfig;
586
+ const { onInit, indexes, ...config } = evoluConfig;
589
587
 
590
588
  const errorStore = createStore<EvoluError | null>(null);
591
589
  const rowsStore = createStore<QueryRowsMap>(new Map());
@@ -608,6 +606,10 @@ const createEvoluInstance =
608
606
  switch (message.type) {
609
607
  case "onInit": {
610
608
  appOwnerStore.set(message.appOwner);
609
+ onInit?.({
610
+ appOwner: message.appOwner,
611
+ isFirst: message.isFirst,
612
+ });
611
613
  break;
612
614
  }
613
615
 
@@ -702,40 +704,10 @@ const createEvoluInstance =
702
704
  return type;
703
705
  };
704
706
 
705
- const initialDataDbChanges: Array<DbChange> = [];
706
-
707
- /**
708
- * Note that the initial data function is called even if it is unnecessary
709
- * (initial data are already in the DB) because we don't want to wait for
710
- * SQLite's response. Initial data should be small (because they are inlined
711
- * in the code), so it's ok.
712
- */
713
- if (initialData)
714
- initialData({
715
- insert: (table, props) => {
716
- const id = createId(deps);
717
- const values = getMutationType(table, "insert").fromUnknown(props);
718
-
719
- if (values.ok) {
720
- const valuesWithCreatedAt = {
721
- ...values.value,
722
- createdAt: new Date(deps.time.now()).toISOString(),
723
- };
724
- const dbChange = { table, id, values: valuesWithCreatedAt };
725
- assertValidDbChange(dbChange);
726
- initialDataDbChanges.push(dbChange);
727
- return ok({ id });
728
- }
729
-
730
- return values;
731
- },
732
- });
733
-
734
707
  dbWorker.postMessage({
735
708
  type: "init",
736
709
  config,
737
710
  dbSchema,
738
- initialData: initialDataDbChanges,
739
711
  });
740
712
 
741
713
  const loadQueryMicrotaskQueue: Array<Query> = [];
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsdoc/no-undefined-types */
1
2
  /**
2
3
  * Evolu Owner - Data Ownership and Collaboration
3
4
  *
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsdoc/no-undefined-types */
1
2
  /**
2
3
  * Evolu Protocol
3
4
  *
@@ -182,7 +183,8 @@ import {
182
183
  PositiveInt,
183
184
  record,
184
185
  } from "../Type.js";
185
- import { Brand, Predicate } from "../Types.js";
186
+ import { Predicate } from "../Types.js";
187
+ import { Brand } from "../Brand.js";
186
188
  import { Owner, OwnerId, WriteKey, writeKeyLength } from "./Owner.js";
187
189
  import {
188
190
  BinaryTimestamp,
@@ -5,12 +5,7 @@
5
5
  */
6
6
 
7
7
  export { createEvolu } from "./Evolu.js";
8
- export type {
9
- Evolu,
10
- EvoluConfigWithInitialData,
11
- EvoluDeps,
12
- EvoluError,
13
- } from "./Evolu.js";
8
+ export type { Evolu, EvoluDeps, EvoluError } from "./Evolu.js";
14
9
  export * from "./Owner.js";
15
10
  export { binaryIdToId, idToBinaryId } from "./Protocol.js";
16
11
  export type { BinaryId } from "./Protocol.js";
@@ -8,7 +8,8 @@ import {
8
8
  SqliteValue,
9
9
  } from "../Sqlite.js";
10
10
  import { Store, StoreSubscribe } from "../Store.js";
11
- import { Brand, Simplify } from "../Types.js";
11
+ import { Simplify } from "../Types.js";
12
+ import { Brand } from "../Brand.js";
12
13
 
13
14
  /**
14
15
  * A type-safe SQL query.
@@ -32,8 +32,8 @@ import { DbSchema } from "./Db.js";
32
32
  import { createIndexes, DbIndexesBuilder } from "./Kysely.js";
33
33
  import {
34
34
  BinaryId,
35
- maxProtocolMessageRangesSize,
36
35
  CrdtMessage,
36
+ maxProtocolMessageRangesSize,
37
37
  } from "./Protocol.js";
38
38
  import { Query, Row } from "./Query.js";
39
39
  import { BinaryTimestamp } from "./Timestamp.js";
@@ -251,13 +251,6 @@ export interface MutationOptions {
251
251
  * `onlyValidate: true`.
252
252
  */
253
253
  readonly onlyValidate?: boolean;
254
-
255
- // /**
256
- // * The owner to use for this mutation. Can be a {@link ShardOwner} for sharding
257
- // * app data or a {@link SharedOwner} for collaborative write access. If
258
- // * omitted, defaults to the app's {@link AppOwner}.
259
- // */
260
- // readonly owner?: ShardOwner | SharedOwner;
261
254
  }
262
255
 
263
256
  /**
@@ -29,7 +29,7 @@ import { RandomDep } from "../Random.js";
29
29
  import { ok, Result } from "../Result.js";
30
30
  import { sql, SqliteDep, SqliteError } from "../Sqlite.js";
31
31
  import { Int64String, NonNegativeInt, PositiveInt } from "../Type.js";
32
- import { Brand } from "../Types.js";
32
+ import { Brand } from "../Brand.js";
33
33
  import { OwnerId } from "./Owner.js";
34
34
  import {
35
35
  BinaryOwnerId,
@@ -13,7 +13,7 @@ import {
13
13
  regex,
14
14
  String,
15
15
  } from "../Type.js";
16
- import { Brand } from "../Types.js";
16
+ import { Brand } from "../Brand.js";
17
17
 
18
18
  export interface TimestampConfig {
19
19
  /**
package/src/Number.ts CHANGED
@@ -2,12 +2,8 @@ import { NonEmptyReadonlyArray } from "./Array.js";
2
2
  import { assertNonEmptyReadonlyArray } from "./Assert.js";
3
3
  import { err, ok, Result } from "./Result.js";
4
4
  import { NonNegativeInt, PositiveInt } from "./Type.js";
5
- import {
6
- IntentionalNever,
7
- IsBranded,
8
- Predicate,
9
- WidenLiteral,
10
- } from "./Types.js";
5
+ import { IntentionalNever, Predicate, WidenLiteral } from "./Types.js";
6
+ import { IsBranded } from "./Brand.js";
11
7
 
12
8
  export const increment = (n: number): number => n + 1;
13
9
 
package/src/Result.ts CHANGED
@@ -1,36 +1,17 @@
1
1
  /**
2
2
  * 🛡️ Type-safe errors
3
3
  *
4
- * ## Intro
5
- *
6
4
  * The problem with throwing an exception in JavaScript is that the caught error
7
5
  * is always of an unknown type. The unknown type is a problem because we can't
8
6
  * be sure all errors have been handled because the TypeScript compiler can't
9
- * help us.
10
- *
11
- * Some other languages like Rust 🦀 or Haskell 📚 use a type-safe approach to
12
- * error handling, where errors are explicitly represented as part of the return
13
- * type, such as Result or Either, allowing the developer to handle all errors
14
- * safely. ✅
15
- *
16
- * ✨ Evolu uses {@link Result}, and it looks like this:
17
- *
18
- * ```ts
19
- * type Result<T, E> = Ok<T> | Err<E>;
20
- *
21
- * interface Ok<T> {
22
- * readonly ok: true;
23
- * readonly value: T;
24
- * }
25
- *
26
- * interface Err<E> {
27
- * readonly ok: false;
28
- * readonly error: E;
29
- * }
7
+ * tell us. Some other languages like Rust 🦀 or Haskell 📚 use a type-safe
8
+ * approach to error handling, where errors are explicitly represented as part
9
+ * of the return type, such as Result or Either, allowing the developer to
10
+ * handle all errors safely.
30
11
  *
31
- * const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
32
- * const err = <E>(error: E): Err<E> => ({ ok: false, error });
33
- * ```
12
+ * This type models that approach in TypeScript. A `Result` can be either
13
+ * {@link Ok} (success) or {@link Err} (error). Use {@link ok} to create a
14
+ * successful result and {@link err} to create an error result.
34
15
  *
35
16
  * Now let's look at how `Result` can be used for safe JSON parsing:
36
17
  *
@@ -197,9 +178,8 @@
197
178
  * ### How do I handle an array of operations and short-circuit on the first error?
198
179
  *
199
180
  * If you have an array of operations (not results), you should make them
200
- * _lazy_—that is, represent each operation as a function (see `LazyValue` in
201
- * `Function.ts`). This way, you only execute each operation as needed, and can
202
- * stop on the first error:
181
+ * _lazy_—that is, represent each operation as a function. This way, you only
182
+ * execute each operation as needed, and can stop on the first error:
203
183
  *
204
184
  * ```ts
205
185
  * import type { LazyValue } from "./Function";
@@ -234,15 +214,6 @@
234
214
  * developers. While monads and functional helpers can be powerful, they often
235
215
  * obscure control flow and make debugging harder. Evolu's approach keeps error
236
216
  * handling explicit and straightforward.
237
- *
238
- * @module
239
- */
240
-
241
- /**
242
- * A `Result` can be either {@link Ok} (success) or {@link Err} (error).
243
- *
244
- * Use {@link ok} to create a successful result and {@link err} to create an error
245
- * result.
246
217
  */
247
218
  export type Result<T, E> = Ok<T> | Err<E>;
248
219
 
@@ -355,12 +326,14 @@ export const err = <E>(error: E): Err<E> => ({ ok: false, error });
355
326
  * const config = getOrThrow(loadConfig());
356
327
  * // Safe to use config here
357
328
  * ```
329
+ *
330
+ * Throws: `Error` with the original error attached as `cause`.
358
331
  */
359
332
  export const getOrThrow = <T, E>(result: Result<T, E>): T => {
360
333
  if (result.ok) {
361
334
  return result.value;
362
335
  } else {
363
- throw new Error(`Result error: ${JSON.stringify(result.error)}`);
336
+ throw new Error("getOrThrow failed", { cause: result.error });
364
337
  }
365
338
  };
366
339
 
package/src/Sqlite.ts CHANGED
@@ -11,7 +11,8 @@ import {
11
11
  Uint8Array,
12
12
  union,
13
13
  } from "./Type.js";
14
- import { Brand, Predicate, IntentionalNever } from "./Types.js";
14
+ import { Predicate, IntentionalNever } from "./Types.js";
15
+ import { Brand } from "./Brand.js";
15
16
 
16
17
  /**
17
18
  * SQLite driver interface. This is the minimal interface that platform-specific
package/src/Type.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable jsdoc/no-undefined-types */
1
2
  /**
2
3
  * 🧩 Validation, Parsing, and Transformation
3
4
  *
@@ -79,7 +80,8 @@ import { NanoIdLibDep } from "./NanoId.js";
79
80
  import { isPlainObject } from "./Object.js";
80
81
  import { Err, err, Ok, ok, Result, trySync } from "./Result.js";
81
82
  import { safelyStringifyUnknownValue } from "./String.js";
82
- import type { Brand, Literal, Simplify, WidenLiteral } from "./Types.js";
83
+ import type { Literal, Simplify, WidenLiteral } from "./Types.js";
84
+ import type { Brand } from "./Brand.js";
83
85
  import { IntentionalNever } from "./Types.js";
84
86
 
85
87
  export interface Type<
package/src/Types.ts CHANGED
@@ -78,82 +78,6 @@ export type NullablePartial<
78
78
  */
79
79
  export type IntentionalNever = never;
80
80
 
81
- /**
82
- * A utility interface for creating branded types.
83
- *
84
- * Branded types enhance type safety by differentiating otherwise identical base
85
- * types, such as `number` or `string`, to enforce stricter type checks.
86
- *
87
- * Supports multiple brands, allowing types to act like flags.
88
- *
89
- * ### Example 1: Single Brand
90
- *
91
- * ```ts
92
- * // A branded type definition
93
- * type UserId = number & Brand<"UserId">;
94
- *
95
- * // A function that creates `UserId` values.
96
- * // Casting with `as UserId` is unsafe, so `createUserId` must be unit-tested.
97
- * const createUserId = (): UserId => {
98
- * return 123 as UserId; // Unsafe casting
99
- * };
100
- *
101
- * const userId = createUserId();
102
- *
103
- * // A function that accepts only `UserId`.
104
- * const getUser = (id: UserId) => {
105
- * // Implementation
106
- * };
107
- *
108
- * getUser(userId); // ✅ Valid
109
- * getUser(123); // ❌ TypeScript error
110
- * getUser("123"); // ❌ TypeScript error
111
- * ```
112
- *
113
- * ### Example 2: Multiple Brands
114
- *
115
- * ```ts
116
- * // Define branded types
117
- * type Min1 = string & Brand<"Min1">;
118
- * type Max100 = string & Brand<"Max100">;
119
- * type Min1Max100 = string & Brand<"Min1" | "Max100">;
120
- *
121
- * // Functions requiring specific brands
122
- * const requiresMin1 = (value: Min1): void => {};
123
- * const requiresMax100 = (value: Max100): void => {};
124
- *
125
- * // Values with single brands
126
- * const min1Value: Min1 = "hello" as Min1;
127
- * const max100Value: Max100 = "world" as Max100;
128
- *
129
- * // Value with multiple brands
130
- * const min1Max100Value: Min1Max100 = "typescript" as Min1Max100;
131
- *
132
- * // Valid cases
133
- * requiresMin1(min1Value); // ✅ Valid
134
- * requiresMax100(max100Value); // ✅ Valid
135
- * requiresMin1(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Min1
136
- * requiresMax100(min1Max100Value); // ✅ Valid: Min1Max100 satisfies Max100
137
- * ```
138
- */
139
- export interface Brand<B extends string> {
140
- readonly [__brand]: Readonly<Record<B, true>>;
141
- }
142
-
143
- declare const __brand: unique symbol;
144
-
145
- /**
146
- * Determines whether a type `T` is a branded type.
147
- *
148
- * Works with any base type intersected with a `Brand`.
149
- *
150
- * ### Examples
151
- *
152
- * - `IsBranded<string>` -> false
153
- * - `IsBranded<string & Brand<"X">>` -> true
154
- */
155
- export type IsBranded<T> = T extends Brand<string> ? true : false;
156
-
157
81
  /**
158
82
  * String | number | bigint | boolean | undefined | null
159
83
  *
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./Array.js";
2
2
  export * from "./Assert.js";
3
3
  export * from "./BigInt.js";
4
+ export * from "./Brand.js";
4
5
  export * from "./Buffer.js";
5
6
  export * from "./Callbacks.js";
6
7
  export * from "./Console.js";