@evolu/common 6.0.1-preview.33 → 6.0.1-preview.35

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 (55) hide show
  1. package/README.md +2 -2
  2. package/dist/src/Array.d.ts +3 -3
  3. package/dist/src/Array.js +3 -3
  4. package/dist/src/Crypto.d.ts +6 -8
  5. package/dist/src/Crypto.d.ts.map +1 -1
  6. package/dist/src/Crypto.js +9 -9
  7. package/dist/src/Evolu/Db.d.ts.map +1 -1
  8. package/dist/src/Evolu/Db.js +4 -4
  9. package/dist/src/Evolu/Evolu.d.ts +9 -4
  10. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  11. package/dist/src/Evolu/Evolu.js +14 -14
  12. package/dist/src/Evolu/Protocol.d.ts +27 -2
  13. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  14. package/dist/src/Evolu/Protocol.js +71 -31
  15. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
  16. package/dist/src/Evolu/PublicKysely.js +0 -1
  17. package/dist/src/Evolu/Query.d.ts +1 -1
  18. package/dist/src/Evolu/Query.d.ts.map +1 -1
  19. package/dist/src/Evolu/Query.js +1 -1
  20. package/dist/src/Evolu/Schema.d.ts +28 -22
  21. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  22. package/dist/src/Evolu/Schema.js +37 -32
  23. package/dist/src/Evolu/Storage.d.ts +11 -2
  24. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  25. package/dist/src/Evolu/Storage.js +21 -3
  26. package/dist/src/Evolu/Sync.d.ts +4 -3
  27. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  28. package/dist/src/Evolu/Sync.js +68 -40
  29. package/dist/src/Evolu/Timestamp.d.ts +10 -14
  30. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  31. package/dist/src/Evolu/Timestamp.js +3 -16
  32. package/dist/src/Object.d.ts +10 -4
  33. package/dist/src/Object.d.ts.map +1 -1
  34. package/dist/src/Object.js +9 -3
  35. package/dist/src/Sqlite.d.ts +29 -3
  36. package/dist/src/Sqlite.d.ts.map +1 -1
  37. package/dist/src/Sqlite.js +29 -3
  38. package/dist/src/Type.d.ts +94 -47
  39. package/dist/src/Type.d.ts.map +1 -1
  40. package/dist/src/Type.js +110 -65
  41. package/package.json +2 -2
  42. package/src/Array.ts +3 -3
  43. package/src/Crypto.ts +11 -9
  44. package/src/Evolu/Db.ts +7 -7
  45. package/src/Evolu/Evolu.ts +31 -24
  46. package/src/Evolu/Protocol.ts +84 -37
  47. package/src/Evolu/PublicKysely.ts +1 -2
  48. package/src/Evolu/Query.ts +2 -2
  49. package/src/Evolu/Schema.ts +54 -50
  50. package/src/Evolu/Storage.ts +39 -2
  51. package/src/Evolu/Sync.ts +97 -43
  52. package/src/Evolu/Timestamp.ts +5 -25
  53. package/src/Object.ts +13 -5
  54. package/src/Sqlite.ts +33 -3
  55. package/src/Type.ts +124 -80
package/src/Sqlite.ts CHANGED
@@ -511,10 +511,14 @@ const drawSqliteQueryPlan = (rows: Array<SqliteQueryPlanRow>): string =>
511
511
  * SQLite represents boolean values using `0` (false) and `1` (true) instead of
512
512
  * a dedicated boolean type.
513
513
  *
514
- * Use {@link sqliteTrue} and {@link sqliteFalse} constants for better
515
- * readability.
516
- *
517
514
  * See: https://www.sqlite.org/quirks.html#no_separate_boolean_datatype
515
+ *
516
+ * ### Tips
517
+ *
518
+ * - Use {@link sqliteTrue} and {@link sqliteFalse} constants for better
519
+ * readability.
520
+ * - Use {@link booleanToSqliteBoolean} and {@link sqliteBooleanToBoolean} for
521
+ * converting between JavaScript booleans and SQLite boolean values.
518
522
  */
