@evolu/common 7.2.3 → 7.4.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.
Files changed (92) hide show
  1. package/dist/src/Buffer.d.ts +2 -1
  2. package/dist/src/Buffer.d.ts.map +1 -1
  3. package/dist/src/Buffer.js +1 -0
  4. package/dist/src/Callbacks.d.ts +1 -1
  5. package/dist/src/Callbacks.d.ts.map +1 -1
  6. package/dist/src/Callbacks.js +1 -1
  7. package/dist/src/Console.d.ts +1 -9
  8. package/dist/src/Console.d.ts.map +1 -1
  9. package/dist/src/Console.js +1 -41
  10. package/dist/src/Function.d.ts +61 -0
  11. package/dist/src/Function.d.ts.map +1 -1
  12. package/dist/src/Function.js +19 -0
  13. package/dist/src/Object.d.ts +16 -0
  14. package/dist/src/Object.d.ts.map +1 -1
  15. package/dist/src/Object.js +16 -0
  16. package/dist/src/Redacted.d.ts +89 -0
  17. package/dist/src/Redacted.d.ts.map +1 -0
  18. package/dist/src/Redacted.js +48 -0
  19. package/dist/src/Result.d.ts +36 -29
  20. package/dist/src/Result.d.ts.map +1 -1
  21. package/dist/src/Sqlite.d.ts +9 -7
  22. package/dist/src/Sqlite.d.ts.map +1 -1
  23. package/dist/src/Sqlite.js +8 -0
  24. package/dist/src/Task.d.ts.map +1 -1
  25. package/dist/src/Task.js +6 -4
  26. package/dist/src/Type.d.ts +34 -10
  27. package/dist/src/Type.d.ts.map +1 -1
  28. package/dist/src/Type.js +82 -202
  29. package/dist/src/index.d.ts +1 -0
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/index.js +1 -0
  32. package/dist/src/local-first/Db.d.ts +1 -5
  33. package/dist/src/local-first/Db.d.ts.map +1 -1
  34. package/dist/src/local-first/Db.js +109 -108
  35. package/dist/src/local-first/Evolu.d.ts +3 -0
  36. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  37. package/dist/src/local-first/Evolu.js +6 -7
  38. package/dist/src/local-first/LocalAuth.d.ts +2 -2
  39. package/dist/src/local-first/Owner.d.ts +7 -0
  40. package/dist/src/local-first/Owner.d.ts.map +1 -1
  41. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  42. package/dist/src/local-first/Protocol.js +10 -10
  43. package/dist/src/local-first/Public.d.ts +6 -1
  44. package/dist/src/local-first/Public.d.ts.map +1 -1
  45. package/dist/src/local-first/Public.js +6 -1
  46. package/dist/src/local-first/PublicKysely.js +1 -1
  47. package/dist/src/local-first/Query.d.ts +54 -6
  48. package/dist/src/local-first/Query.d.ts.map +1 -1
  49. package/dist/src/local-first/Query.js +130 -11
  50. package/dist/src/local-first/Relay.d.ts.map +1 -1
  51. package/dist/src/local-first/Relay.js +17 -18
  52. package/dist/src/local-first/Schema.d.ts +15 -14
  53. package/dist/src/local-first/Schema.d.ts.map +1 -1
  54. package/dist/src/local-first/Schema.js +43 -51
  55. package/dist/src/local-first/Storage.d.ts +2 -2
  56. package/dist/src/local-first/Storage.d.ts.map +1 -1
  57. package/dist/src/local-first/Storage.js +2 -6
  58. package/dist/src/local-first/Sync.d.ts +9 -4
  59. package/dist/src/local-first/Sync.d.ts.map +1 -1
  60. package/dist/src/local-first/Sync.js +132 -59
  61. package/dist/src/local-first/index.d.ts +9 -2
  62. package/dist/src/local-first/index.d.ts.map +1 -1
  63. package/dist/src/local-first/index.js +9 -2
  64. package/package.json +3 -4
  65. package/src/Buffer.ts +2 -1
  66. package/src/Callbacks.ts +1 -1
  67. package/src/Console.ts +1 -10
  68. package/src/Function.ts +74 -0
  69. package/src/Object.ts +20 -0
  70. package/src/Redacted.ts +118 -0
  71. package/src/Result.ts +38 -29
  72. package/src/Sqlite.ts +18 -7
  73. package/src/Task.ts +6 -4
  74. package/src/Type.ts +148 -20
  75. package/src/index.ts +1 -0
  76. package/src/local-first/Db.ts +181 -209
  77. package/src/local-first/Evolu.ts +9 -6
  78. package/src/local-first/LocalAuth.ts +2 -2
  79. package/src/local-first/Owner.ts +8 -0
  80. package/src/local-first/Protocol.ts +10 -16
  81. package/src/local-first/Public.ts +6 -1
  82. package/src/local-first/PublicKysely.ts +1 -1
  83. package/src/local-first/Query.ts +216 -21
  84. package/src/local-first/Relay.ts +20 -24
  85. package/src/local-first/Schema.ts +83 -70
  86. package/src/local-first/Storage.ts +3 -8
  87. package/src/local-first/Sync.ts +190 -66
  88. package/src/local-first/index.ts +9 -2
  89. package/dist/src/local-first/Diff.d.ts +0 -43
  90. package/dist/src/local-first/Diff.d.ts.map +0 -1
  91. package/dist/src/local-first/Diff.js +0 -97
  92. package/src/local-first/Diff.ts +0 -144
