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.
- package/CHANGELOG.md +16 -0
- package/dist/Emailer.d.ts +51 -0
- package/dist/Emailer.d.ts.map +1 -0
- package/dist/Emailer.js +7 -0
- package/dist/Model/Repository/Registry.d.ts +21 -0
- package/dist/Model/Repository/Registry.d.ts.map +1 -0
- package/dist/Model/Repository/Registry.js +18 -0
- package/dist/Model/Repository/ext.d.ts +60 -0
- package/dist/Model/Repository/ext.d.ts.map +1 -0
- package/dist/Model/Repository/ext.js +122 -0
- package/dist/Model/Repository/internal/internal.d.ts +62 -0
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
- package/dist/Model/Repository/internal/internal.js +413 -0
- package/dist/Model/Repository/legacy.d.ts +21 -0
- package/dist/Model/Repository/legacy.d.ts.map +1 -0
- package/dist/Model/Repository/legacy.js +2 -0
- package/dist/Model/Repository/makeRepo.d.ts +53 -0
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
- package/dist/Model/Repository/makeRepo.js +27 -0
- package/dist/Model/Repository/service.d.ts +97 -0
- package/dist/Model/Repository/service.d.ts.map +1 -0
- package/dist/Model/Repository/service.js +2 -0
- package/dist/Model/Repository/validation.d.ts +71 -0
- package/dist/Model/Repository/validation.d.ts.map +1 -0
- package/dist/Model/Repository/validation.js +32 -0
- package/dist/Model/Repository.d.ts +7 -0
- package/dist/Model/Repository.d.ts.map +1 -0
- package/dist/Model/Repository.js +7 -0
- package/dist/Model/dsl.d.ts +33 -0
- package/dist/Model/dsl.d.ts.map +1 -0
- package/dist/Model/dsl.js +43 -0
- package/dist/Model/filter/filterApi.d.ts +30 -0
- package/dist/Model/filter/filterApi.d.ts.map +1 -0
- package/dist/Model/filter/filterApi.js +2 -0
- package/dist/Model/filter/types/errors.d.ts +29 -0
- package/dist/Model/filter/types/errors.d.ts.map +1 -0
- package/dist/Model/filter/types/errors.js +2 -0
- package/dist/Model/filter/types/fields.d.ts +15 -0
- package/dist/Model/filter/types/fields.d.ts.map +1 -0
- package/dist/Model/filter/types/fields.js +2 -0
- package/dist/Model/filter/types/path/common.d.ts +316 -0
- package/dist/Model/filter/types/path/common.d.ts.map +1 -0
- package/dist/Model/filter/types/path/common.js +2 -0
- package/dist/Model/filter/types/path/eager.d.ts +95 -0
- package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
- package/dist/Model/filter/types/path/eager.js +31 -0
- package/dist/Model/filter/types/path/index.d.ts +4 -0
- package/dist/Model/filter/types/path/index.d.ts.map +1 -0
- package/dist/Model/filter/types/path/index.js +3 -0
- package/dist/Model/filter/types/utils.d.ts +79 -0
- package/dist/Model/filter/types/utils.d.ts.map +1 -0
- package/dist/Model/filter/types/utils.js +2 -0
- package/dist/Model/filter/types/validator.d.ts +30 -0
- package/dist/Model/filter/types/validator.d.ts.map +1 -0
- package/dist/Model/filter/types/validator.js +2 -0
- package/dist/Model/filter/types.d.ts +5 -0
- package/dist/Model/filter/types.d.ts.map +1 -0
- package/dist/Model/filter/types.js +7 -0
- package/dist/Model/query/dsl.d.ts +446 -0
- package/dist/Model/query/dsl.d.ts.map +1 -0
- package/dist/Model/query/dsl.js +342 -0
- package/dist/Model/query/new-kid-interpreter.d.ts +136 -0
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
- package/dist/Model/query/new-kid-interpreter.js +336 -0
- package/dist/Model/query.d.ts +15 -0
- package/dist/Model/query.d.ts.map +1 -0
- package/dist/Model/query.js +3 -0
- package/dist/Model.d.ts +5 -0
- package/dist/Model.d.ts.map +1 -0
- package/dist/Model.js +5 -0
- package/dist/QueueMaker.d.ts +13 -0
- package/dist/QueueMaker.d.ts.map +1 -0
- package/dist/QueueMaker.js +4 -0
- package/dist/RequestContext.d.ts +103 -0
- package/dist/RequestContext.d.ts.map +1 -0
- package/dist/RequestContext.js +49 -0
- package/dist/Schema/ext.d.ts +9 -9
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +1 -1
- package/dist/Store.d.ts +147 -0
- package/dist/Store.d.ts.map +1 -0
- package/dist/Store.js +95 -0
- package/dist/client/apiClientFactory.d.ts +1 -1
- package/dist/client/clientFor.d.ts +5 -5
- package/dist/client/clientFor.d.ts.map +1 -1
- package/dist/client/makeClient.d.ts +36 -36
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/rpc/MiddlewareMaker.d.ts +2 -2
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.d.ts +2 -2
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +40 -0
- package/dist/toast.d.ts +51 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +34 -0
- package/dist/withToast.d.ts +30 -0
- package/dist/withToast.d.ts.map +1 -0
- package/dist/withToast.js +64 -0
- package/package.json +113 -1
- package/src/Emailer.ts +51 -0
- package/src/Model/Repository/Registry.ts +34 -0
- package/src/Model/Repository/ext.ts +375 -0
- package/src/Model/Repository/internal/internal.ts +708 -0
- package/src/Model/Repository/legacy.ts +29 -0
- package/src/Model/Repository/makeRepo.ts +144 -0
- package/src/Model/Repository/service.ts +639 -0
- package/src/Model/Repository/validation.ts +31 -0
- package/src/Model/Repository.ts +6 -0
- package/src/Model/dsl.ts +129 -0
- package/src/Model/filter/filterApi.ts +60 -0
- package/src/Model/filter/types/errors.ts +47 -0
- package/src/Model/filter/types/fields.ts +50 -0
- package/src/Model/filter/types/path/common.ts +404 -0
- package/src/Model/filter/types/path/eager.ts +297 -0
- package/src/Model/filter/types/path/index.ts +4 -0
- package/src/Model/filter/types/utils.ts +128 -0
- package/src/Model/filter/types/validator.ts +46 -0
- package/src/Model/filter/types.ts +6 -0
- package/src/Model/query/dsl.ts +2546 -0
- package/src/Model/query/new-kid-interpreter.ts +484 -0
- package/src/Model/query.ts +13 -0
- package/src/Model.ts +4 -0
- package/src/QueueMaker.ts +19 -0
- package/src/RequestContext.ts +62 -0
- package/src/Schema/ext.ts +6 -6
- package/src/Store.ts +243 -0
- package/src/client/clientFor.ts +8 -8
- package/src/client/makeClient.ts +11 -11
- package/src/index.ts +2 -0
- package/src/rpc/MiddlewareMaker.ts +1 -1
- package/src/rpc/RpcMiddleware.ts +1 -1
- package/src/runtime.ts +56 -0
- package/src/toast.ts +54 -0
- package/src/withToast.ts +133 -0
- 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
|
+
}
|
package/src/client/clientFor.ts
CHANGED
|
@@ -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> ?
|
|
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> ?
|
|
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 } ?
|
|
154
|
-
:
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
|
|
174
|
-
|
|
173
|
+
T["success"]["Type"],
|
|
174
|
+
T["error"]["Type"] | E,
|
|
175
175
|
R | T["success"]["DecodingServices"] | T["error"]["DecodingServices"],
|
|
176
176
|
T,
|
|
177
177
|
Id
|
package/src/client/makeClient.ts
CHANGED
|
@@ -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.
|
|
47
|
+
: S.Struct<Payload>["Type"]
|
|
48
48
|
|
|
49
|
-
type OutputFromSuccess<Success extends S.Top> = Success extends typeof ForceVoid ? void :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 } ?
|
|
97
|
+
export type Errors<T> = T extends TagClassAny ? T extends { error: S.Top } ? T["error"]["Type"]
|
|
98
98
|
: never
|
|
99
99
|
: never
|
|
100
100
|
|
package/src/rpc/RpcMiddleware.ts
CHANGED
|
@@ -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> } ?
|
|
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
|
+
}
|
package/src/withToast.ts
ADDED
|
@@ -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"}
|