519
523
  export const SqliteBoolean = union(0, 1);
520
524
  export type SqliteBoolean = typeof SqliteBoolean.Type;
@@ -532,3 +536,29 @@ export const sqliteTrue = 1;
532
536
  * See {@link SqliteBoolean}.
533
537
  */
534
538
  export const sqliteFalse = 0;
539
+
540
+ /**
541
+ * Converts a JavaScript boolean to a {@link SqliteBoolean}.
542
+ *
543
+ * ### Example
544
+ *
545
+ * ```ts
546
+ * const isActive = true;
547
+ * const sqlValue = booleanToSqliteBoolean(isActive); // Returns 1
548
+ * ```
549
+ */
550
+ export const booleanToSqliteBoolean = (value: boolean): SqliteBoolean =>
551
+ value ? sqliteTrue : sqliteFalse;
552
+
553
+ /**
554
+ * Converts a {@link SqliteBoolean} to a JavaScript boolean.
555
+ *
556
+ * ### Example
557
+ *
558
+ * ```ts
559
+ * const sqlValue: SqliteBoolean = 1;
560
+ * const bool = sqliteBooleanToBoolean(sqlValue); // Returns true
561
+ * ```
562
+ */
563
+ export const sqliteBooleanToBoolean = (value: SqliteBoolean): boolean =>
564
+ value === sqliteTrue;
package/src/Type.ts CHANGED
@@ -3,10 +3,7 @@
3
3
  *
4
4
  * Evolu {@link Type} is like a type guard that returns typed errors (via
5
5
  * {@link Result}) instead of throwing. We either get a safely typed value or a
