effect-app 4.0.0-beta.247 → 4.0.0-beta.249

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 (140) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/Emailer.d.ts +51 -0
  3. package/dist/Emailer.d.ts.map +1 -0
  4. package/dist/Emailer.js +7 -0
  5. package/dist/Model/Repository/Registry.d.ts +21 -0
  6. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  7. package/dist/Model/Repository/Registry.js +18 -0
  8. package/dist/Model/Repository/ext.d.ts +60 -0
  9. package/dist/Model/Repository/ext.d.ts.map +1 -0
  10. package/dist/Model/Repository/ext.js +122 -0
  11. package/dist/Model/Repository/internal/internal.d.ts +62 -0
  12. package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
  13. package/dist/Model/Repository/internal/internal.js +413 -0
  14. package/dist/Model/Repository/legacy.d.ts +21 -0
  15. package/dist/Model/Repository/legacy.d.ts.map +1 -0
  16. package/dist/Model/Repository/legacy.js +2 -0
  17. package/dist/Model/Repository/makeRepo.d.ts +53 -0
  18. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  19. package/dist/Model/Repository/makeRepo.js +27 -0
  20. package/dist/Model/Repository/service.d.ts +97 -0
  21. package/dist/Model/Repository/service.d.ts.map +1 -0
  22. package/dist/Model/Repository/service.js +2 -0
  23. package/dist/Model/Repository/validation.d.ts +71 -0
  24. package/dist/Model/Repository/validation.d.ts.map +1 -0
  25. package/dist/Model/Repository/validation.js +32 -0
  26. package/dist/Model/Repository.d.ts +7 -0
  27. package/dist/Model/Repository.d.ts.map +1 -0
  28. package/dist/Model/Repository.js +7 -0
  29. package/dist/Model/dsl.d.ts +33 -0
  30. package/dist/Model/dsl.d.ts.map +1 -0
  31. package/dist/Model/dsl.js +43 -0
  32. package/dist/Model/filter/filterApi.d.ts +30 -0
  33. package/dist/Model/filter/filterApi.d.ts.map +1 -0
  34. package/dist/Model/filter/filterApi.js +2 -0
  35. package/dist/Model/filter/types/errors.d.ts +29 -0
  36. package/dist/Model/filter/types/errors.d.ts.map +1 -0
  37. package/dist/Model/filter/types/errors.js +2 -0
  38. package/dist/Model/filter/types/fields.d.ts +15 -0
  39. package/dist/Model/filter/types/fields.d.ts.map +1 -0
  40. package/dist/Model/filter/types/fields.js +2 -0
  41. package/dist/Model/filter/types/path/common.d.ts +316 -0
  42. package/dist/Model/filter/types/path/common.d.ts.map +1 -0
  43. package/dist/Model/filter/types/path/common.js +2 -0
  44. package/dist/Model/filter/types/path/eager.d.ts +95 -0
  45. package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
  46. package/dist/Model/filter/types/path/eager.js +31 -0
  47. package/dist/Model/filter/types/path/index.d.ts +4 -0
  48. package/dist/Model/filter/types/path/index.d.ts.map +1 -0
  49. package/dist/Model/filter/types/path/index.js +3 -0
  50. package/dist/Model/filter/types/utils.d.ts +79 -0
  51. package/dist/Model/filter/types/utils.d.ts.map +1 -0
  52. package/dist/Model/filter/types/utils.js +2 -0
  53. package/dist/Model/filter/types/validator.d.ts +30 -0
  54. package/dist/Model/filter/types/validator.d.ts.map +1 -0
  55. package/dist/Model/filter/types/validator.js +2 -0
  56. package/dist/Model/filter/types.d.ts +5 -0
  57. package/dist/Model/filter/types.d.ts.map +1 -0
  58. package/dist/Model/filter/types.js +7 -0
  59. package/dist/Model/query/dsl.d.ts +446 -0
  60. package/dist/Model/query/dsl.d.ts.map +1 -0
  61. package/dist/Model/query/dsl.js +342 -0
  62. package/dist/Model/query/new-kid-interpreter.d.ts +136 -0
  63. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
  64. package/dist/Model/query/new-kid-interpreter.js +336 -0
  65. package/dist/Model/query.d.ts +15 -0
  66. package/dist/Model/query.d.ts.map +1 -0
  67. package/dist/Model/query.js +3 -0
  68. package/dist/Model.d.ts +5 -0
  69. package/dist/Model.d.ts.map +1 -0
  70. package/dist/Model.js +5 -0
  71. package/dist/QueueMaker.d.ts +13 -0
  72. package/dist/QueueMaker.d.ts.map +1 -0
  73. package/dist/QueueMaker.js +4 -0
  74. package/dist/RequestContext.d.ts +103 -0
  75. package/dist/RequestContext.d.ts.map +1 -0
  76. package/dist/RequestContext.js +49 -0
  77. package/dist/Schema/ext.d.ts +9 -9
  78. package/dist/Schema/ext.d.ts.map +1 -1
  79. package/dist/Schema/ext.js +1 -1
  80. package/dist/Store.d.ts +147 -0
  81. package/dist/Store.d.ts.map +1 -0
  82. package/dist/Store.js +95 -0
  83. package/dist/client/apiClientFactory.d.ts +1 -1
  84. package/dist/client/clientFor.d.ts +5 -5
  85. package/dist/client/clientFor.d.ts.map +1 -1
  86. package/dist/client/makeClient.d.ts +36 -36
  87. package/dist/client/makeClient.d.ts.map +1 -1
  88. package/dist/index.d.ts +3 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +3 -1
  91. package/dist/rpc/MiddlewareMaker.d.ts +2 -2
  92. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  93. package/dist/rpc/RpcMiddleware.d.ts +2 -2
  94. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  95. package/dist/runtime.d.ts +19 -0
  96. package/dist/runtime.d.ts.map +1 -0
  97. package/dist/runtime.js +40 -0
  98. package/dist/toast.d.ts +51 -0
  99. package/dist/toast.d.ts.map +1 -0
  100. package/dist/toast.js +34 -0
  101. package/dist/withToast.d.ts +30 -0
  102. package/dist/withToast.d.ts.map +1 -0
  103. package/dist/withToast.js +64 -0
  104. package/package.json +113 -1
  105. package/src/Emailer.ts +51 -0
  106. package/src/Model/Repository/Registry.ts +34 -0
  107. package/src/Model/Repository/ext.ts +375 -0
  108. package/src/Model/Repository/internal/internal.ts +708 -0
  109. package/src/Model/Repository/legacy.ts +29 -0
  110. package/src/Model/Repository/makeRepo.ts +144 -0
  111. package/src/Model/Repository/service.ts +639 -0
  112. package/src/Model/Repository/validation.ts +31 -0
  113. package/src/Model/Repository.ts +6 -0
  114. package/src/Model/dsl.ts +129 -0
  115. package/src/Model/filter/filterApi.ts +60 -0
  116. package/src/Model/filter/types/errors.ts +47 -0
  117. package/src/Model/filter/types/fields.ts +50 -0
  118. package/src/Model/filter/types/path/common.ts +404 -0
  119. package/src/Model/filter/types/path/eager.ts +297 -0
  120. package/src/Model/filter/types/path/index.ts +4 -0
  121. package/src/Model/filter/types/utils.ts +128 -0
  122. package/src/Model/filter/types/validator.ts +46 -0
  123. package/src/Model/filter/types.ts +6 -0
  124. package/src/Model/query/dsl.ts +2546 -0
  125. package/src/Model/query/new-kid-interpreter.ts +484 -0
  126. package/src/Model/query.ts +13 -0
  127. package/src/Model.ts +4 -0
  128. package/src/QueueMaker.ts +19 -0
  129. package/src/RequestContext.ts +62 -0
  130. package/src/Schema/ext.ts +6 -6
  131. package/src/Store.ts +243 -0
  132. package/src/client/clientFor.ts +8 -8
  133. package/src/client/makeClient.ts +11 -11
  134. package/src/index.ts +2 -0
  135. package/src/rpc/MiddlewareMaker.ts +1 -1
  136. package/src/rpc/RpcMiddleware.ts +1 -1
  137. package/src/runtime.ts +56 -0
  138. package/src/toast.ts +54 -0
  139. package/src/withToast.ts +133 -0
  140. package/test/dist/rpc-dynamic-middleware.test.d.ts.map +1 -0
