effect-app 4.0.0-beta.265 → 4.0.0-beta.267

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.
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import type * as Scope from "effect/Scope"
3
- import type { InvalidStateError, NotFoundError, OptimisticConcurrencyException } from "../../client/errors.js"
3
+ import type { DatabaseError, InvalidStateError, NotFoundError, OptimisticConcurrencyException } from "../../client/errors.js"
4
4
  import type * as Effect from "../../Effect.js"
5
5
  import type * as Option from "../../Option.js"
6
6
  import type * as S from "../../Schema.js"
@@ -49,32 +49,32 @@ export interface Repository<
49
49
  > {
50
50
  readonly itemType: ItemType
51
51
  readonly idKey: IdKey
52
- readonly find: (id: T[IdKey]) => Effect.Effect<Option.Option<T>, never, RSchema>
53
- readonly all: Effect.Effect<T[], never, RSchema>
52
+ readonly find: (id: T[IdKey]) => Effect.Effect<Option.Option<T>, DatabaseError, RSchema>
53
+ readonly all: Effect.Effect<T[], DatabaseError, RSchema>
54
54
  readonly saveAndPublish: (
55
55
  items: Iterable<T>,
56
56
  events?: Iterable<Evt>
57
- ) => Effect.Effect<void, InvalidStateError | OptimisticConcurrencyException, RSchema | RPublish>
57
+ ) => Effect.Effect<void, InvalidStateError | OptimisticConcurrencyException | DatabaseError, RSchema | RPublish>
58
58
  readonly changeFeed: ChangeFeed<T>
59
59
  readonly removeAndPublish: (
60
60
  items: Iterable<T>,
61
61
  events?: Iterable<Evt>
62
- ) => Effect.Effect<void, never, RSchema | RPublish>
62
+ ) => Effect.Effect<void, DatabaseError, RSchema | RPublish>
63
63
 
64
64
  readonly removeById: (
65
65
  idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>
66
- ) => Effect.Effect<void, never, RSchema>
66
+ ) => Effect.Effect<void, DatabaseError, RSchema>
67
67
 
68
68
  readonly queryRaw: <T, Out, R>(
69
69
  schema: S.Codec<T, Out, R>,
70
70
  raw: RawQuery<Encoded, Out>
71
- ) => Effect.Effect<readonly T[], S.SchemaError, R>
71
+ ) => Effect.Effect<readonly T[], S.SchemaError | DatabaseError, R>
72
72
 
73
73
  /**
74
74
  * Explicitly seed a namespace. Primary is seeded eagerly on initialization.
75
75
  * Non-primary namespaces must be seeded explicitly before use.
76
76
  */
77
- readonly seedNamespace: (namespace: string) => Effect.Effect<void>
77
+ readonly seedNamespace: (namespace: string) => Effect.Effect<void, DatabaseError>
78
78
 
79
79
  readonly query: {
80
80
  // ending with projection
@@ -91,7 +91,8 @@ export interface Repository<
91
91
  ): Effect.Effect<
92
92
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
93
93
  | (TType extends "many" ? never : NotFoundError<ItemType>)
94
- | (TType extends "count" ? never : S.SchemaError),
94
+ | (TType extends "count" ? never : S.SchemaError)
95
+ | DatabaseError,
95
96
  Exclude<R, RProvided> | RSchema
96
97
  >
97
98
  <
@@ -111,7 +112,8 @@ export interface Repository<
111
112
  ): Effect.Effect<
112
113
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
113
114
  | (TType extends "many" ? never : NotFoundError<ItemType>)
114
- | (TType extends "count" ? never : S.SchemaError),
115
+ | (TType extends "count" ? never : S.SchemaError)
116
+ | DatabaseError,
115
117
  Exclude<R, RProvided> | RSchema
116
118
  >
117
119
  <
@@ -133,7 +135,8 @@ export interface Repository<
133
135
  ): Effect.Effect<
134
136
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
135
137
  | (TType extends "many" ? never : NotFoundError<ItemType>)
136
- | (TType extends "count" ? never : S.SchemaError),
138
+ | (TType extends "count" ? never : S.SchemaError)
139
+ | DatabaseError,
137
140
  Exclude<R, RProvided> | RSchema
138
141
  >
139
142
  <
@@ -157,7 +160,8 @@ export interface Repository<
157
160
  ): Effect.Effect<
158
161
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
159
162
  | (TType extends "many" ? never : NotFoundError<ItemType>)