@@ -0,0 +1,118 @@
1
+ import { assert } from "./Assert.js";
2
+ import type { Brand } from "./Brand.js";
3
+ import type { Eq } from "./Eq.js";
4
+
5
+ /**
6
+ * A wrapper type that prevents sensitive values from being accidentally exposed
7
+ * through logging, serialization, or inspection.
8
+ *
9
+ * The wrapped value is hidden and can only be accessed explicitly via
10
+ * {@link revealRedacted}. All standard methods (`toString`, `toJSON`, and
11
+ * Node.js inspect) return `<redacted>`.
12
+ *
13
+ * For type-level distinction between different secrets, use branded types.
14
+ *
15
+ * The actual value lives in a `WeakMap`, so it never appears as a property and
16
+ * is automatically garbage collected when the wrapper is dropped. This is
17
+ * better than a class with a private field because private fields are still
18
+ * visible in devtools. Symbols can't be used because they don't support custom
19
+ * `toString`.
20
+ *
21
+ * Implements `Disposable` for automatic cleanup via the `using` syntax.
22
+ *
23
+ * ### Example
24
+ *
25
+ * ```ts
26
+ * // Define branded types for your secrets
27
+ * type ApiKey = string & Brand<"ApiKey">;
28
+ * type DbPassword = string & Brand<"DbPassword">;
29
+ *
30
+ * // Wrap them with Redacted for safe passing
31
+ * type RedactedApiKey = Redacted<ApiKey>;
32
+ * type RedactedDbPassword = Redacted<DbPassword>;
33
+ *
34
+ * // Create a redacted secret
35
+ * const apiKey: ApiKey = "secret-123" as ApiKey;
36
+ * const redactedKey: RedactedApiKey = createRedacted(apiKey);
37
+ *
38
+ * console.log(redactedKey); // <redacted>
39
+ * console.log(revealRedacted(redactedKey)); // secret-123
40
+ *
41
+ * // Type safety: RedactedApiKey ≠ RedactedDbPassword
42
+ * const fetchUser = (key: RedactedApiKey) => {
43
+ * const value: ApiKey = revealRedacted(key);
44
+ * // use value...
45
+ * };
46
+ *
47
+ * fetchUser(redactedKey); // ✅
48
+ * // fetchUser(createRedacted("x" as DbPassword)); // ❌ type error
49
+ *
50
+ * // Automatic cleanup with `using`
51
+ * {
52
+ * using secret = createRedacted("sensitive" as ApiKey);
53
+ * // ... use secret ...
54
+ * } // automatically wiped from memory
55
+ * ```
56
+ *
57
+ * @experimental
58
+ */
59
+ export interface Redacted<A> extends Brand<"Redacted">, Disposable {
60
+ /** The inner type. Useful for inference via `typeof redacted.Type`. */
61
+ readonly Type: A;
62
+ }
63
+
64
+ /** Creates a {@link Redacted} wrapper for a sensitive value. */
65
+ export const createRedacted = <A>(value: A): Redacted<A> => {
66
+ const redacted = Object.create(proto) as Redacted<A>;
67
+ registry.set(redacted, value);
68
+ return redacted;
69
+ };
70
+
71
+ const proto = {
72
+ toString: () => redactedString,
73
+ toJSON: () => redactedString,
74
+ [Symbol.for("nodejs.util.inspect.custom")]: () => redactedString,
75
+ [Symbol.dispose](this: Redacted<unknown>) {
76
+ registry.delete(this);
77
+ },
78
+ };
79
+ const redactedString = "<redacted>";
80
+ const registry = new WeakMap<Redacted<unknown>, unknown>();
81
+
82
+ /**
83
+ * Reveals the original value from a {@link Redacted} wrapper.
84
+ *
85
+ * This is a separate function rather than a method on {@link Redacted} to make
86
+ * access visually explicit and easy to grep in code reviews. Accessing
87
+ * sensitive values should feel intentional, not convenient.
88
+ */
89
+ export const revealRedacted = <A>(redacted: Redacted<A>): A => {
90
+ assert(registry.has(redacted), "Redacted value was not in registry");
91
+ return registry.get(redacted) as A;
92
+ };
93
+
94
+ /** Checks if a value is a {@link Redacted} wrapper. */
95
+ export const isRedacted = (value: unknown): value is Redacted<unknown> =>
96
+ typeof value === "object" &&
97
+ value !== null &&
98
+ Object.getPrototypeOf(value) === proto;
99
+
100
+ /**
101
+ * Creates an {@link Eq} for {@link Redacted} values based on an equality function
102
+ * for the underlying type.
103
+ *
104
+ * ### Example
105
+ *
106
+ * ```ts
107
+ * type ApiKey = string & Brand<"ApiKey">;
108
+ * const eqRedactedApiKey = createEqRedacted<ApiKey>(eqString);
109
+ *
110
+ * const a = createRedacted("x" as ApiKey);
111
+ * const b = createRedacted("x" as ApiKey);
112
+ * eqRedactedApiKey(a, b); // true
113
+ * ```
114
+ */
115
+ export const createEqRedacted =
116
+ <A>(eq: Eq<A>): Eq<Redacted<A>> =>
117
+ (x, y) =>
118
+ eq(revealRedacted(x), revealRedacted(y));
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: `const user = getUser()`
88
- * - For a single void operation: `const result = foo()`
89
- * - For multiple void operations: use descriptive names for all
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
- * // multiple void operations - use descriptive names
106
- * const baseTables = createBaseTables();
107
- * if (!baseTables.ok) return baseTables;
108
- *
109
- * const relayTables = createRelayTables();
110
- * if (!relayTables.ok) return relayTables;
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 resetResult = deps.sqlite.transaction(() => {
128
- * const dropAllTablesResult = dropAllTables(deps);
129
- * if (!dropAllTablesResult.ok) return dropAllTablesResult;
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
- * const ensureDbSchemaResult = ensureDbSchema(deps)(
136
- * message.restore.dbSchema,
137
- * dbSchema.value,
138
- * );
139
- * if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
140
- *
141
- * const initializeDbResult = initializeDb(deps)(
142
- * message.restore.mnemonic,
143
- * );
144
- * if (!initializeDbResult.ok) return initializeDbResult;
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 (!resetResult.ok) {
150
- * deps.postMessage({ type: "onError", error: resetResult.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 parseResult = trySync(
266
+ * const result = trySync(
258
267
  * () => JSON.parse(data),
259
268
  * (error) => ({ type: "ParseError", message: String(error) }),
260
269
  * );
261
270
  *
262
- * if (!parseResult.ok) {
263
- * logError(parseResult.error);
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<"TimestampString">;
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
- const delayResult = await wait(NonNegativeInt.orThrow(delay))(context);
574
- if (!delayResult.ok) {
575
- // If delay was aborted, return AbortError (will be handled by toTask)
576
- return delayResult;
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
@@ -1,6 +1,19 @@
1
+ import { utf8ToBytes } from "@noble/ciphers/utils.js";
2
+ import { sha256 } from "@noble/hashes/sha2.js";
3
+ import * as bip39 from "@scure/bip39";
4
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
5
+ import { pack } from "msgpackr";
6
+ import type { Brand } from "./Brand.js";
7
+ import type { RandomBytesDep } from "./Crypto.js";
8
+ import { isPlainObject } from "./Object.js";
9
+ import { hasNodeBuffer } from "./Platform.js";
10
+ import { err, getOrNull, getOrThrow, ok, Result, trySync } from "./Result.js";
11
+ import { safelyStringifyUnknownValue } from "./String.js";
12
+ import type { TimeDep } from "./Time.js";
13
+ import type { Literal, Refinement, Simplify, WidenLiteral } from "./Types.js";
14
+ import { IntentionalNever } from "./Types.js";
15
+
1
16
  /**
2
- * Runtime types
3
- *
4
17
  * Evolu {@link Type} is like a type guard that returns typed errors (via
5
18
  * {@link Result}) instead of throwing. We either get a safely typed value or a
6
19
  * composable typed error telling us exactly why validation failed.
@@ -197,25 +210,7 @@
197
210
  * ```
198
211
  *
199
212
  * Note `minLength` and `maxLength` are curried because they are factories.
200
- *
201
- * @module
202
213
  */
203
-
204
- import { utf8ToBytes } from "@noble/ciphers/utils.js";
205
- import { sha256 } from "@noble/hashes/sha2.js";
206
- import * as bip39 from "@scure/bip39";
207
- import { wordlist } from "@scure/bip39/wordlists/english.js";
208
- import { pack } from "msgpackr";
209
- import type { Brand } from "./Brand.js";
210
- import { type RandomBytesDep } from "./Crypto.js";
211
- import { isPlainObject } from "./Object.js";
212
- import { hasNodeBuffer } from "./Platform.js";
213
- import { err, getOrNull, getOrThrow, ok, Result, trySync } from "./Result.js";
214
- import { safelyStringifyUnknownValue } from "./String.js";
215
- import type { TimeDep } from "./Time.js";
216
- import type { Literal, Refinement, Simplify, WidenLiteral } from "./Types.js";
217
- import { IntentionalNever } from "./Types.js";
218
-
219
214
  export interface Type<
220
215
  Name extends TypeName,
221
216
  /** The type this Type resolves to. */
@@ -2391,6 +2386,124 @@ export const formatArrayError = <Error extends TypeError>(
2391
2386
  }
2392
2387
  });
2393
2388
 
2389
+ /**
2390
+ * Set of a specific {@link Type}.
2391
+ *
2392
+ * ### Example
2393
+ *
2394
+ * ```ts
2395
+ * const NumberSet = set(Number);
2396
+ *
2397
+ * const result1 = NumberSet.from(new Set([1, 2, 3])); // ok(Set { 1, 2, 3 })
2398
+ * const result2 = NumberSet.from(new Set(["a", "b"])); // err(...)
2399
+ * ```
2400
+ *
2401
+ * @category Base Factories
2402
+ */
2403
+ export const set = <ElementType extends AnyType>(
2404
+ element: ElementType,
2405
+ ): SetType<ElementType> => {
2406
+ const fromUnknown = (
2407
+ value: unknown,
2408
+ ): Result<
2409
+ ReadonlySet<InferType<ElementType>>,
2410
+ SetError<InferErrors<ElementType>>
2411
+ > => {
2412
+ if (!(value instanceof globalThis.Set)) {
2413
+ return err<SetError<InferErrors<ElementType>>>({
2414
+ type: "Set",
2415
+ value,
2416
+ reason: { kind: "NotSet" },
2417
+ });
2418
+ }
2419
+
2420
+ let index = 0;
2421
+ for (const item of value) {
2422
+ const elementResult = element.fromUnknown(item);
2423
+ if (!elementResult.ok) {
2424
+ return err<SetError<InferErrors<ElementType>>>({
2425
+ type: "Set",
2426
+ value,
2427
+ reason: {
2428
+ kind: "Element",
2429
+ index,
2430
+ error: elementResult.error as InferErrors<ElementType>,
2431
+ },
2432
+ });
2433
+ }
2434
+ index++;
2435
+ }
2436
+
2437
+ return ok(value as ReadonlySet<InferType<ElementType>>);
2438
+ };
2439
+
2440
+ const fromParent = (
2441
+ value: ReadonlySet<InferParent<ElementType>>,
2442
+ ): Result<
2443
+ ReadonlySet<InferType<ElementType>>,
2444
+ SetError<InferError<ElementType>>
2445
+ > => {
2446
+ let index = 0;
2447
+ for (const item of value) {
2448
+ const elementResult = element.fromParent(item);
2449
+ if (!elementResult.ok) {
2450
+ return err({
2451
+ type: "Set",
2452
+ value,
2453
+ reason: {
2454
+ kind: "Element",
2455
+ index,
2456
+ error: elementResult.error as InferError<ElementType>,
2457
+ },
2458
+ });
2459
+ }
2460
+ index++;
2461
+ }
2462
+ return ok(value as ReadonlySet<InferType<ElementType>>);
2463
+ };
2464
+
2465
+ return {
2466
+ ...createType("Set", { fromUnknown, fromParent }),
2467
+ element,
2468
+ };
2469
+ };
2470
+
2471
+ /** SetType extends Type with an additional `element` property for reflection. */
2472
+ export interface SetType<ElementType extends AnyType>
2473
+ extends Type<
2474
+ "Set",
2475
+ ReadonlySet<InferType<ElementType>>,
2476
+ ReadonlySet<InferInput<ElementType>>,
2477
+ SetError<InferError<ElementType>>,
2478
+ ReadonlySet<InferParent<ElementType>>,
2479
+ SetError<InferParentError<ElementType>>
2480
+ > {
2481
+ readonly element: ElementType;
2482
+ }
2483
+
2484
+ export interface SetError<Error extends TypeError = TypeError>
2485
+ extends TypeErrorWithReason<
2486
+ "Set",
2487
+ | { readonly kind: "NotSet" }
2488
+ | {
2489
+ readonly kind: "Element";
2490
+ readonly index: number;
2491
+ readonly error: Error;
2492
+ }
2493
+ > {}
2494
+
2495
+ export const formatSetError = <Error extends TypeError>(
2496
+ formatTypeError: TypeErrorFormatter<Error>,
2497
+ ): TypeErrorFormatter<SetError<Error>> =>
2498
+ createTypeErrorFormatter((error) => {
2499
+ switch (error.reason.kind) {
2500
+ case "NotSet":
2501
+ return `Expected a Set but received ${error.value}.`;
2502
+ case "Element":
2503
+ return `Invalid element at index ${error.reason.index}: ${formatTypeError(error.reason.error)}`;
2504
+ }
2505
+ });
2506
+
2394
2507
  /**
2395
2508
  * Record of a key {@link Type} and value {@link Type}.
2396
2509
  *
@@ -3970,6 +4083,7 @@ export type TypeErrors<ExtraErrors extends TypeError = never> =
3970
4083
  | ExtraErrors
3971
4084
  // Composite errors
3972
4085
  | ArrayError<TypeErrors<ExtraErrors>>
4086
+ | SetError<TypeErrors<ExtraErrors>>
3973
4087
  | RecordError<TypeErrors<ExtraErrors>, TypeErrors<ExtraErrors>>
3974
4088
  | ObjectError<Record<string, TypeErrors<ExtraErrors>>>
3975
4089
  | ObjectWithRecordError<
@@ -4154,6 +4268,8 @@ export const createFormatTypeError = <ExtraErrors extends TypeError = never>(
4154
4268
  return formatSimplePasswordError(formatTypeError)(error);
4155
4269
  case "Array":
4156
4270
  return formatArrayError(formatTypeError)(error);
4271
+ case "Set":
4272
+ return formatSetError(formatTypeError)(error);
4157
4273
  case "Record":
4158
4274
  return formatRecordError(formatTypeError)(error);
4159
4275
  case "Object":
@@ -4202,6 +4318,18 @@ export const typeErrorToStandardSchemaIssues = <
4202
4318
  );
4203
4319
  }
4204
4320
 
4321
+ if (error.type === "Set") {
4322
+ const setError = error as SetError;
4323
+ if (setError.reason.kind === "NotSet") {
4324
+ return [{ message: formatTypeError(error), path }];
4325
+ }
4326
+ return typeErrorToStandardSchemaIssues(
4327
+ setError.reason.error as TypeErrors<ExtraErrors>,
4328
+ formatTypeError,
4329
+ [...path, setError.reason.index],
4330
+ );
4331
+ }
4332
+
4205
4333
  if (error.type === "Object") {
4206
4334
  const objectError = error as ObjectError;
4207
4335
  if (
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export * from "./Object.js";
18
18
  export * from "./Order.js";
19
19
  export * from "./Platform.js";
20
20
  export * from "./Random.js";
21
+ export * from "./Redacted.js";
21
22
  export * from "./Ref.js";
22
23
  export * from "./Relation.js";
23
24
  export * from "./Resources.js";