package/src/Store.ts ADDED
@@ -0,0 +1,243 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type * as Redacted from "effect/Redacted"
3
+ import * as Semaphore from "effect/Semaphore"
4
+ import type { NonEmptyReadonlyArray } from "./Array.js"
5
+ import type { OptimisticConcurrencyException } from "./client/errors.js"
6
+ import * as Context from "./Context.js"
7
+ import * as Effect from "./Effect.js"
8
+ import type { FilterResult } from "./Model/filter/filterApi.js"
9
+ import type { FieldValues } from "./Model/filter/types.js"
10
+ import type { FieldPath } from "./Model/filter/types/path/index.js"
11
+ import type { AggregateIrExpression, ComputedProjectionIrExpression, RawQuery } from "./Model/query.js"
12
+ import type * as Option from "./Option.js"
13
+
14
+ /**
15
+ * Adapter-neutral unique-key definition for stores that support unique indexes,
16
+ * such as the Cosmos adapter. This shape is intentionally kept structurally
17
+ * compatible with adapter-specific `UniqueKey` types. Each path identifies a
18
+ * field participating in the unique key, and adapters forward these paths
19
+ * directly to the underlying storage engine.
20
+ */
21
+ export interface UniqueKey {
22
+ readonly paths: string[]
23
+ }
24
+
25
+ export interface StoreConfig<E> {
26
+ partitionValue: (e?: E) => string
27
+ /**
28
+ * Primarily used for testing, creating namespaces in the database to separate data e.g to run multiple tests in isolation within the same database.
29
+ * Memory/Disk use separate store instances per namespace. CosmosDB uses namespace-prefixed partition keys. SQL uses a `_namespace` column.
30
+ */
31
+ allowNamespace?: (namespace: string) => boolean
32
+ /**
33
+ * just in time migrations, supported by the database driver, supporting queries, for simple default values
34
+ */
35
+ defaultValues?: Partial<E>
36
+
37
+ /**
38
+ * How many items can be processed in one batch at a time.
39
+ * Defaults to 100 for CosmosDB.
40
+ */
41
+ maxBulkSize?: number
42
+
43
+ /**
44
+ * Unique indexes, mainly for CosmosDB
45
+ */
46
+ uniqueKeys?: UniqueKey[]
47
+ }
48
+
49
+ export type SupportedValues = string | boolean | number | null
50
+ export type SupportedValues2 = string | boolean | number
51
+
52
+ // default is eq
53
+ export type Where =
54
+ | { key: string; t?: "eq" | "not-eq"; value: SupportedValues }
55
+ | { key: string; t: "gt" | "lt" | "gte" | "lte"; value: SupportedValues2 }
56
+ | {
57
+ key: string
58
+ t: "contains" | "starts-with" | "ends-with" | "not-contains" | "not-starts-with" | "not-ends-with"
59
+ value: string
60
+ }
61
+ | { key: string; t: "includes" | "not-includes"; value: string }
62
+ | {
63
+ key: string
64
+ t: "in" | "not-in"
65
+ value: readonly (SupportedValues)[]
66
+ }
67
+
68
+ export type Filter = readonly FilterResult[]
69
+
70
+ export interface O<TFieldValues extends FieldValues> {
71
+ key: FieldPath<TFieldValues>
72
+ direction: "ASC" | "DESC"
73
+ }
74
+
75
+ export interface FilterArgs<Encoded extends FieldValues, U extends keyof Encoded = never> {
76
+ t: Encoded
77
+ filter?: Filter | undefined
78
+ select?:
79
+ | NonEmptyReadonlyArray<
80
+ U | { key: string; subKeys: readonly string[] } | {
81
+ key: string
82
+ computed: ComputedProjectionIrExpression
83
+ } | {
84
+ key: string
85
+ path: string
86
+ } | {
87
+ key: string
88
+ aggregate: AggregateIrExpression
89
+ }
90
+ >
91
+ | undefined
92
+ order?: NonEmptyReadonlyArray<O<NoInfer<Encoded>>>
93
+ limit?: number | undefined
94
+ skip?: number | undefined
95
+ }
96
+
97
+ export type FilterFunc<Encoded extends FieldValues> = <U extends keyof Encoded = never>(
98
+ args: FilterArgs<Encoded, U>
99
+ ) => Effect.Effect<(U extends undefined ? Encoded : Pick<Encoded, U>)[]>
100
+
101
+ export interface Store<
102
+ IdKey extends keyof Encoded,
103
+ Encoded extends FieldValues,
104
+ PM extends PersistenceModelType<Encoded> = PersistenceModelType<Encoded>
105
+ > {
106
+ all: Effect.Effect<PM[]>
107
+ filter: FilterFunc<Encoded>
108
+ find: (id: Encoded[IdKey]) => Effect.Effect<Option.Option<PM>>
109
+ set: (e: PM) => Effect.Effect<PM, OptimisticConcurrencyException>
110
+ batchSet: (
111
+ items: NonEmptyReadonlyArray<PM>
112
+ ) => Effect.Effect<NonEmptyReadonlyArray<PM>, OptimisticConcurrencyException>
113
+ bulkSet: (
114
+ items: NonEmptyReadonlyArray<PM>
115
+ ) => Effect.Effect<NonEmptyReadonlyArray<PM>, OptimisticConcurrencyException>
116
+ batchRemove: (ids: NonEmptyReadonlyArray<Encoded[IdKey]>, partitionKey?: string) => Effect.Effect<void>
117
+ queryRaw: <Out>(query: RawQuery<Encoded, Out>) => Effect.Effect<readonly Out[]>
118
+ /**
119
+ * Explicitly seed a namespace. Primary is seeded eagerly on initialization.
120
+ * Non-primary namespaces must be seeded explicitly before use.
121
+ */
122
+ seedNamespace: (namespace: string) => Effect.Effect<void>
123
+ }
124
+
125
+ export class StoreMaker extends Context.Opaque<StoreMaker, {
126
+ make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
127
+ name: string,
128
+ idKey: IdKey,
129
+ seed?: Effect.Effect<Iterable<Encoded>, E, R>,
130
+ config?: StoreConfig<Encoded>
131
+ ) => Effect.Effect<Store<IdKey, Encoded>, E, R>
132
+ }>()("effect-app/StoreMaker") {
133
+ }
134
+
135
+ export const makeContextMap = () => {
136
+ const etags = new Map<string, string>()
137
+ const getEtag = (id: string) => etags.get(id)
138
+ const setEtag = (id: string, eTag: string | undefined) => {
139
+ if (eTag === undefined) {
140
+ etags.delete(id)
141
+ } else {
142
+ etags.set(id, eTag)
143
+ }
144
+ }
145
+
146
+ // const parsedCache = new Map<
147
+ // Parser<any, any, any>,
148
+ // Map<unknown, These.These<unknown, unknown>>
149
+ // >()
150
+
151
+ // const parserCache = new Map<
152
+ // Parser<any, any, any>,
153
+ // (i: any) => These.These<any, any>
154
+ // >()
155
+
156
+ // const setAndReturn = <I, E, A>(
157
+ // p: Parser<I, E, A>,
158
+ // np: (i: I) => These.These<E, A>
159
+ // ) => {
160
+ // parserCache.set(p, np)
161
+ // return np
162
+ // }
163
+
164
+ // const parserEnv: ParserEnv = {
165
+ // // TODO: lax: true would turn off refinement checks, may help on large payloads
166
+ // // but of course removes confirming of validation rules (which may be okay for a database owned by the app, as we write safely)
167
+ // lax: false,
168
+ // cache: {
169
+ // getOrSetParser: (p) => parserCache.get(p) ?? setAndReturn(p, (i) => parserEnv.cache!.getOrSet(i, p)),
170
+ // getOrSetParsers: (parsers) => {
171
+ // return Object.entries(parsers).reduce((prev, [k, v]) => {
172
+ // prev[k] = parserEnv.cache!.getOrSetParser(v)
173
+ // return prev
174
+ // }, {} as any)
175
+ // },
176
+ // getOrSet: (i, parse): any => {
177
+ // const c = parsedCache.get(parse)
178
+ // if (c) {
179
+ // const f = c.get(i)
180
+ // if (f) {
181
+ // // console.log("$$$ cache hit", i)
182
+ // return f
183
+ // } else {
184
+ // const nf = parse(i, parserEnv)
185
+ // c.set(i, nf)
186
+ // return nf
187
+ // }
188
+ // } else {
189
+ // const nf = parse(i, parserEnv)
190
+ // parsedCache.set(parse, new Map([[i, nf]]))
191
+ // return nf
192
+ // }
193
+ // }
194
+ // }
195
+ // }
196
+
197
+ const store = new Map<symbol, unknown>()
198
+ const sem = Semaphore.makeUnsafe(1)
199
+
200
+ return {
201
+ createdAt: new Date(),
202
+ get: getEtag,
203
+ set: setEtag,
204
+ getOrCreateStore: <T>(key: symbol, make: () => T): T => {
205
+ let value = store.get(key) as T | undefined
206
+ if (value === undefined) {
207
+ value = make()
208
+ store.set(key, value)
209
+ }
210
+ return value
211
+ },
212
+ getOrCreateStoreEffect: <T, E, R>(key: symbol, make: Effect.Effect<T, E, R>): Effect.Effect<T, E, R> =>
213
+ sem.withPermits(1)(Effect.uninterruptible(Effect.gen(function*() {
214
+ const value = store.get(key) as T | undefined
215
+ if (value !== undefined) return value
216
+ const v = yield* make
217
+ store.set(key, v)
218
+ return v
219
+ }))),
220
+ clear: () => {
221
+ etags.clear()
222
+ store.clear()
223
+ }
224
+ }
225
+ }
226
+
227
+ const makeMap = Effect.acquireRelease(
228
+ Effect.sync(() => makeContextMap()),
229
+ (m) => Effect.sync(() => m.clear())
230
+ )
231
+
232
+ export class ContextMap extends Context.Opaque<ContextMap>()("effect-app/ContextMap", { make: makeMap }) {
233
+ }
234
+
235
+ export type PersistenceModelType<Encoded extends object> = Encoded & {
236
+ _etag?: string | undefined
237
+ }
238
+
239
+ export interface StorageConfig {
240
+ url: Redacted.Redacted
241
+ prefix: string
242
+ dbName: string
243
+ }
@@ -63,11 +63,11 @@ export type Client<M extends RequestsAny, ModuleName extends string> = RequestHa
63
63
  ModuleName