160
- | (TType extends "count" ? never : S.SchemaError),
163
+ | (TType extends "count" ? never : S.SchemaError)
164
+ | DatabaseError,
161
165
  Exclude<R, RProvided> | RSchema
162
166
  >
163
167
  <
@@ -183,7 +187,8 @@ export interface Repository<
183
187
  ): Effect.Effect<
184
188
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
185
189
  | (TType extends "many" ? never : NotFoundError<ItemType>)
186
- | (TType extends "count" ? never : S.SchemaError),
190
+ | (TType extends "count" ? never : S.SchemaError)
191
+ | DatabaseError,
187
192
  Exclude<R, RProvided> | RSchema
188
193
  >
189
194
  <
@@ -209,7 +214,8 @@ export interface Repository<
209
214
  ): Effect.Effect<
210
215
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
211
216
  | (TType extends "many" ? never : NotFoundError<ItemType>)
212
- | (TType extends "count" ? never : S.SchemaError),
217
+ | (TType extends "count" ? never : S.SchemaError)
218
+ | DatabaseError,
213
219
  Exclude<R, RProvided> | RSchema
214
220
  >
215
221
  <
@@ -237,7 +243,8 @@ export interface Repository<
237
243
  ): Effect.Effect<
238
244
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
239
245
  | (TType extends "many" ? never : NotFoundError<ItemType>)
240
- | (TType extends "count" ? never : S.SchemaError),
246
+ | (TType extends "count" ? never : S.SchemaError)
247
+ | DatabaseError,
241
248
  Exclude<R, RProvided> | RSchema
242
249
  >
243
250
  <
@@ -267,7 +274,8 @@ export interface Repository<
267
274
  ): Effect.Effect<
268
275
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
269
276
  | (TType extends "many" ? never : NotFoundError<ItemType>)
270
- | (TType extends "count" ? never : S.SchemaError),
277
+ | (TType extends "count" ? never : S.SchemaError)
278
+ | DatabaseError,
271
279
  Exclude<R, RProvided> | RSchema
272
280
  >
273
281
  <
@@ -299,7 +307,8 @@ export interface Repository<
299
307
  ): Effect.Effect<
300
308
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
301
309
  | (TType extends "many" ? never : NotFoundError<ItemType>)
302
- | (TType extends "count" ? never : S.SchemaError),
310
+ | (TType extends "count" ? never : S.SchemaError)
311
+ | DatabaseError,
303
312
  Exclude<R, RProvided> | RSchema
304
313
  >
305
314
  <
@@ -333,7 +342,8 @@ export interface Repository<
333
342
  ): Effect.Effect<
334
343
  TType extends "many" ? readonly A[] : TType extends "count" ? NonNegativeInt : A,
335
344
  | (TType extends "many" ? never : NotFoundError<ItemType>)
336
- | (TType extends "count" ? never : S.SchemaError),
345
+ | (TType extends "count" ? never : S.SchemaError)
346
+ | DatabaseError,
337
347
  Exclude<R, RProvided> | RSchema
338
348
  >
339
349
 
@@ -347,7 +357,7 @@ export interface Repository<
347
357
  q: (initial: Query<Encoded>) => QAll<Encoded, EncodedRefined, RefineTHelper<T, EncodedRefined>, R, TType, E>
348
358
  ): Effect.Effect<
349
359
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined> : RefineTHelper<T, EncodedRefined>,
350
- TType extends "many" ? never : NotFoundError<ItemType>,
360
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
351
361
  Exclude<R, RProvided> | RSchema
352
362
  >
353
363
  <
@@ -363,7 +373,7 @@ export interface Repository<
363
373
  ) => QAll<Encoded, EncodedRefined, RefineTHelper<T, EncodedRefined>, R, TType, E>
364
374
  ): Effect.Effect<
365
375
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined> : RefineTHelper<T, EncodedRefined>,
366
- TType extends "many" ? never : NotFoundError<ItemType>,
376
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
367
377
  Exclude<R, RProvided> | RSchema
368
378
  >
369
379
  <
@@ -383,7 +393,7 @@ export interface Repository<
383
393
  ) => QAll<Encoded, EncodedRefined, RefineTHelper<T, EncodedRefined>, R, TType, E>
384
394
  ): Effect.Effect<
385
395
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined> : RefineTHelper<T, EncodedRefined>,
386
- TType extends "many" ? never : NotFoundError<ItemType>,
396
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
387
397
  Exclude<R, RProvided> | RSchema
388
398
  >
389
399
  <
@@ -403,7 +413,7 @@ export interface Repository<
403
413
  ) => QAll<Encoded, EncodedRefined, RefineTHelper<T, EncodedRefined>, R, TType, E>