6
- * precise, composable error value telling us exactly why validation failed.
7
- *
8
- * Evolu Type supports [Standard Schema](https://standardschema.dev/) for
9
- * interoperability with 40+ validation-compatible tools and frameworks.
6
+ * composable typed error telling us exactly why validation failed.
10
7
  *
11
8
  * Why another validation library?
12
9
  *
@@ -15,16 +12,19 @@
15
12
  * messages.
16
13
  * - **Consistent constraints via {@link Brand}** – every constraint becomes part
17
14
  * of the type.
18
- * - **Selective validation** – parent validations are skipped when already proved
19
- * by typing.
20
- * - **Simple, top-down implementation** – readable source code from top to bottom
21
- * with no hidden magic; just plain functions and composition.
15
+ * - **Skippable validation** – parent validations can be skipped when already
16
+ * proved by types.
17
+ * - **Simple, top-down implementation** – readable source code from top to
18
+ * bottom.
19
+ * - **No user-land chaining DSL** – prepared for TC39 Hack pipes.
20
+ *
21
+ * A distinctive feature of Evolu Type compared to other validation libraries is
22
+ * that it returns typed errors rather than string messages. This allows
23
+ * TypeScript to enforce that all validation errors are handled by type
24
+ * checking, significantly improving the developer experience.
22
25
  *
23
- * TODO: Refactor Type factories and composition for Hack pipes compatibility.
24
- * Currently, brand factories like `minLength(min)(Type)` use constraint-first
25
- * currying. For optimal Hack pipe support, these should be refactored to
26
- * `minLength(Type, min)` to enable: `Type |> minLength(%, 1) |> maxLength(%,
27
- * 100)`
26
+ * Evolu Type supports [Standard Schema](https://standardschema.dev/) for
27
+ * interoperability with 40+ validation-compatible tools and frameworks.
28
28
  *
29
29
  * ### Base Types Quick Start
30
30
  *
@@ -175,6 +175,29 @@
175
175
  * reverse transforms would not buy much. We may revisit this if we can design a
176
176
  * minimal, 100% safe API that preserves simplicity.
177
177
  *
178
+ * ### Prepared for TC39 Hack Pipes
179
+ *
180
+ * Take a look how `SimplePassword` is defined:
181
+ *
182
+ * ```ts
183
+ * export const SimplePassword = brand(
184
+ * "SimplePassword",
185
+ * minLength(8)(maxLength(64)(TrimmedString)),
186
+ * );
187
+ * ```
188
+ *
189
+ * Nested functions are often OK (if not, make a helper) and read well, but with
190
+ * TC39 Hack pipes it would be clearer:
191
+ *
192
+ * ```ts
193
+ * // TrimmedString
194
+ * // |> minLength(8)(%)
195
+ * // |> maxLength(64)(%)
196
+ * // |> brand("SimplePassword", %)
197
+ * ```
198
+ *
199
+ * Note `minLength` and `maxLength` are curried because they are factories.
200
+ *
178
201
  * @module
179
202
  */
180
203
 
@@ -189,6 +212,7 @@ import { isPlainObject } from "./Object.js";
189
212
  import { hasNodeBuffer } from "./Platform.js";
190
213
  import { err, getOrNull, getOrThrow, ok, Result, trySync } from "./Result.js";
191
214
  import { safelyStringifyUnknownValue } from "./String.js";
215
+ import type { TimeDep } from "./Time.js";
192
216
  import type { Literal, Simplify, WidenLiteral } from "./Types.js";
193
217
  import { IntentionalNever } from "./Types.js";
194
218
 
@@ -1501,7 +1525,7 @@ export const base64UrlToUint8Array: (str: Base64Url) => Uint8Array =
1501
1525
  * Uses the same safe alphabet as {@link UrlSafeString} (letters, digits, `-`,
1502
1526
  * `_`). See `UrlSafeString` for details.
1503
1527
  *
1504
- * The string must be between 1 and 42 characters.
1528
+ * The string must be between 1 and 64 characters.
1505
1529
  *
1506
1530
  * ### Example
1507
1531
  *
@@ -1517,7 +1541,7 @@ export const base64UrlToUint8Array: (str: Base64Url) => Uint8Array =
1517
1541
  * @category String
1518
1542
  */
1519
1543
  export const SimpleName = brand("SimpleName", UrlSafeString, (value) =>
1520
- value.length >= 1 && value.length <= 42
1544
+ value.length >= 1 && value.length <= 64
1521
1545
  ? ok(value)
1522
1546
  : err<SimpleNameError>({ type: "SimpleName", value }),
1523
1547
  );
@@ -1527,6 +1551,25 @@ export interface SimpleNameError extends TypeError<"SimpleName"> {}
1527
1551
  /**
1528
1552
  * Trimmed string between 8 and 64 characters, branded as `SimplePassword`.
1529
1553
  *
1554
+ * Take a look how `SimplePassword` is defined:
1555
+ *
1556
+ * ```ts
1557
+ * export const SimplePassword = brand(
1558
+ * "SimplePassword",
1559
+ * minLength(8)(maxLength(64)(TrimmedString)),
1560
+ * );
1561
+ * ```
1562
+ *
1563
+ * Nested functions are often OK (if not, make a helper), but with TC39 Hack
1564
+ * pipes it would be clearer:
1565
+ *
1566
+ * ```ts
1567
+ * // TrimmedString
1568
+ * // |> minLength(8)(%)
1569
+ * // |> maxLength(64)(%)
1570
+ * // |> brand("SimplePassword", %)
1571
+ * ```
1572
+ *
1530
1573
  * @category String
1531
1574
  */
1532
1575
  export const SimplePassword = brand(
@@ -1547,41 +1590,27 @@ export const formatSimplePasswordError = (
1547
1590
  );
1548
1591
 
1549
1592
  /**
1550
- * Globally unique identifier.
1551
- *
1552
- * **Evolu Id** is 16 random bytes from a cryptographically secure random
1553
- * generator, encoded as 22-character Base64Url string. This provides strong
1554
- * collision resistance for distributed ID generation.
1555
- *
1556
- * ### Design Rationale
1557
- *
1558
- * Why Evolu Id over alternatives:
1559
- *
1560
- * - **NanoID**: No standard binary serialization format, and uses only ~126 bits
1561
- * of entropy (21 characters from 64-symbol alphabet) compared to Evolu Id's
1562
- * 128 bits.
1563
- * - **UUID (v4)**: String format is 36 characters (with hyphens) compared to
1564
- * Evolu Id's 22 characters. While UUIDs can be stored as 16 bytes, their
1565
- * standard string representation is verbose.
1566
- * - **UUID v7**: Includes timestamp in the ID, which leaks information about when
1567
- * data was created. This is a privacy concern for local-first applications
1568
- * where creation time must remain private.
1569
- *
1570
- * Evolu Id provides 128 bits of entropy, compact string representation (22
1571
- * characters), standard and native string serialization (Base64Url), and no
1572
- * privacy leaks.
1573
- *
1574
- * ### Future Consideration
1575
- *
1576
- * For database-heavy workloads where insert performance is critical, a hybrid
1577
- * approach could be considered: `timestamp ^ H(cluster_id, timestamp >> N)`
1578
- * where H is a keyed hash function and N is a configurable parameter. This
1579
- * would maintain spatial locality for database caches (improving insert
1580
- * performance by an order of magnitude) while adding entropy to prevent
1581
- * timestamp leakage and correlation across systems. The parameter N would allow
1582
- * trading off cache locality (larger N = better locality) versus entropy
1583
- * distribution. See https://brooker.co.za/blog/2025/10/22/uuidv7.html for
1584
- * details on this approach.
1593
+ * Evolu Id: 16 bytes encoded as a 22‑character Base64Url string.
1594
+ *
1595
+ * There are three ways to create an Evolu Id:
1596
+ *
1597
+ * - {@link createId} default cryptographically secure random bytes
1598
+ * (privacy‑preserving)
1599
+ * - {@link createIdFromString} – deterministic: first 16 bytes of SHA‑256 of a
1600
+ * string
1601
+ * - {@link createIdAsUuidv7} optional: embeds timestamp bits (UUID v7 layout)
1602
+ *
1603
+ * Privacy: the default random Id does not leak creation time and is safe to
1604
+ * share or log. The UUID v7 variant leaks creation time anywhere the Id is
1605
+ * copied (logs, URLs, exports); only use it when you explicitly want insertion
1606
+ * locality for very large write‑heavy tables and accept timestamp exposure.
1607
+ *
1608
+ * ### Future
1609
+ *
1610
+ * A possible hybrid masked‑time approach (`timestamp ^ H(cluster_id, timestamp
1611
+ *
1612
+ * > > N)`) could provide locality without exposing raw creation time. See
1613
+ * > > https://brooker.co.za/blog/2025/10/22/uuidv7.html
1585
1614
  *
1586
1615
  * @category String
1587
1616
  */
@@ -1599,26 +1628,25 @@ export const formatIdError = createTypeErrorFormatter<IdError>(
1599
1628
  );
1600
1629
 
1601
1630
  /**
1602
- * Creates an {@link Id}.
1631
+ * Creates a random {@link Id}. This is the recommended default.
1632
+ *
1633
+ * Use {@link createIdFromString} for deterministic mapping of external IDs or
1634
+ * {@link createIdAsUuidv7} when you accept timestamp leakage for index
1635
+ * locality.
1603
1636
  *
1604
1637
  * ### Example
1605
1638
  *
1606
1639
  * ```ts
1607
- * // string & Brand<"Id">
1608
1640
  * const id = createId(deps);
1609
- *
1610
- * // string & Brand<"Id"> & Brand<"Todo">
1611
1641
  * const todoId = createId<"Todo">(deps);
1612
1642
  * ```
1613
1643
  */
1614
1644
  export const createId = <B extends string = never>(
1615
1645
  deps: RandomBytesDep,
1616
- ): [B] extends [never] ? Id : Id & Brand<B> =>
1617
- uint8ArrayToBase64Url(deps.randomBytes.create(16)) as unknown as [B] extends [
1618
- never,
1619
- ]
1620
- ? Id
1621
- : Id & Brand<B>;
1646
+ ): [B] extends [never] ? Id : Id & Brand<B> => {
1647
+ const id = uint8ArrayToBase64Url(deps.randomBytes.create(16));
1648
+ return id as unknown as [B] extends [never] ? Id : Id & Brand<B>;
1649
+ };
1622
1650
 
1623
1651
  /**
1624
1652
  * Creates an {@link Id} from a string using SHA-256.
@@ -1662,6 +1690,42 @@ export const createIdFromString = <B extends string = never>(
1662
1690
  return id as [B] extends [never] ? Id : Id & Brand<B>;
1663
1691
  };
1664
1692
 
1693
+ /**
1694
+ * Creates an {@link Id} embedding timestamp bits (UUID v7 layout) before
1695
+ * Base64Url encoding.
1696
+ *
1697
+ * Tradeoff: better insertion locality / index performance for huge datasets vs
1698
+ * leaking creation time everywhere the Id appears. Evolu uses {@link createId}
1699
+ * by default to avoid activity leakage; choose this only if you explicitly
1700
+ * accept timestamp exposure.
1701
+ *
1702
+ * ### Example
1703
+ *
1704
+ * ```ts
1705
+ * const id = createIdAsUuidv7({ randomBytes, time });
1706
+ * const todoId = createIdAsUuidv7<"Todo">({ randomBytes, time });
1707
+ * ```
1708
+ */
1709
+ export const createIdAsUuidv7 = <B extends string = never>(
1710
+ deps: RandomBytesDep & TimeDep,
1711
+ ): [B] extends [never] ? Id : Id & Brand<B> => {
1712
+ const id = deps.randomBytes.create(16);
1713
+
1714
+ const timestamp = globalThis.BigInt(deps.time.now());
1715
+
1716
+ id[0] = globalThis.Number((timestamp >> 40n) & 0xffn);
1717
+ id[1] = globalThis.Number((timestamp >> 32n) & 0xffn);
1718
+ id[2] = globalThis.Number((timestamp >> 24n) & 0xffn);
1719
+ id[3] = globalThis.Number((timestamp >> 16n) & 0xffn);
1720
+ id[4] = globalThis.Number((timestamp >> 8n) & 0xffn);
1721
+ id[5] = globalThis.Number(timestamp & 0xffn);
1722
+
1723
+ id[6] = (id[6] & 0x0f) | 0x70;
1724
+ id[8] = (id[8] & 0x3f) | 0x80;
1725
+
1726
+ return id as unknown as [B] extends [never] ? Id : Id & Brand<B>;
1727
+ };
1728
+
1665
1729
  /**
1666
1730
  * Creates a branded {@link Id} Type for a table's primary key.
1667
1731
  *
@@ -3432,26 +3496,6 @@ export const formatInt64Error = createTypeErrorFormatter<Int64Error>(
3432
3496
  `The value ${error.value} is not a valid 64-bit signed integer (Int64).`,
3433
3497
  );
3434
3498
 
3435
- // // co s timhle? je to string, ze ktereho lze udelat bigint
3436
-
3437
- // export const BigIntFromString = transform(
3438
- // String,
3439
- // BigInt,
3440
- // (value) =>
3441
- // trySync(
3442
- // () => globalThis.BigInt(value),
3443
- // (): BigIntFromStringError => ({ type: "BigIntFromString", value }),
3444
- // ),
3445
- // (value) => value.toString(),
3446
- // );
3447
-
3448
- // export interface BigIntFromStringError extends TypeError<"BigIntFromString"> {}
3449
-
3450
- // export const formatBigIntFromStringError =
3451
- // createTypeErrorFormatter<BigIntFromStringError>(
3452
- // (error) => `The value ${error.value} could not be converted to a BigInt.`,
3453
- // );
3454
-
3455
3499
  /**
3456
3500
  * Stringified {@link Int64}.
3457
3501
  *