64
64
  >
65
65
 
66
- export type ExtractResponse<T> = T extends S.Codec<any> ? S.Schema.Type<T>
66
+ export type ExtractResponse<T> = T extends S.Codec<any> ? T["Type"]
67
67
  : T extends unknown ? void
68
68
  : never
69
69
 
70
- export type ExtractEResponse<T> = T extends S.Codec<any> ? S.Codec.Encoded<T>
70
+ export type ExtractEResponse<T> = T extends S.Codec<any> ? T["Encoded"]
71
71
  : T extends unknown ? void
72
72
  : never
73
73
 
@@ -150,8 +150,8 @@ export type HandlerInput<I extends { readonly make: (...args: any[]) => any }> =
150
150
  : RequestInput<I>
151
151
 
152
152
  /** Extracts the final-value type from a stream request. Defaults to the success type when no `final` schema is set. */
153
- type FinalTypeOf<T extends Req> = T extends { readonly final: infer F extends S.Top } ? S.Schema.Type<F>
154
- : S.Schema.Type<T["success"]>
153
+ type FinalTypeOf<T extends Req> = T extends { readonly final: infer F extends S.Top } ? F["Type"]
154
+ : T["success"]["Type"]
155
155
 
156
156
  // `T["success"]` / `T["error"]` are constrained to `S.Top` via `Req`, so we