404
414
  ): Effect.Effect<
405
415
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined> : RefineTHelper<T, EncodedRefined>,
406
- TType extends "many" ? never : NotFoundError<ItemType>,
416
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
407
417
  Exclude<R, RProvided> | RSchema
408
418
  >
409
419
  <
@@ -425,7 +435,7 @@ export interface Repository<
425
435
  ) => QAll<Encoded, EncodedRefined, RefineTHelper<T, EncodedRefined>, R, TType, E>
426
436
  ): Effect.Effect<
427
437
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined> : RefineTHelper<T, EncodedRefined>,
428
- TType extends "many" ? never : NotFoundError<ItemType>,
438
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
429
439
  Exclude<R, RProvided> | RSchema
430
440
  >
431
441
  <
@@ -449,7 +459,7 @@ export interface Repository<
449
459
  ) => QAll<Encoded, EncodedRefined, RefineTHelper<T, EncodedRefined>, R, TType, E>
450
460
  ): Effect.Effect<
451
461
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined> : RefineTHelper<T, EncodedRefined>,
452
- TType extends "many" ? never : NotFoundError<ItemType>,
462
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
453
463
  Exclude<R, RProvided> | RSchema
454
464
  >
455
465
  <
@@ -475,7 +485,7 @@ export interface Repository<
475
485
  ) => QAll<Encoded, EncodedRefined, RefineTHelper<T, EncodedRefined>, R, TType, E>
476
486
  ): Effect.Effect<
477
487
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined> : RefineTHelper<T, EncodedRefined>,
478
- TType extends "many" ? never : NotFoundError<ItemType>,
488
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
479
489
  Exclude<R, RProvided> | RSchema
480
490
  >
481
491
  <
@@ -504,7 +514,7 @@ export interface Repository<
504
514
  ): Effect.Effect<
505
515
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined>
506
516
  : RefineTHelper<T, EncodedRefined>,
507
- TType extends "many" ? never : NotFoundError<ItemType>,
517
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
508
518
  Exclude<R, RProvided> | RSchema
509
519
  >
510
520
  <
@@ -535,7 +545,7 @@ export interface Repository<
535
545
  ): Effect.Effect<
536
546
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined>
537
547
  : RefineTHelper<T, EncodedRefined>,
538
- TType extends "many" ? never : NotFoundError<ItemType>,
548
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
539
549
  Exclude<R, RProvided> | RSchema
540
550
  >
541
551
  <
@@ -568,7 +578,7 @@ export interface Repository<
568
578
  ): Effect.Effect<
569
579
  TType extends "many" ? DistributeQueryIfExclusiveOnArray<E, T, EncodedRefined>
570
580
  : RefineTHelper<T, EncodedRefined>,
571
- TType extends "many" ? never : NotFoundError<ItemType>,
581
+ (TType extends "many" ? never : NotFoundError<ItemType>) | DatabaseError,
572
582
  Exclude<R, RProvided> | RSchema
573
583
  >
574
584
  }
@@ -585,7 +595,7 @@ export interface Repository<
585
595
  percentage?: number
586
596
  /** optional maximum number of items to sample */
587
597
  maxItems?: number
588
- }) => Effect.Effect<ValidationResult, never, RSchema>
598
+ }) => Effect.Effect<ValidationResult, DatabaseError, RSchema>
589
599
  }
590
600
 
591
601
  type DistributeQueryIfExclusiveOnArray<Exclusive extends boolean, T, EncodedRefined> = [Exclusive] extends [true]
@@ -7,27 +7,36 @@ import type { ArrayKey, IsTuple, TupleKeys } from "./common.js"
7
7
 
8
8
  /**
9
9
  * Helper type for recursively constructing paths through a type.
10
- * See {@link Path}
10
+ * `Seen` carries the set of object types already entered on this branch so
11
+ * recursion terminates on self-referential types (see {@link Path}).
11
12
  */
12
- type PathImpl<K extends string | number, V> = V extends
13
+ type PathImpl<K extends string | number, V, Seen> = V extends
13
14
  | Primitive
14
15
  | BrowserNativeObject ? `${K}`
15
- : `${K}` | `${K}.${Path<V>}`
16
+ // Stop before re-entering a type already on this branch. Recursive types
17
+ // (notably `Json`, the encoded form of `Schema.Defect()`:
18
+ // `JsonObject = { [x: string]: Json }`) would otherwise descend forever and
19
+ // blow past TS's instantiation limit => TS2589. The key itself stays a valid
20
+ // leaf path; only the unbounded descent is cut.
21
+ : [V] extends [Seen] ? `${K}`
22
+ : `${K}` | `${K}.${Path<V, Seen | V>}`
16
23
 
