@effect-app/vue 2.15.1 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mutate.ts CHANGED
@@ -102,7 +102,8 @@ type MaybeRefDeep<T> = MaybeRef<
102
102
  : T
103
103
  >
104
104
 
105
- export interface MutationOptions {
105
+ export interface MutationOptions<A, E, R, A2 = A, E2 = E, R2 = R, I = void> {
106
+ mapHandler?: (handler: Effect<A, E, R>, input: I) => Effect<A2, E2, R2>
106
107
  queryInvalidation?: (defaultKey: string[], name: string) => {
107
108
  filters?: MaybeRefDeep<InvalidateQueryFilters> | undefined
108
109
  options?: MaybeRefDeep<InvalidateOptions> | undefined
@@ -126,33 +127,33 @@ export const makeMutation = () => {
126
127
  * Returns a tuple with state ref and execution function which reports errors as Toast.
127
128
  */
128
129
  const useSafeMutation: {
129
- <I, E, A, R, Request extends TaggedRequestClassAny>(
130
+ <I, E, A, R, Request extends TaggedRequestClassAny, A2 = A, E2 = E, R2 = R>(
130
131
  self: RequestHandlerWithInput<I, A, E, R, Request>,
131
- options?: MutationOptions
132
+ options?: MutationOptions<A, E, R, A2, E2, R2, I>
132
133
  ): readonly [
133
- Readonly<Ref<MutationResult<A, E>>>,
134
- (i: I) => Effect<A, E, R>
134
+ Readonly<Ref<MutationResult<A2, E2>>>,
135
+ (i: I) => Effect<A2, E2, R2>
135
136
  ]
136
- <E, A, R, Request extends TaggedRequestClassAny>(
137
+ <E, A, R, Request extends TaggedRequestClassAny, A2 = A, E2 = E, R2 = R>(
137
138
  self: RequestHandler<A, E, R, Request>,
138
- options?: MutationOptions
139
+ options?: MutationOptions<A, E, R, A2, E2, R2>
139
140
  ): readonly [
140
- Readonly<Ref<MutationResult<A, E>>>,
141
- Effect<A, E, R>
141
+ Readonly<Ref<MutationResult<A2, E2>>>,
142
+ Effect<A2, E2, R2>
142
143
  ]
143
- } = <I, E, A, R, Request extends TaggedRequestClassAny>(
144
+ } = <I, E, A, R, Request extends TaggedRequestClassAny, A2 = A, E2 = E, R2 = R>(
144
145
  self: RequestHandlerWithInput<I, A, E, R, Request> | RequestHandler<A, E, R, Request>,
145
- options?: MutationOptions
146
+ options?: MutationOptions<A, E, R, A2, E2, R2, I>
146
147
  ) => {
147
148
  const queryClient = useQueryClient()
148
- const state: Ref<MutationResult<A, E>> = ref<MutationResult<A, E>>({ _tag: "Initial" }) as any
149
+ const state: Ref<MutationResult<A2, E2>> = ref<MutationResult<A2, E2>>({ _tag: "Initial" }) as any
149
150
 
150
151
  const invalidateQueries = (
151
152
  filters?: MaybeRefDeep<InvalidateQueryFilters>,
152
153
  options?: MaybeRefDeep<InvalidateOptions>
153
154
  ) => Effect.promise(() => queryClient.invalidateQueries(filters, options))
154
155
 
155
- function handleExit(exit: Exit.Exit<A, E>) {
156
+ function handleExit(exit: Exit.Exit<A2, E2>) {
156
157
  return Effect.sync(() => {
157
158
  if (Exit.isSuccess(exit)) {
158
159
  state.value = { _tag: "Success", data: exit.value }
@@ -193,36 +194,30 @@ export const makeMutation = () => {
193
194
  .pipe(Effect.withSpan("client.query.invalidation", { captureStackTrace: false }))
194
195
  })
195
196
 
197
+ const mapHandler = options?.mapHandler ?? ((_) => _)
198
+
199
+ const handle = (self: Effect<A, E, R>, name: string, i: I | void = void 0) =>
200
+ Effect
201
+ .sync(() => {
202
+ state.value = { _tag: "Loading" }
203
+ })
204
+ .pipe(
205
+ Effect.zipRight(
206
+ mapHandler(
207
+ Effect.tapBoth(self, { onFailure: () => invalidateCache, onSuccess: () => invalidateCache }),
208
+ i as I
209
+ ) as Effect<A2, E2, R2>
210
+ ),
211
+ Effect.tapDefect(reportRuntimeError),
212
+ Effect.onExit(handleExit),
213
+ Effect.withSpan(`mutation ${name}`, { captureStackTrace: false })
214
+ )
215
+
196
216
  const handler = self.handler
197
- const r = Effect.isEffect(handler)
198
- ? tuple(
199
- state,
200
- Effect
201
- .sync(() => {
202
- state.value = { _tag: "Loading" }
203
- })
204
- .pipe(
205
- Effect.zipRight(handler),
206
- Effect.tap(invalidateCache),
207
- Effect.tapDefect(reportRuntimeError),
208
- Effect.onExit(handleExit),
209
- Effect.withSpan(`mutation ${self.name}`, { captureStackTrace: false })
210
- )
211
- )
212
- : tuple(state, (fst: I) => {
213
- const effect = handler(fst)
214
- return Effect
215
- .sync(() => {
216
- state.value = { _tag: "Loading" }
217
- })
218
- .pipe(
219
- Effect.zipRight(effect),
220
- Effect.tapBoth({ onFailure: () => invalidateCache, onSuccess: () => invalidateCache }),
221
- Effect.tapDefect(reportRuntimeError),
222
- Effect.onExit(handleExit),
223
- Effect.withSpan(`mutation ${self.name}`, { captureStackTrace: false })
224
- )
225
- })
217
+ const r = tuple(
218
+ state,
219
+ Effect.isEffect(handler) ? handle(handler, self.name) : (i: I) => handle(handler(i), self.name, i)
220
+ )
226
221
 
227
222
  return r as any
228
223
  }
package/src/query.ts CHANGED
@@ -12,7 +12,7 @@ import type {
12
12
  UseQueryReturnType
13
13
  } from "@tanstack/vue-query"
14
14
  import { useQuery } from "@tanstack/vue-query"
15
- import { Cause, Effect, Option, Runtime, S } from "effect-app"
15
+ import { Array, Cause, Effect, Option, Runtime, S } from "effect-app"
16
16
  import { ServiceUnavailableError } from "effect-app/client"
17
17
  import type { RequestHandler, RequestHandlerWithInput, TaggedRequestClassAny } from "effect-app/client/clientFor"
18
18
  import { isHttpRequestError, isHttpResponseError } from "effect-app/http/http-client"
@@ -203,3 +203,49 @@ export const makeQuery = <R>(runtime: ShallowRef<Runtime.Runtime<R> | undefined>
203
203
 
204
204
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
205
205
  export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
206
+
207
+ function orPrevious<E, A>(result: Result.Result<A, E>) {
208
+ return Result.isFailure(result) && Option.isSome(result.previousValue)
209
+ ? Result.success(result.previousValue.value, result.waiting)
210
+ : result
211
+ }
212
+
213
+ export function composeQueries<
214
+ R extends Record<string, Result.Result<any, any>>
215
+ >(
216
+ results: R,
217
+ renderPreviousOnFailure?: boolean
218
+ ): Result.Result<
219
+ {
220
+ [Property in keyof R]: R[Property] extends Result.Result<infer A, any> ? A
221
+ : never
222
+ },
223
+ {
224
+ [Property in keyof R]: R[Property] extends Result.Result<any, infer E> ? E
225
+ : never
226
+ }[keyof R]
227
+ > {
228
+ const values = renderPreviousOnFailure
229
+ ? Object.values(results).map(orPrevious)
230
+ : Object.values(results)
231
+ const error = values.find(Result.isFailure)
232
+ if (error) {
233
+ return error
234
+ }
235
+ const initial = Array.findFirst(values, (x) => x._tag === "Initial" ? Option.some(x) : Option.none())
236
+ if (initial.value !== undefined) {
237
+ return initial.value
238
+ }
239
+ const loading = Array.findFirst(values, (x) => Result.isInitial(x) && x.waiting ? Option.some(x) : Option.none())
240
+ if (loading.value !== undefined) {
241
+ return loading.value
242
+ }
243
+
244
+ const isRefreshing = values.some((x) => x.waiting)
245
+
246
+ const r = Object.entries(results).reduce((prev, [key, value]) => {
247
+ prev[key] = Result.value(value).value
248
+ return prev
249
+ }, {} as any)
250
+ return Result.success(r, isRefreshing)
251
+ }