157
157
  // can read `["DecodingServices"]` directly. Avoids the conditional in
@@ -161,8 +161,8 @@ type FinalTypeOf<T extends Req> = T extends { readonly final: infer F extends S.
161
161
  type RequestHandlerFor<R, E, T extends Req, Id extends string> = T["stream"] extends true
162
162
  ? RequestStreamHandlerWithInput<
163
163
  HandlerInput<T>,
164
- S.Schema.Type<T["success"]>,
165
- S.Schema.Type<T["error"]> | E,
164
+ T["success"]["Type"],
165
+ T["error"]["Type"] | E,
166
166
  R | T["success"]["DecodingServices"] | T["error"]["DecodingServices"],
167
167
  T,
168
168
  Id,
@@ -170,8 +170,8 @@ type RequestHandlerFor<R, E, T extends Req, Id extends string> = T["stream"] ext
170
170
  >
171
171
  : RequestHandlerWithInput<
172
172
  HandlerInput<T>,
173
- S.Schema.Type<T["success"]>,
174
- S.Schema.Type<T["error"]> | E,
173
+ T["success"]["Type"],
174
+ T["error"]["Type"] | E,
175
175
  R | T["success"]["DecodingServices"] | T["error"]["DecodingServices"],
176
176
  T,
177
177
  Id
@@ -44,9 +44,9 @@ type QueryOnlyResources<Resources> = {
44
44
  }
45
45
 
46
46
  type InputFromPayload<Payload extends S.Struct.Fields> = keyof Payload extends never ? void
47
- : S.Schema.Type<S.Struct<Payload>>
47
+ : S.Struct<Payload>["Type"]
48
48
 
49
- type OutputFromSuccess<Success extends S.Top> = Success extends typeof ForceVoid ? void : S.Schema.Type<Success>
49
+ type OutputFromSuccess<Success extends S.Top> = Success extends typeof ForceVoid ? void : Success["Type"]
50
50
 
51
51
  type InvalidationResources = Record<string, Record<string, unknown>>
52
52
 
@@ -95,7 +95,7 @@ type InvalidationConfigForCommand<
95
95
  Resources,
96
96
  InputFromPayload<Payload>,
97
97
  OutputFromSuccess<Success>,
98
- S.Schema.Type<Error>
98
+ Error["Type"]
99
99
  >
100
100
 
101
101
  export const configureInvalidation = <Resources>() =>
@@ -225,7 +225,7 @@ export const makeRpcClient = <
225
225
  Resources,
226
226
  InputFromPayload<Payload>,
227
227
  OutputFromSuccess<SchemaOrFields<Success>>,
228
- S.Schema.Type<ErrorResult<C & { success: Success; error: Error }>>
228
+ ErrorResult<C & { success: Success; error: Error }>["Type"]
229
229
  >
230
230
  ): TaggedRequestForResult<