17
24
  /**
18
25
  * Type which eagerly collects all paths through a type
19
- * @typeParam T - type which should be introspected
26
+ * @typeParam T - type which should be introspected
27
+ * @typeParam Seen - object types already entered on this recursion branch;
28
+ * used to terminate on self-referential types
20
29
  * @example
21
30
  * ```
22
31
  * Path<{foo: {bar: string}}> = 'foo' | 'foo.bar'
23
32
  * ```
24
33
  */
25
- export type Path<T> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true ? {
26
- [K in TupleKeys<T>]-?: PathImpl<K & string, T[K]> | "length"
34
+ export type Path<T, Seen = never> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true ? {
35
+ [K in TupleKeys<T>]-?: PathImpl<K & string, T[K], Seen> | "length"
27
36
  }[TupleKeys<T>]
28
- : PathImpl<ArrayKey, V> | "length"
37
+ : PathImpl<ArrayKey, V, Seen | T> | "length"
29
38
  : {
30
- [K in keyof T]-?: PathImpl<K & string, T[K]>
39
+ [K in keyof T]-?: PathImpl<K & string, T[K], Seen | T>
31
40
  }[keyof T]
32
41
 
33
42
  /**
@@ -35,6 +44,27 @@ export type Path<T> = T extends ReadonlyArray<infer V> ? IsTuple<T> extends true
35
44
  */
36
45
  export type FieldPath<TFieldValues extends FieldValues> = Path<TFieldValues>
37
46
 
47
+ export namespace PathRecursionTests {
48
+ // Regression: self-referential types must terminate instead of blowing past
49
+ // TS's instantiation limit (TS2589). `JsonLike` mirrors effect's `Json` — the
50
+ // encoded form of `Schema.Defect()` — whose `{ [x: string]: Json }` index
51
+ // signature previously made `Path` recurse forever. These aliases failing to
52
+ // compile (rather than resolving to a string union) signals the regression.
53
+ type JsonLike =
54
+ | string
55
+ | number
56
+ | boolean
57
+ | null
58
+ | ReadonlyArray<JsonLike>
59
+ | { readonly [x: string]: JsonLike }
60
+ export type _RawDefect = Path<{ id: string; raw: JsonLike }>
61
+ export type _NestedDefect = Path<{ a: { b: { error: { raw: JsonLike } } } }>
62
+ export type _ArrayOfDefect = Path<{ items: ReadonlyArray<{ raw: JsonLike }> }>
63
+ // finite paths are still produced
64
+ expectTypeOf<"id">().toExtend<_RawDefect>()
65
+ expectTypeOf<"raw">().toExtend<_RawDefect>()
66
+ }
67
+
38
68
  /**
39
69
  * Type to evaluate the type which the given path points to.
40
70
  * @typeParam T - deeply nested type which is indexed by the path
package/src/Store.ts CHANGED
@@ -3,7 +3,7 @@ import * as Data from "effect/Data"
3
3
  import type * as Redacted from "effect/Redacted"
4
4
  import * as Semaphore from "effect/Semaphore"
5
5
  import type { NonEmptyReadonlyArray } from "./Array.js"
6
- import type { OptimisticConcurrencyException } from "./client/errors.js"
6
+ import type { DatabaseError, OptimisticConcurrencyException } from "./client/errors.js"
7
7
  import * as Context from "./Context.js"
8
8
  import * as Effect from "./Effect.js"
9
9
  import * as Layer from "./Layer.js"
@@ -99,30 +99,30 @@ export interface FilterArgs<Encoded extends FieldValues, U extends keyof Encoded
99
99
 
100
100
  export type FilterFunc<Encoded extends FieldValues> = <U extends keyof Encoded = never>(
101
101
  args: FilterArgs<Encoded, U>
102
- ) => Effect.Effect<(U extends undefined ? Encoded : Pick<Encoded, U>)[]>
102
+ ) => Effect.Effect<(U extends undefined ? Encoded : Pick<Encoded, U>)[], DatabaseError>
103
103
 
104
104
  export interface Store<
105
105
  IdKey extends keyof Encoded,
106
106
  Encoded extends FieldValues,
107
107
  PM extends PersistenceModelType<Encoded> = PersistenceModelType<Encoded>
108
108
  > {
109
- all: Effect.Effect<PM[]>
109
+ all: Effect.Effect<PM[], DatabaseError>
110
110
  filter: FilterFunc<Encoded>
111
- find: (id: Encoded[IdKey]) => Effect.Effect<Option.Option<PM>>
112
- set: (e: PM) => Effect.Effect<PM, OptimisticConcurrencyException>
111
+ find: (id: Encoded[IdKey]) => Effect.Effect<Option.Option<PM>, DatabaseError>
112
+ set: (e: PM) => Effect.Effect<PM, OptimisticConcurrencyException | DatabaseError>
113
113
  batchSet: (
114
114
  items: NonEmptyReadonlyArray<PM>
115
- ) => Effect.Effect<NonEmptyReadonlyArray<PM>, OptimisticConcurrencyException>
115
+ ) => Effect.Effect<NonEmptyReadonlyArray<PM>, OptimisticConcurrencyException | DatabaseError>
116
116
  bulkSet: (
117
117
  items: NonEmptyReadonlyArray<PM>
118
- ) => Effect.Effect<NonEmptyReadonlyArray<PM>, OptimisticConcurrencyException>
119
- batchRemove: (ids: NonEmptyReadonlyArray<Encoded[IdKey]>, partitionKey?: string) => Effect.Effect<void>
120
- queryRaw: <Out>(query: RawQuery<Encoded, Out>) => Effect.Effect<readonly Out[]>
118
+ ) => Effect.Effect<NonEmptyReadonlyArray<PM>, OptimisticConcurrencyException | DatabaseError>
119
+ batchRemove: (ids: NonEmptyReadonlyArray<Encoded[IdKey]>, partitionKey?: string) => Effect.Effect<void, DatabaseError>
120
+ queryRaw: <Out>(query: RawQuery<Encoded, Out>) => Effect.Effect<readonly Out[], DatabaseError>
121
121
  /**
122
122
  * Explicitly seed a namespace. Primary is seeded eagerly on initialization.
123
123
  * Non-primary namespaces must be seeded explicitly before use.
124
124
  */
125
- seedNamespace: (namespace: string) => Effect.Effect<void>
125
+ seedNamespace: (namespace: string) => Effect.Effect<void, DatabaseError>
126
126
  }
