@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.
- package/README.md +2 -2
- package/dist/src/Array.d.ts +3 -3
- package/dist/src/Array.js +3 -3
- package/dist/src/Crypto.d.ts +6 -8
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +9 -9
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +4 -4
- package/dist/src/Evolu/Evolu.d.ts +9 -4
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +14 -14
- package/dist/src/Evolu/Protocol.d.ts +27 -2
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +71 -31
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
- package/dist/src/Evolu/PublicKysely.js +0 -1
- package/dist/src/Evolu/Query.d.ts +1 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Query.js +1 -1
- package/dist/src/Evolu/Schema.d.ts +28 -22
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +37 -32
- package/dist/src/Evolu/Storage.d.ts +11 -2
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +21 -3
- package/dist/src/Evolu/Sync.d.ts +4 -3
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +68 -40
- package/dist/src/Evolu/Timestamp.d.ts +10 -14
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +3 -16
- package/dist/src/Object.d.ts +10 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +9 -3
- package/dist/src/Sqlite.d.ts +29 -3
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +29 -3
- package/dist/src/Type.d.ts +94 -47
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +110 -65
- package/package.json +2 -2
- package/src/Array.ts +3 -3
- package/src/Crypto.ts +11 -9
- package/src/Evolu/Db.ts +7 -7
- package/src/Evolu/Evolu.ts +31 -24
- package/src/Evolu/Protocol.ts +84 -37
- package/src/Evolu/PublicKysely.ts +1 -2
- package/src/Evolu/Query.ts +2 -2
- package/src/Evolu/Schema.ts +54 -50
- package/src/Evolu/Storage.ts +39 -2
- package/src/Evolu/Sync.ts +97 -43
- package/src/Evolu/Timestamp.ts +5 -25
- package/src/Object.ts +13 -5
- package/src/Sqlite.ts +33 -3
- 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
|
-
*
|
|
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
|
-
* - **
|
|
19
|
-
* by
|
|
20
|
-
* - **Simple, top-down implementation** – readable source code from top to
|
|
21
|
-
*
|
|
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
|
-
*
|
|
24
|
-
*
|
|
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
|
|
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 <=
|
|
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
|
-
*
|
|
1551
|
-
*
|
|
1552
|
-
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
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
|
|
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))
|
|
1618
|
-
|
|
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
|
*
|