231
231
  Self,
@@ -275,7 +275,7 @@ export const makeRpcClient = <
275
275
  Resources,
276
276
  InputFromPayload<Payload>,
277
277
  OutputFromSuccess<SchemaOrFields<Success>>,
278
- S.Schema.Type<ErrorResult<C & { success: Success }>>
278
+ ErrorResult<C & { success: Success }>["Type"]
279
279
  >
280
280
  ): TaggedRequestForResult<
281
281
  Self,
@@ -322,7 +322,7 @@ export const makeRpcClient = <
322
322
  Resources,
323
323
  InputFromPayload<Payload>,
324
324
  void,
325
- S.Schema.Type<ErrorResult<C & { error: Error }>>
325
+ ErrorResult<C & { error: Error }>["Type"]
326
326
  >
327
327
  ): TaggedRequestForResult<
328
328
  Self,
@@ -367,7 +367,7 @@ export const makeRpcClient = <
367
367
  Resources,
368
368
  InputFromPayload<Payload>,
369
369
  void,
370
- S.Schema.Type<ErrorResult<C>>
370
+ ErrorResult<C>["Type"]
371
371
  >
372
372
  ): TaggedRequestForResult<
373
373
  Self,
@@ -405,7 +405,7 @@ export const makeRpcClient = <
405
405
  Resources,
406
406
  InputFromPayload<Payload>,
407
407
  OutputFromSuccess<SchemaOrFields<Success>>,
408
- S.Schema.Type<ErrorResult<C & { success: Success; error: Error }>>
408
+ ErrorResult<C & { success: Success; error: Error }>["Type"]
409
409
  >
410
410
  ): TaggedRequestForResult<
411
411
  Self,
@@ -457,7 +457,7 @@ export const makeRpcClient = <
457
457
  Resources,
458
458
  InputFromPayload<Payload>,
459
459
  OutputFromSuccess<SchemaOrFields<Success>>,
460
- S.Schema.Type<ErrorResult<C & { success: Success }>>
460
+ ErrorResult<C & { success: Success }>["Type"]
461
461
  >