127
127
 
128
128
  export class StoreMaker extends Context.Opaque<StoreMaker, {
@@ -157,6 +157,45 @@ export class OptimisticConcurrencyException extends TaggedErrorClass<OptimisticC
157
157
  }
158
158
  }
159
159
 
160
+ /**
161
+ * Raised by a store adapter when a database operation fails for an
162
+ * infrastructure reason (request timeout, throttle, 5xx, dropped socket, or any
163
+ * non-conflict error). Distinct from `OptimisticConcurrencyException`, which is
164
+ * an expected etag conflict.
165
+ *
166
+ * `transient` is set by the adapter for failures worth retrying (timeout /
167
+ * throttle / 5xx / network). `cause` is the underlying adapter error, carried
168
+ * through a `Defect` schema so it serializes (an `Error` encodes to
169
+ * `{ name, message, cause }`) instead of breaking JSON encoding of the channel.
170
+ *
171
+ * Treated by the api/client/FE machinery like an unexpected (500-class) error.
172
+ */
173
+ export class DatabaseError extends TaggedErrorClass<DatabaseError>()(
174
+ "DatabaseError",
175
+ {
176
+ message: S.String,
177
+ transient: S.Boolean,
178
+ cause: S.optional(S.Defect())
179
+ }
180
+ ) {
181
+ constructor(
182
+ args: { message?: string; transient?: boolean; cause?: unknown },
183
+ disableValidation?: boolean
184
+ ) {
185
+ super(
186
+ {
187
+ message: args.message ?? "Database operation failed",
188
+ transient: args.transient ?? false,
189
+ cause: args.cause
190
+ },
191
+ disableValidation as any
192
+ )
193
+ }
194
+ override toString() {
195
+ return `DatabaseError: ${this.message}`
196
+ }
197
+ }
198
+
160
199
  const MutationOnlyErrors = [
161
200
  InvalidStateError,
162
201
  OptimisticConcurrencyException
@@ -168,7 +207,8 @@ const GeneralErrors = [
168
207
  LoginError,
169
208
  UnauthorizedError,
170
209
  ValidationError,
171
- ServiceUnavailableError
210
+ ServiceUnavailableError,
211
+ DatabaseError
172
212
  ] as const
173
213
 
174
214
  export const SupportedErrors = S.Union([