@evolu/common 7.2.2 → 7.3.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.
- package/dist/src/Function.d.ts +61 -0
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +19 -0
- package/dist/src/Object.d.ts +32 -1
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +32 -1
- package/dist/src/Order.d.ts +2 -1
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +1 -0
- package/dist/src/Result.d.ts +36 -29
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Sqlite.d.ts +9 -7
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +8 -0
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +6 -4
- package/dist/src/Type.d.ts +30 -2
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +82 -0
- package/dist/src/local-first/Db.d.ts +1 -5
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +112 -117
- package/dist/src/local-first/Evolu.d.ts +3 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +14 -12
- package/dist/src/local-first/LocalAuth.d.ts +2 -2
- package/dist/src/local-first/Owner.d.ts +3 -0
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.d.ts +1 -1
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +12 -12
- package/dist/src/local-first/PublicKysely.js +1 -1
- package/dist/src/local-first/Query.d.ts +54 -6
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +130 -11
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +17 -18
- package/dist/src/local-first/Schema.d.ts +15 -14
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +43 -51
- package/dist/src/local-first/Storage.d.ts +2 -2
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +2 -6
- package/dist/src/local-first/Sync.d.ts +10 -7
- package/dist/src/local-first/Sync.d.ts.map +1 -1
- package/dist/src/local-first/Sync.js +154 -103
- package/dist/src/local-first/Timestamp.d.ts +13 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +14 -1
- package/dist/src/local-first/index.d.ts +0 -1
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +0 -1
- package/package.json +1 -1
- package/src/Function.ts +74 -0
- package/src/Object.ts +40 -1
- package/src/Order.ts +2 -1
- package/src/Result.ts +38 -29
- package/src/Sqlite.ts +18 -7
- package/src/Task.ts +6 -4
- package/src/Type.ts +134 -1
- package/src/local-first/Db.ts +182 -217
- package/src/local-first/Evolu.ts +21 -10
- package/src/local-first/LocalAuth.ts +2 -2
- package/src/local-first/Owner.ts +4 -0
- package/src/local-first/Protocol.ts +13 -19
- package/src/local-first/PublicKysely.ts +1 -1
- package/src/local-first/Query.ts +216 -21
- package/src/local-first/Relay.ts +20 -24
- package/src/local-first/Schema.ts +83 -70
- package/src/local-first/Storage.ts +3 -8
- package/src/local-first/Sync.ts +215 -135
- package/src/local-first/Timestamp.ts +14 -1
- package/src/local-first/index.ts +0 -1
- package/dist/src/local-first/Diff.d.ts +0 -43
- package/dist/src/local-first/Diff.d.ts.map +0 -1
- package/dist/src/local-first/Diff.js +0 -97
- package/src/local-first/Diff.ts +0 -144
package/src/Function.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.js";
|
|
2
|
+
import { ReadonlyRecord } from "./Object.js";
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Helper function to ensure exhaustive matching in a switch statement. Throws
|
|
3
6
|
* an error if an unhandled case is encountered.
|
|
@@ -31,8 +34,79 @@ export const exhaustiveCheck = (value: never): never => {
|
|
|
31
34
|
throw new Error(`exhaustiveCheck unhandled case: ${JSON.stringify(value)}`);
|
|
32
35
|
};
|
|
33
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Returns the input value unchanged.
|
|
39
|
+
*
|
|
40
|
+
* Useful as a default transformation, placeholder callback, or when a function
|
|
41
|
+
* is required but no transformation is needed.
|
|
42
|
+
*
|
|
43
|
+
* ### Example
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const values = [1, 2, 3];
|
|
47
|
+
* const same = values.map(identity); // [1, 2, 3]
|
|
48
|
+
*
|
|
49
|
+
* const getTransform = (shouldDouble: boolean) =>
|
|
50
|
+
* shouldDouble ? (x: number) => x * 2 : identity;
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
34
53
|
export const identity = <A>(a: A): A => a;
|
|
35
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Casts an array, set, record, or map to its readonly counterpart.
|
|
57
|
+
*
|
|
58
|
+
* Zero runtime cost — returns the same value with a readonly type. Use this to
|
|
59
|
+
* enforce immutability at the type level. Preserves {@link NonEmptyArray} as
|
|
60
|
+
* {@link NonEmptyReadonlyArray}.
|
|
61
|
+
*
|
|
62
|
+
* ### Example
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* // Array literals become NonEmptyReadonlyArray
|
|
66
|
+
* const items = readonly([1, 2, 3]);
|
|
67
|
+
* // Type: NonEmptyReadonlyArray<number>
|
|
68
|
+
*
|
|
69
|
+
* // NonEmptyArray is preserved as NonEmptyReadonlyArray
|
|
70
|
+
* const nonEmpty: NonEmptyArray<number> = [1, 2, 3];
|
|
71
|
+
* const readonlyNonEmpty = readonly(nonEmpty);
|
|
72
|
+
* // Type: NonEmptyReadonlyArray<number>
|
|
73
|
+
*
|
|
74
|
+
* // Regular arrays become ReadonlyArray
|
|
75
|
+
* const arr: Array<number> = getNumbers();
|
|
76
|
+
* const readonlyArr = readonly(arr);
|
|
77
|
+
* // Type: ReadonlyArray<number>
|
|
78
|
+
*
|
|
79
|
+
* // Sets, Records, and Maps
|
|
80
|
+
* const ids = readonly(new Set(["a", "b"]));
|
|
81
|
+
* // Type: ReadonlySet<string>
|
|
82
|
+
*
|
|
83
|
+
* const users: Record<UserId, string> = { ... };
|
|
84
|
+
* const readonlyUsers = readonly(users);
|
|
85
|
+
* // Type: ReadonlyRecord<UserId, string>
|
|
86
|
+
*
|
|
87
|
+
* const lookup = readonly(new Map([["key", "value"]]));
|
|
88
|
+
* // Type: ReadonlyMap<string, string>
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @experimental
|
|
92
|
+
*/
|
|
93
|
+
export function readonly<T>(array: NonEmptyArray<T>): NonEmptyReadonlyArray<T>;
|
|
94
|
+
export function readonly<T>(array: Array<T>): ReadonlyArray<T>;
|
|
95
|
+
export function readonly<T>(set: Set<T>): ReadonlySet<T>;
|
|
96
|
+
export function readonly<K, V>(map: Map<K, V>): ReadonlyMap<K, V>;
|
|
97
|
+
export function readonly<K extends keyof any, V>(
|
|
98
|
+
record: Record<K, V>,
|
|
99
|
+
): ReadonlyRecord<K, V>;
|
|
100
|
+
export function readonly<T, K extends keyof any, V>(
|
|
101
|
+
value: Array<T> | Set<T> | Map<K, V> | Record<K, V>,
|
|
102
|
+
):
|
|
103
|
+
| ReadonlyArray<T>
|
|
104
|
+
| ReadonlySet<T>
|
|
105
|
+
| ReadonlyMap<K, V>
|
|
106
|
+
| ReadonlyRecord<K, V> {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
|
|
36
110
|
/**
|
|
37
111
|
* A function that delays computation and returns a value of type T.
|
|
38
112
|
*
|
package/src/Object.ts
CHANGED
|
@@ -31,7 +31,7 @@ type StringKeyOf<T> = Extract<keyof T, string>;
|
|
|
31
31
|
*
|
|
32
32
|
* ```ts
|
|
33
33
|
* type UserId = string & { readonly __brand: "UserId" };
|
|
34
|
-
* const users
|
|
34
|
+
* const users = createRecord<UserId, string>();
|
|
35
35
|
* const entries = objectToEntries(users); // [UserId, string][]
|
|
36
36
|
* ```
|
|
37
37
|
*/
|
|
@@ -70,3 +70,42 @@ export const excludeProp = <T extends object, K extends keyof T>(
|
|
|
70
70
|
const { [prop]: _, ...rest } = obj;
|
|
71
71
|
return rest;
|
|
72
72
|
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a prototype-less object typed as `Record<K, V>`.
|
|
76
|
+
*
|
|
77
|
+
* Use this function when you need a plain record without a prototype chain
|
|
78
|
+
* (e.g. when keys are controlled by external sources) to avoid prototype
|
|
79
|
+
* pollution and accidental collisions with properties like `__proto__`.
|
|
80
|
+
*
|
|
81
|
+
* Example:
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* const values = createRecord<string, SqliteValue>();
|
|
85
|
+
* values["__proto__"] = someValue; // safe, no prototype pollution
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export const createRecord = <K extends string = string, V = unknown>(): Record<
|
|
89
|
+
K,
|
|
90
|
+
V
|
|
91
|
+
> => Object.create(null) as Record<K, V>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Safely gets a property from a record, returning `undefined` if the key
|
|
95
|
+
* doesn't exist.
|
|
96
|
+
*
|
|
97
|
+
* TypeScript's `Record<K, V>` type assumes all keys exist, but at runtime
|
|
98
|
+
* accessing a non-existent key returns `undefined`. This helper provides proper
|
|
99
|
+
* typing for that case without needing a type assertion.
|
|
100
|
+
*
|
|
101
|
+
* ### Example
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* const users: Record<string, User> = { alice: { name: "Alice" } };
|
|
105
|
+
* const user = getProperty(users, "bob"); // User | undefined
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export const getProperty = <K extends string, V>(
|
|
109
|
+
record: ReadonlyRecord<K, V>,
|
|
110
|
+
key: string,
|
|
111
|
+
): V | undefined => (key in record ? record[key as K] : undefined);
|
package/src/Order.ts
CHANGED
|
@@ -94,7 +94,8 @@ export const orderNumber = createOrder<number>((a, b) => a < b);
|
|
|
94
94
|
*/
|
|
95
95
|
export const orderBigInt = createOrder<bigint>((a, b) => a < b);
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
/** An {@link Order} for Uint8Array. */
|
|
98
|
+
export const orderUint8Array: Order<Uint8Array> = (a, b) => {
|
|
98
99
|
if (a.byteLength > b.byteLength) return 1;
|
|
99
100
|
if (a.byteLength < b.byteLength) return -1;
|
|
100
101
|
|
package/src/Result.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Task } from "./Task.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The problem with throwing an exception in JavaScript is that the caught error
|
|
3
5
|
* is always of an unknown type. The unknown type is a problem because we can't
|
|
@@ -82,19 +84,22 @@
|
|
|
82
84
|
* };
|
|
83
85
|
* ```
|
|
84
86
|
*
|
|
87
|
+
* For lazy, cancellable async operations, see {@link Task}.
|
|
88
|
+
*
|
|
85
89
|
* ### Naming convention
|
|
86
90
|
*
|
|
87
|
-
* - For values
|
|
88
|
-
* - For
|
|
89
|
-
*
|
|
91
|
+
* - **For values you need:** use a name without Result suffix (`user`, `config`)
|
|
92
|
+
* - **For void operations:** use `result` (no value to name)
|
|
93
|
+
*
|
|
94
|
+
* For multiple void operations, use block scopes to avoid potentially long
|
|
95
|
+
* names like `createBaseTablesResult`, `createRelayTablesResult`, or counters
|
|
96
|
+
* like `result1`, `result2`:
|
|
90
97
|
*
|
|
91
98
|
* ```ts
|
|
92
99
|
* const processUser = () => {
|
|
93
|
-
* // we have a value
|
|
94
100
|
* const user = getUser();
|
|
95
101
|
* if (!user.ok) return user;
|
|
96
102
|
*
|
|
97
|
-
* // single void operation
|
|
98
103
|
* const result = saveToDatabase(user.value);
|
|
99
104
|
* if (!result.ok) return result;
|
|
100
105
|
*
|
|
@@ -102,12 +107,15 @@
|
|
|
102
107
|
* };
|
|
103
108
|
*
|
|
104
109
|
* const setupDatabase = () => {
|
|
105
|
-
* //
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
110
|
+
* // Multiple void operations - use block scopes to avoid name clash
|
|
111
|
+
* {
|
|
112
|
+
* const result = createBaseTables();
|
|
113
|
+
* if (!result.ok) return result;
|
|
114
|
+
* }
|
|
115
|
+
* {
|
|
116
|
+
* const result = createRelayTables();
|
|
117
|
+
* if (!result.ok) return result;
|
|
118
|
+
* }
|
|
111
119
|
*
|
|
112
120
|
* return ok();
|
|
113
121
|
* };
|
|
@@ -124,30 +132,31 @@
|
|
|
124
132
|
* schema, and initializes the database, stopping on the first error:
|
|
125
133
|
*
|
|
126
134
|
* ```ts
|
|
127
|
-
* const
|
|
128
|
-
* const
|
|
129
|
-
* if (!
|
|
135
|
+
* const result = deps.sqlite.transaction(() => {
|
|
136
|
+
* const result = dropAllTables(deps);
|
|
137
|
+
* if (!result.ok) return result;
|
|
130
138
|
*
|
|
131
139
|
* if (message.restore) {
|
|
132
140
|
* const dbSchema = getDbSchema(deps)();
|
|
133
141
|
* if (!dbSchema.ok) return dbSchema;
|
|
134
142
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
143
|
+
* {
|
|
144
|
+
* const result = ensureDbSchema(deps)(
|
|
145
|
+
* message.restore.dbSchema,
|
|
146
|
+
* dbSchema.value,
|
|
147
|
+
* );
|
|
148
|
+
* if (!result.ok) return result;
|
|
149
|
+
* }
|
|
150
|
+
* {
|
|
151
|
+
* const result = initializeDb(deps)(message.restore.mnemonic);
|
|
152
|
+
* if (!result.ok) return result;
|
|
153
|
+
* }
|
|
145
154
|
* }
|
|
146
155
|
* return ok();
|
|
147
156
|
* });
|
|
148
157
|
*
|
|
149
|
-
* if (!
|
|
150
|
-
* deps.postMessage({ type: "onError", error:
|
|
158
|
+
* if (!result.ok) {
|
|
159
|
+
* deps.postMessage({ type: "onError", error: result.error });
|
|
151
160
|
* return;
|
|
152
161
|
* }
|
|
153
162
|
* ```
|
|
@@ -254,13 +263,13 @@
|
|
|
254
263
|
* ```ts
|
|
255
264
|
* // ✅ Safe to return void - unsafe code is wrapped and error is handled
|
|
256
265
|
* const processData = (data: string): void => {
|
|
257
|
-
* const
|
|
266
|
+
* const result = trySync(
|
|
258
267
|
* () => JSON.parse(data),
|
|
259
268
|
* (error) => ({ type: "ParseError", message: String(error) }),
|
|
260
269
|
* );
|
|
261
270
|
*
|
|
262
|
-
* if (!
|
|
263
|
-
* logError(
|
|
271
|
+
* if (!result.ok) {
|
|
272
|
+
* logError(result.error);
|
|
264
273
|
* return;
|
|
265
274
|
* }
|
|
266
275
|
*
|
package/src/Sqlite.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Brand } from "./Brand.js";
|
|
|
2
2
|
import { createLruCache } from "./Cache.js";
|
|
3
3
|
import { ConsoleDep } from "./Console.js";
|
|
4
4
|
import { EncryptionKey } from "./Crypto.js";
|
|
5
|
+
import { Eq, eqArrayNumber } from "./Eq.js";
|
|
5
6
|
import { createTransferableError, TransferableError } from "./Error.js";
|
|
6
7
|
import { err, ok, Result, tryAsync, trySync } from "./Result.js";
|
|
7
8
|
import {
|
|
@@ -34,8 +35,8 @@ export interface CreateSqliteDriverDep {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export interface SqliteDriverOptions {
|
|
37
|
-
memory?: boolean;
|
|
38
|
-
encryptionKey?: EncryptionKey | undefined;
|
|
38
|
+
readonly memory?: boolean;
|
|
39
|
+
readonly encryptionKey?: EncryptionKey | undefined;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -75,7 +76,7 @@ export interface SqliteQuery {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/** A type representing a sanitized SQL string. */
|
|
78
|
-
export type SafeSql = string & Brand<"
|
|
79
|
+
export type SafeSql = string & Brand<"SafeSql">;
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
82
|
* A value that can be stored in Sqlite.
|
|
@@ -86,6 +87,16 @@ export type SafeSql = string & Brand<"TimestampString">;
|
|
|
86
87
|
export const SqliteValue = union(Null, String, Number, Uint8Array);
|
|
87
88
|
export type SqliteValue = typeof SqliteValue.Type;
|
|
88
89
|
|
|
90
|
+
export const eqSqliteValue: Eq<SqliteValue> = (x, y) => {
|
|
91
|
+
if (
|
|
92
|
+
x instanceof globalThis.Uint8Array &&
|
|
93
|
+
y instanceof globalThis.Uint8Array
|
|
94
|
+
) {
|
|
95
|
+
return eqArrayNumber(x, y);
|
|
96
|
+
}
|
|
97
|
+
return x === y;
|
|
98
|
+
};
|
|
99
|
+
|
|
89
100
|
export interface SqliteQueryOptions {
|
|
90
101
|
/**
|
|
91
102
|
* If set to `true`, logs the time taken to execute the SQL query. Useful for
|
|
@@ -292,13 +303,13 @@ export const createPreparedStatementsCache = <P>(
|
|
|
292
303
|
};
|
|
293
304
|
|
|
294
305
|
export interface SqlIdentifier {
|
|
295
|
-
type: "SqlIdentifier";
|
|
296
|
-
sql: SafeSql;
|
|
306
|
+
readonly type: "SqlIdentifier";
|
|
307
|
+
readonly sql: SafeSql;
|
|
297
308
|
}
|
|
298
309
|
|
|
299
310
|
export interface RawSql {
|
|
300
|
-
type: "RawSql";
|
|
301
|
-
sql: string;
|
|
311
|
+
readonly type: "RawSql";
|
|
312
|
+
readonly sql: string;
|
|
302
313
|
}
|
|
303
314
|
|
|
304
315
|
export type SqlTemplateParam = SqliteValue | SqlIdentifier | RawSql;
|
package/src/Task.ts
CHANGED
|
@@ -570,10 +570,12 @@ export const retry = <T, E>(
|
|
|
570
570
|
}
|
|
571
571
|
|
|
572
572
|
// Wait before retry
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
573
|
+
{
|
|
574
|
+
const result = await wait(NonNegativeInt.orThrow(delay))(context);
|
|
575
|
+
if (!result.ok) {
|
|
576
|
+
// If delay was aborted, return AbortError (will be handled by toTask)
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
577
579
|
}
|
|
578
580
|
}
|
|
579
581
|
});
|
package/src/Type.ts
CHANGED
|
@@ -207,7 +207,7 @@ import * as bip39 from "@scure/bip39";
|
|
|
207
207
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
208
208
|
import { pack } from "msgpackr";
|
|
209
209
|
import type { Brand } from "./Brand.js";
|
|
210
|
-
import {
|
|
210
|
+
import type { RandomBytesDep } from "./Crypto.js";
|
|
211
211
|
import { isPlainObject } from "./Object.js";
|
|
212
212
|
import { hasNodeBuffer } from "./Platform.js";
|
|
213
213
|
import { err, getOrNull, getOrThrow, ok, Result, trySync } from "./Result.js";
|
|
@@ -2391,6 +2391,124 @@ export const formatArrayError = <Error extends TypeError>(
|
|
|
2391
2391
|
}
|
|
2392
2392
|
});
|
|
2393
2393
|
|
|
2394
|
+
/**
|
|
2395
|
+
* Set of a specific {@link Type}.
|
|
2396
|
+
*
|
|
2397
|
+
* ### Example
|
|
2398
|
+
*
|
|
2399
|
+
* ```ts
|
|
2400
|
+
* const NumberSet = set(Number);
|
|
2401
|
+
*
|
|
2402
|
+
* const result1 = NumberSet.from(new Set([1, 2, 3])); // ok(Set { 1, 2, 3 })
|
|
2403
|
+
* const result2 = NumberSet.from(new Set(["a", "b"])); // err(...)
|
|
2404
|
+
* ```
|
|
2405
|
+
*
|
|
2406
|
+
* @category Base Factories
|
|
2407
|
+
*/
|
|
2408
|
+
export const set = <ElementType extends AnyType>(
|
|
2409
|
+
element: ElementType,
|
|
2410
|
+
): SetType<ElementType> => {
|
|
2411
|
+
const fromUnknown = (
|
|
2412
|
+
value: unknown,
|
|
2413
|
+
): Result<
|
|
2414
|
+
ReadonlySet<InferType<ElementType>>,
|
|
2415
|
+
SetError<InferErrors<ElementType>>
|
|
2416
|
+
> => {
|
|
2417
|
+
if (!(value instanceof globalThis.Set)) {
|
|
2418
|
+
return err<SetError<InferErrors<ElementType>>>({
|
|
2419
|
+
type: "Set",
|
|
2420
|
+
value,
|
|
2421
|
+
reason: { kind: "NotSet" },
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
let index = 0;
|
|
2426
|
+
for (const item of value) {
|
|
2427
|
+
const elementResult = element.fromUnknown(item);
|
|
2428
|
+
if (!elementResult.ok) {
|
|
2429
|
+
return err<SetError<InferErrors<ElementType>>>({
|
|
2430
|
+
type: "Set",
|
|
2431
|
+
value,
|
|
2432
|
+
reason: {
|
|
2433
|
+
kind: "Element",
|
|
2434
|
+
index,
|
|
2435
|
+
error: elementResult.error as InferErrors<ElementType>,
|
|
2436
|
+
},
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
index++;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
return ok(value as ReadonlySet<InferType<ElementType>>);
|
|
2443
|
+
};
|
|
2444
|
+
|
|
2445
|
+
const fromParent = (
|
|
2446
|
+
value: ReadonlySet<InferParent<ElementType>>,
|
|
2447
|
+
): Result<
|
|
2448
|
+
ReadonlySet<InferType<ElementType>>,
|
|
2449
|
+
SetError<InferError<ElementType>>
|
|
2450
|
+
> => {
|
|
2451
|
+
let index = 0;
|
|
2452
|
+
for (const item of value) {
|
|
2453
|
+
const elementResult = element.fromParent(item);
|
|
2454
|
+
if (!elementResult.ok) {
|
|
2455
|
+
return err({
|
|
2456
|
+
type: "Set",
|
|
2457
|
+
value,
|
|
2458
|
+
reason: {
|
|
2459
|
+
kind: "Element",
|
|
2460
|
+
index,
|
|
2461
|
+
error: elementResult.error as InferError<ElementType>,
|
|
2462
|
+
},
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
index++;
|
|
2466
|
+
}
|
|
2467
|
+
return ok(value as ReadonlySet<InferType<ElementType>>);
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
return {
|
|
2471
|
+
...createType("Set", { fromUnknown, fromParent }),
|
|
2472
|
+
element,
|
|
2473
|
+
};
|
|
2474
|
+
};
|
|
2475
|
+
|
|
2476
|
+
/** SetType extends Type with an additional `element` property for reflection. */
|
|
2477
|
+
export interface SetType<ElementType extends AnyType>
|
|
2478
|
+
extends Type<
|
|
2479
|
+
"Set",
|
|
2480
|
+
ReadonlySet<InferType<ElementType>>,
|
|
2481
|
+
ReadonlySet<InferInput<ElementType>>,
|
|
2482
|
+
SetError<InferError<ElementType>>,
|
|
2483
|
+
ReadonlySet<InferParent<ElementType>>,
|
|
2484
|
+
SetError<InferParentError<ElementType>>
|
|
2485
|
+
> {
|
|
2486
|
+
readonly element: ElementType;
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
export interface SetError<Error extends TypeError = TypeError>
|
|
2490
|
+
extends TypeErrorWithReason<
|
|
2491
|
+
"Set",
|
|
2492
|
+
| { readonly kind: "NotSet" }
|
|
2493
|
+
| {
|
|
2494
|
+
readonly kind: "Element";
|
|
2495
|
+
readonly index: number;
|
|
2496
|
+
readonly error: Error;
|
|
2497
|
+
}
|
|
2498
|
+
> {}
|
|
2499
|
+
|
|
2500
|
+
export const formatSetError = <Error extends TypeError>(
|
|
2501
|
+
formatTypeError: TypeErrorFormatter<Error>,
|
|
2502
|
+
): TypeErrorFormatter<SetError<Error>> =>
|
|
2503
|
+
createTypeErrorFormatter((error) => {
|
|
2504
|
+
switch (error.reason.kind) {
|
|
2505
|
+
case "NotSet":
|
|
2506
|
+
return `Expected a Set but received ${error.value}.`;
|
|
2507
|
+
case "Element":
|
|
2508
|
+
return `Invalid element at index ${error.reason.index}: ${formatTypeError(error.reason.error)}`;
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
|
|
2394
2512
|
/**
|
|
2395
2513
|
* Record of a key {@link Type} and value {@link Type}.
|
|
2396
2514
|
*
|
|
@@ -3970,6 +4088,7 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
|
|
|
3970
4088
|
| ExtraErrors
|
|
3971
4089
|
// Composite errors
|
|
3972
4090
|
| ArrayError<TypeErrors<ExtraErrors>>
|
|
4091
|
+
| SetError<TypeErrors<ExtraErrors>>
|
|
3973
4092
|
| RecordError<TypeErrors<ExtraErrors>, TypeErrors<ExtraErrors>>
|
|
3974
4093
|
| ObjectError<Record<string, TypeErrors<ExtraErrors>>>
|
|
3975
4094
|
| ObjectWithRecordError<
|
|
@@ -4154,6 +4273,8 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
|
|
|
4154
4273
|
return formatSimplePasswordError(formatTypeError)(error);
|
|
4155
4274
|
case "Array":
|
|
4156
4275
|
return formatArrayError(formatTypeError)(error);
|
|
4276
|
+
case "Set":
|
|
4277
|
+
return formatSetError(formatTypeError)(error);
|
|
4157
4278
|
case "Record":
|
|
4158
4279
|
return formatRecordError(formatTypeError)(error);
|
|
4159
4280
|
case "Object":
|
|
@@ -4202,6 +4323,18 @@ export const typeErrorToStandardSchemaIssues = <
|
|
|
4202
4323
|
);
|
|
4203
4324
|
}
|
|
4204
4325
|
|
|
4326
|
+
if (error.type === "Set") {
|
|
4327
|
+
const setError = error as SetError;
|
|
4328
|
+
if (setError.reason.kind === "NotSet") {
|
|
4329
|
+
return [{ message: formatTypeError(error), path }];
|
|
4330
|
+
}
|
|
4331
|
+
return typeErrorToStandardSchemaIssues(
|
|
4332
|
+
setError.reason.error as TypeErrors<ExtraErrors>,
|
|
4333
|
+
formatTypeError,
|
|
4334
|
+
[...path, setError.reason.index],
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4205
4338
|
if (error.type === "Object") {
|
|
4206
4339
|
const objectError = error as ObjectError;
|
|
4207
4340
|
if (
|