462
462
  ): TaggedRequestForResult<
463
463
  Self,
@@ -502,7 +502,7 @@ export const makeRpcClient = <
502
502
  Resources,
503
503
  InputFromPayload<Payload>,
504
504
  void,
505
- S.Schema.Type<ErrorResult<C & { error: Error }>>
505
+ ErrorResult<C & { error: Error }>["Type"]
506
506
  >
507
507
  ): TaggedRequestForResult<
508
508
  Self,
@@ -544,7 +544,7 @@ export const makeRpcClient = <
544
544
  Resources,
545
545
  InputFromPayload<Payload>,
546
546
  void,
547
- S.Schema.Type<ErrorResult<C>>
547
+ ErrorResult<C>["Type"]
548
548
  >
549
549
  ): TaggedRequestForResult<
550
550
  Self,
package/src/index.ts CHANGED
@@ -10,8 +10,10 @@ export * as ConfigProvider from "./ConfigProvider.js"
10
10
  export * as Context from "./Context.js"
11
11
  export * as Effect from "./Effect.js"
12
12
  export * as Layer from "./Layer.js"
13
+ export * as Model from "./Model.js"
13
14
  export * as NonEmptySet from "./NonEmptySet.js"
14
15
  export * as Set from "./Set.js"
16
+ export * as Store from "./Store.js"
15
17
 
16
18
  export { type NonEmptyArray, type NonEmptyReadonlyArray } from "./Array.js"
17
19
 
@@ -94,7 +94,7 @@ export namespace MiddlewareMaker {
94
94
  : never
95
95
  : never
96
96
 
97
- export type Errors<T> = T extends TagClassAny ? T extends { error: S.Top } ? S.Schema.Type<T["error"]>
97
+ export type Errors<T> = T extends TagClassAny ? T extends { error: S.Top } ? T["error"]["Type"]
98
98
  : never
99
99
  : never
100
100
 
@@ -65,7 +65,7 @@ export declare namespace TagClass {
65
65
  */
66
66
  export type Failure<Options> = Options extends { readonly error: S.Codec<infer _A>; readonly optional?: false } ? _A
67
67
  // actually not, the Failure depends on Dynamic Middleware Configuration!
68
- : Options extends { readonly dynamic: RpcDynamic<any, infer A> } ? S.Schema.Type<A["error"]>
68
+ : Options extends { readonly dynamic: RpcDynamic<any, infer A> } ? A["error"]["Type"]
69
69
  : never
70
70
 
71
71
  /**
package/src/runtime.ts ADDED
@@ -0,0 +1,56 @@
1
+ import * as Exit from "effect/Exit"
2
+ import { flow } from "effect/Function"
3
+ import * as Logger from "effect/Logger"
4
+ import * as ManagedRuntime from "effect/ManagedRuntime"
5
+ import { CauseException } from "./client/errors.js"
6
+ import { type Context } from "./Context.js"
7
+ import * as Effect from "./Effect.js"
8
+ import * as Layer from "./Layer.js"
9
+
10
+ export const makeAppRuntime = Effect.fnUntraced(function*<A, E>(layer: Layer.Layer<A, E>) {
11
+ const l = layer.pipe(
12
+ Layer.provide(Logger.layer([Logger.consolePretty()]))
13
+ ) as Layer.Layer<A>
14
+ const mrt = ManagedRuntime.make(l)
15
+ yield* mrt.contextEffect
16
+ return Object.assign(mrt, {
17
+ [Symbol.dispose]() {
18
+ return Effect.runSync(mrt.disposeEffect)
19
+ },
20
+
21
+ [Symbol.asyncDispose]() {
22
+ return mrt.dispose()
23
+ }
24
+ }) // as we initialise here, there is no more error left.
25
+ })
26
+
27
+ export function initializeSync<A, E>(layer: Layer.Layer<A, E>) {
28
+ const runtime = Effect.runSync(makeAppRuntime(layer))
29
+ return runtime
30
+ }
31
+
32
+ export function initializeAsync<A, E>(layer: Layer.Layer<A, E>) {
33
+ return Effect
34
+ .runPromise(makeAppRuntime(layer))
35
+ }
36
+
37
+ // we wrap into CauseException because we want to keep the full cause of the failure.
38
+ export const makeRunPromise = <T>(services: Context<T>) =>
39
+ flow(Effect.runPromiseExitWith(services), (_) =>
40
+ _.then(
41
+ Exit.match({
42
+ onFailure: (cause) => Promise.reject(new CauseException(cause, "runPromise")),
43
+ onSuccess: (value) => Promise.resolve(value)
44
+ })
45
+ ))
46
+
47
+ export const makeRunSync = <T>(services: Context<T>) =>
48
+ flow(
49
+ Effect.runSyncExitWith(services),
50
+ Exit.match({
51
+ onFailure: (cause) => {
52
+ throw new CauseException(cause, "runSync")
53
+ },
54
+ onSuccess: (value) => value
55
+ })
56
+ )
package/src/toast.ts ADDED
@@ -0,0 +1,54 @@
1
+ import * as Context from "./Context.js"
2
+ import { accessEffectFn } from "./Context.js"
3
+ import * as Effect from "./Effect.js"
4
+ import * as Option from "./Option.js"
5
+
6
+ export type ToastId = string | number
7
+ export type ToastOpts = { id?: ToastId; timeout?: number; groupId?: string; requestId?: string }
8
+ export type ToastOptsInternal = { id?: ToastId | null; timeout?: number; groupId?: string; requestId?: string }
9
+
10
+ export type UseToast = () => {
11
+ error: (this: void, message: string, options?: ToastOpts) => ToastId
12
+ warning: (this: void, message: string, options?: ToastOpts) => ToastId
13
+ success: (this: void, message: string, options?: ToastOpts) => ToastId
14
+ info: (this: void, message: string, options?: ToastOpts) => ToastId
15
+ dismiss: (this: void, id: ToastId) => void
16
+ }
17
+
18
+ export class CurrentToastId extends Context.Opaque<CurrentToastId, { toastId: ToastId }>()("CurrentToastId") {}
19
+
20
+ /** fallback to CurrentToastId when available unless id is explicitly set to a value or null */
21
+ export const wrap = (toast: ReturnType<UseToast>) => {
22
+ const wrap = (toastHandler: (message: string, options?: ToastOpts) => ToastId) => {
23
+ return (message: string, options?: ToastOptsInternal) =>
24
+ Effect.serviceOption(CurrentToastId).pipe(
25
+ Effect.flatMap((currentToast) =>
26
+ Effect.sync(() => {
27
+ const { id: _id, ...rest } = options ?? {}
28
+ const id = _id !== undefined
29
+ ? _id ?? undefined
30
+ : Option.getOrUndefined(Option.map(currentToast, (_) => _.toastId))
31
+ // when id is undefined, we may end up with no toast at all..
32
+ return toastHandler(message, id !== undefined ? { ...rest, id } : rest)
33
+ })
34
+ )
35
+ )
36
+ }
37
+ return {
38
+ error: wrap(toast.error),
39
+ info: wrap(toast.info),
40
+ success: wrap(toast.success),
41
+ warning: wrap(toast.warning),
42
+ dismiss: (toastId: ToastId) => Effect.sync(() => toast.dismiss(toastId))
43
+ }
44
+ }
45
+
46
+ type ToastShape = ReturnType<typeof wrap>
47
+
48
+ export class Toast extends Context.Opaque<Toast, ToastShape>()("Toast") {
49
+ static readonly error = accessEffectFn(this, "error")
50
+ static readonly info = accessEffectFn(this, "info")
51
+ static readonly success = accessEffectFn(this, "success")
52
+ static readonly warning = accessEffectFn(this, "warning")
53
+ static readonly dismiss = accessEffectFn(this, "dismiss")
54
+ }
@@ -0,0 +1,133 @@
1
+ import * as Cause from "effect/Cause"
2
+ import * as Fiber from "effect/Fiber"
3
+ import * as Context from "./Context.js"
4
+ import * as Effect from "./Effect.js"
5
+ import * as Layer from "./Layer.js"
6
+ import type * as Option from "./Option.js"
7
+ import * as S from "./Schema.js"
8
+ import { CurrentToastId, Toast, type ToastId } from "./toast.js"
9
+ import { wrapEffect } from "./utils.js"
10
+
11
+ export interface ToastOptions<A, E, Args extends ReadonlyArray<unknown>, WaiR, SucR, ErrR> {
12
+ stableToastId?: undefined | string | ((...args: Args) => string | undefined)
13
+ timeout?: number
14
+ showSpanInfo?: false
15
+ groupId?: string
16
+ onWaiting:
17
+ | string
18
+ | ((...args: Args) => string | null)
19
+ | null
20
+ | ((
21
+ ...args: Args
22
+ ) => Effect.Effect<string | null, never, WaiR>)
23
+ onSuccess:
24
+ | string
25
+ | ((a: A, ...args: Args) => string | null)
26
+ | null
27
+ | ((
28
+ a: A,
29
+ ...args: Args
30
+ ) => Effect.Effect<string | null, never, SucR>)
31
+ onFailure:
32
+ | string
33
+ | ((
34
+ error: Option.Option<E>,
35
+ ...args: Args
36
+ ) => string | { level: "warn" | "error"; message: string })
37
+ | ((
38
+ error: Option.Option<E>,
39
+ ...args: Args
40
+ ) => Effect.Effect<string | { level: "warn" | "error"; message: string }, never, ErrR>)
41
+ }
42
+
43
+ // @effect-diagnostics-next-line missingEffectServiceDependency:off
44
+ export class WithToast extends Context.Service<WithToast>()("WithToast", {
45
+ make: Effect.gen(function*() {
46
+ const toast = yield* Toast
47
+ return <A, E, Args extends readonly unknown[], R, WaiR = never, SucR = never, ErrR = never>(
48
+ options: ToastOptions<A, E, Args, WaiR, SucR, ErrR>
49
+ ) =>
50
+ Effect.fnUntraced(function*(self: Effect.Effect<A, E, R>, ...args: Args) {
51
+ const baseTimeout = options.timeout ?? 3_000
52
+
53
+ const stableToastId = typeof options.stableToastId === "function"
54
+ ? options.stableToastId(...args)
55
+ : options.stableToastId
56
+
57
+ const requestId: string = yield* Effect.currentSpan.pipe(
58
+ Effect.map((span) => span.traceId),
59
+ Effect.orElseSucceed(() => S.StringId.make())
60
+ )
61
+ const groupId = options.groupId
62
+ const meta = { ...(groupId !== undefined ? { groupId } : {}), requestId }
63
+
64
+ const t = yield* wrapEffect(options.onWaiting)(...args)
65
+ const toastId: ToastId | undefined = t === null
66
+ ? stableToastId
67
+ : stableToastId ?? `wait-${Math.random().toString(36).slice(2)}`
68
+
69
+ const waitingFiber = t === null ? undefined : yield* Effect.forkChild(
70
+ Effect.sleep("1 seconds").pipe(
71
+ Effect.andThen(toast.info(t, { id: toastId!, timeout: Infinity, ...meta }))
72
+ )
73
+ )
74
+ const interruptWaiting = waitingFiber ? Fiber.interrupt(waitingFiber) : Effect.void
75
+
76
+ return yield* self.pipe(
77
+ Effect.tap(Effect.fnUntraced(function*(a) {
78
+ yield* interruptWaiting
79
+ const t = yield* wrapEffect(options.onSuccess)(a, ...args)
80
+ if (t === null) {
81
+ return
82
+ }
83
+ yield* toast.success(
84
+ t,
85
+ toastId !== undefined
86
+ ? { id: toastId, timeout: baseTimeout, ...meta }
87
+ : { timeout: baseTimeout, ...meta }
88
+ )
89
+ })),
90
+ Effect.tapCause(Effect.fnUntraced(function*(cause) {
91
+ yield* interruptWaiting
92
+ yield* Effect.logDebug(
93
+ "WithToast - caught error cause: " + Cause.squash(cause),
94
+ Cause.hasInterruptsOnly(cause),
95
+ cause
96
+ )
97
+
98
+ if (Cause.hasInterruptsOnly(cause)) {
99
+ if (toastId) yield* toast.dismiss(toastId)
100
+ return
101
+ }
102
+
103
+ const spanInfo = options.showSpanInfo !== false
104
+ ? yield* Effect.currentSpan.pipe(
105
+ Effect.map((span) => `\nTrace: ${span.traceId}\nSpan: ${span.spanId}`),
106
+ Effect.orElseSucceed(() => "")
107
+ )
108
+ : ""
109
+
110
+ const t = yield* wrapEffect(options.onFailure)(Cause.findErrorOption(cause), ...args)
111
+ const opts = { timeout: baseTimeout * 2, ...meta }
112
+
113
+ if (typeof t === "object") {
114
+ const message = t.message + spanInfo
115
+ return t.level === "warn"
116
+ ? yield* toast.warning(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
117
+ : yield* toast.error(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
118
+ }
119
+ yield* toast.error(t + spanInfo, toastId !== undefined ? { ...opts, id: toastId } : opts)
120
+ }, Effect.uninterruptible)),
121
+ toastId !== undefined ? Effect.provideService(CurrentToastId, CurrentToastId.of({ toastId })) : (_) => _
122
+ )
123
+ })
124
+ })
125
+ }) {
126
+ static readonly DefaultWithoutDependencies = Layer.effect(this, this.make)
127
+ static readonly Default = this.DefaultWithoutDependencies
128
+
129
+ static readonly handle = <A, E, Args extends Array<unknown>, R, WaiR = never, SucR = never, ErrR = never>(
130
+ options: ToastOptions<A, E, Args, WaiR, SucR, ErrR>
131
+ ): (self: Effect.Effect<A, E, R>, ...args: Args) => Effect.Effect<A, E, R | WaiR | SucR | ErrR | WithToast> =>
132
+ (self, ...args) => this.use((_) => _<A, E, Args, R, WaiR, SucR, ErrR>(options)(self, ...args))
133
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-dynamic-middleware.test.d.ts","sourceRoot":"","sources":["../rpc-dynamic-middleware.test.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAGrC,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,IAAI,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,CAAC,MAAM,kBAAkB,CAAA;;;;;;;;AASrC,cAAM,WAAY,SAAQ,gBAKzB;CAAG;;;;;;;AAeJ,cAAM,cAAe,SAAQ,mBAE3B;CAAG;;;;;;;;AAGL,cAAM,eAAgB,SAAQ,oBAG5B;CAAG;;;;;;;;;AASL,cAAM,aAAc,SAAQ,kBAIC;CAC3B;AAkDF,eAAO,MAAM,OAAO,+MAWlB,CAAA;AA4CF,eAAO,MAAM,SAAS,yNAAwC,CAAA;AA0G9D,eAAO,MAAM,gBAAgB,sRAA+C,CAAA"}