@effect-app/vue 1.25.2 → 1.26.1

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/dist/query2.js ADDED
@@ -0,0 +1,119 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
5
+ import { isHttpRequestError, isHttpResponseError } from "@effect-app/core/http/http-client";
6
+ import * as Result from "@effect-rx/rx/Result";
7
+ import { useQuery } from "@tanstack/vue-query";
8
+ import { Cause, Effect, Option, Runtime, S } from "effect-app";
9
+ import { ServiceUnavailableError } from "effect-app/client";
10
+ import { computed, ref } from "vue";
11
+ import { makeQueryKey, reportRuntimeError } from "./internal.js";
12
+ export const makeQuery2 = (runtime) => {
13
+ // TODO: options
14
+ // declare function useQuery<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(options: UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseQueryReturnType<TData, TError>;
15
+ // declare function useQuery<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(options: DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>, queryClient?: QueryClient): UseQueryDefinedReturnType<TData, TError>;
16
+ // declare function useQuery<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey>(options: UseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>, queryClient?: QueryClient): UseQueryReturnType<TData, TError>;
17
+ const useSafeQuery_ = (q, arg, options = {} // TODO
18
+ ) => {
19
+ const runPromise = Runtime.runPromise(runtime.value);
20
+ const arr = arg;
21
+ const req = !arg
22
+ ? undefined
23
+ : typeof arr === "function"
24
+ ? {
25
+ get value() {
26
+ return arr();
27
+ }
28
+ }
29
+ : ref(arg);
30
+ const queryKey = makeQueryKey(q.name);
31
+ const handler = q.handler;
32
+ const r = useQuery(Effect.isEffect(handler)
33
+ ? {
34
+ ...options,
35
+ retry: (retryCount, error) => {
36
+ if (Runtime.isFiberFailure(error)) {
37
+ const cause = error[Runtime.FiberFailureCauseId];
38
+ const sq = Cause.squash(cause);
39
+ if (!isHttpRequestError(sq) && !isHttpResponseError(sq) && !S.is(ServiceUnavailableError)(sq)) {
40
+ return false;
41
+ }
42
+ }
43
+ return retryCount < 5;
44
+ },
45
+ queryKey,
46
+ queryFn: ({ signal }) => runPromise(handler
47
+ .pipe(Effect.tapDefect(reportRuntimeError), Effect.withSpan(`query ${q.name}`, { captureStackTrace: false })), { signal })
48
+ }
49
+ : {
50
+ ...options,
51
+ retry: (retryCount, error) => {
52
+ if (Runtime.isFiberFailure(error)) {
53
+ const cause = error[Runtime.FiberFailureCauseId];
54
+ const sq = Cause.squash(cause);
55
+ if (!isHttpRequestError(sq) && !isHttpResponseError(sq) && !S.is(ServiceUnavailableError)(sq)) {
56
+ return false;
57
+ }
58
+ }
59
+ return retryCount < 5;
60
+ },
61
+ queryKey: [...queryKey, req],
62
+ queryFn: ({ signal }) => runPromise(handler(req.value)
63
+ .pipe(Effect.tapDefect(reportRuntimeError), Effect.withSpan(`query ${q.name}`, { captureStackTrace: false })), { signal })
64
+ });
65
+ const result = computed(() => swrToQuery({
66
+ error: r.error.value ?? undefined,
67
+ data: r.data.value,
68
+ isValidating: r.isFetching.value
69
+ }));
70
+ const latestSuccess = computed(() => Option.getOrUndefined(Result.value(result.value)));
71
+ return [
72
+ result,
73
+ latestSuccess,
74
+ // one thing to keep in mind is that span will be disconnected as Context does not pass from outside.
75
+ (options) => Effect.promise(() => r.refetch(options)),
76
+ r
77
+ ];
78
+ };
79
+ function swrToQuery(r) {
80
+ if (r.error) {
81
+ return Result.failureWithPrevious(r.error[Runtime.FiberFailureCauseId], r.data === undefined ? Option.none() : Option.some(Result.success(r.data)), r.isValidating);
82
+ }
83
+ if (r.data !== undefined) {
84
+ return Result.success(r.data, r.isValidating);
85
+ }
86
+ return Result.initial(r.isValidating);
87
+ }
88
+ function useSafeQuery(self,
89
+ /*
90
+ q:
91
+ | {
92
+ handler: (
93
+ req: I
94
+ ) => Effect<
95
+ A,
96
+ E,
97
+ R
98
+ >
99
+ mapPath: (req: I) => string
100
+ name: string
101
+ }
102
+ | {
103
+ handler: Effect<
104
+ A,
105
+ E,
106
+ R
107
+ >
108
+ mapPath: string
109
+ name: string
110
+ },
111
+ */
112
+ argOrOptions, options) {
113
+ return Effect.isEffect(self.handler)
114
+ ? useSafeQuery_(self, undefined, argOrOptions)
115
+ : useSafeQuery_(self, argOrOptions, options);
116
+ }
117
+ return useSafeQuery;
118
+ };
119
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcnkyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3F1ZXJ5Mi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSx1REFBdUQ7QUFDdkQsc0RBQXNEO0FBQ3RELHdEQUF3RDtBQUN4RCw0REFBNEQ7QUFDNUQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLE1BQU0sbUNBQW1DLENBQUE7QUFDM0YsT0FBTyxLQUFLLE1BQU0sTUFBTSxzQkFBc0IsQ0FBQTtBQVE5QyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFDOUMsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDOUQsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sbUJBQW1CLENBQUE7QUFDM0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxHQUFHLEVBQUUsTUFBTSxLQUFLLENBQUE7QUFFbkMsT0FBTyxFQUFFLFlBQVksRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGVBQWUsQ0FBQTtBQWtCaEUsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLENBQUksT0FBZ0MsRUFBRSxFQUFFO0lBQ2hFLGdCQUFnQjtJQUNoQixzUkFBc1I7SUFDdFIsMlJBQTJSO0lBQzNSLHVSQUF1UjtJQUN2UixNQUFNLGFBQWEsR0FBRyxDQUNwQixDQW9CRyxFQUNILEdBQXdCLEVBQ3hCLFVBQXdFLEVBQUUsQ0FBQyxPQUFPO01BQ2xGLEVBQUU7UUFDRixNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUNwRCxNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUE7UUFDZixNQUFNLEdBQUcsR0FBaUIsQ0FBQyxHQUFHO1lBQzVCLENBQUMsQ0FBQyxTQUFTO1lBQ1gsQ0FBQyxDQUFDLE9BQU8sR0FBRyxLQUFLLFVBQVU7Z0JBQzNCLENB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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/vue",
3
- "version": "1.25.2",
3
+ "version": "1.26.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/effect-ts-app/libs/tree/main/packages/vue",
@@ -10,9 +10,9 @@
10
10
  "@vueuse/core": "^11.1.0",
11
11
  "query-string": "^9.1.1",
12
12
  "@effect-app/core": "1.17.0",
13
+ "@effect-app/vue": "1.26.1",
13
14
  "effect-app": "1.30.0",
14
- "@effect-app/schema": "1.19.0",
15
- "@effect-app/vue": "1.25.2"
15
+ "@effect-app/schema": "1.19.0"
16
16
  },
17
17
  "peerDependencies": {
18
18
  "@effect/platform": "^0.68.5",
@@ -102,6 +102,16 @@
102
102
  "default": "./_cjs/makeClient.cjs"
103
103
  }
104
104
  },
105
+ "./makeClient2": {
106
+ "import": {
107
+ "types": "./dist/makeClient2.d.ts",
108
+ "default": "./dist/makeClient2.js"
109
+ },
110
+ "require": {
111
+ "types": "./dist/makeClient2.d.ts",
112
+ "default": "./_cjs/makeClient2.cjs"
113
+ }
114
+ },
105
115
  "./makeContext": {
106
116
  "import": {
107
117
  "types": "./dist/makeContext.d.ts",
@@ -132,6 +142,16 @@
132
142
  "default": "./_cjs/mutate.cjs"
133
143
  }
134
144
  },
145
+ "./mutate2": {
146
+ "import": {
147
+ "types": "./dist/mutate2.d.ts",
148
+ "default": "./dist/mutate2.js"
149
+ },
150
+ "require": {
151
+ "types": "./dist/mutate2.d.ts",
152
+ "default": "./_cjs/mutate2.cjs"
153
+ }
154
+ },
135
155
  "./query": {
136
156
  "import": {
137
157
  "types": "./dist/query.d.ts",
@@ -142,6 +162,16 @@
142
162
  "default": "./_cjs/query.cjs"
143
163
  }
144
164
  },
165
+ "./query2": {
166
+ "import": {
167
+ "types": "./dist/query2.d.ts",
168
+ "default": "./dist/query2.js"
169
+ },
170
+ "require": {
171
+ "types": "./dist/query2.d.ts",
172
+ "default": "./_cjs/query2.cjs"
173
+ }
174
+ },
145
175
  "./routeParams": {
146
176
  "import": {
147
177
  "types": "./dist/routeParams.d.ts",
@@ -0,0 +1,445 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { flow, pipe, tuple } from "@effect-app/core/Function"
3
+ import type { MutationResult } from "@effect-app/vue"
4
+ import { Result } from "@effect-app/vue"
5
+ import * as Sentry from "@sentry/browser"
6
+ import { type MaybeRefOrGetter, type Pausable, useIntervalFn, type UseIntervalFnOptions } from "@vueuse/core"
7
+ import { Array, Cause, Effect, Exit, Match, Option, S } from "effect-app"
8
+ import { type SupportedErrors } from "effect-app/client"
9
+ import { Failure, Success } from "effect-app/Operations"
10
+ import { dropUndefinedT } from "effect-app/utils"
11
+ import { computed, type ComputedRef } from "vue"
12
+ import type { MakeIntlReturn } from "./makeIntl.js"
13
+ import type { MakeMutation2, MutationOptions } from "./mutate2.js"
14
+
15
+ /**
16
+ * Use this after handling an error yourself, still continueing on the Error track, but the error will not be reported.
17
+ */
18
+ export class SuppressErrors extends Cause.YieldableError {
19
+ readonly _tag = "SuppressErrors"
20
+ }
21
+
22
+ export type ResponseErrors = S.ParseResult.ParseError | SupportedErrors | SuppressErrors
23
+
24
+ export function pauseWhileProcessing(
25
+ iv: Pausable,
26
+ pmf: () => Promise<unknown>
27
+ ) {
28
+ return Promise
29
+ .resolve(iv.pause())
30
+ .then(() => pmf())
31
+ .finally(() => iv.resume())
32
+ }
33
+
34
+ export function useIntervalPauseWhileProcessing(
35
+ pmf: () => Promise<unknown>,
36
+ interval?: MaybeRefOrGetter<number>,
37
+ options?: Omit<UseIntervalFnOptions, "immediateCallback">
38
+ ) {
39
+ const iv = useIntervalFn(
40
+ () => pauseWhileProcessing(iv, pmf),
41
+ interval,
42
+ options ? { ...options, immediateCallback: false } : options
43
+ )
44
+ return {
45
+ isActive: iv.isActive
46
+ }
47
+ }
48
+
49
+ export interface Opts<A> extends MutationOptions {
50
+ suppressErrorToast?: boolean
51
+ suppressSuccessToast?: boolean
52
+ successToast?: (a: A) => any
53
+ }
54
+
55
+ export interface Res<A, E> {
56
+ readonly loading: boolean
57
+ readonly data: A | undefined
58
+ readonly error: E | undefined
59
+ }
60
+
61
+ type WithAction<A> = A & {
62
+ action: string
63
+ }
64
+
65
+ // computed() takes a getter function and returns a readonly reactive ref
66
+ // object for the returned value from the getter.
67
+ type Resp<I, E, A, R> = readonly [
68
+ ComputedRef<Res<A, E>>,
69
+ WithAction<(I: I) => Effect<A, E, R>>
70
+ ]
71
+
72
+ type ActResp<E, A, R> = readonly [
73
+ ComputedRef<Res<A, E>>,
74
+ WithAction<() => Effect<A, E, R>>
75
+ ]
76
+
77
+ export function mutationResultToVue<A, E>(
78
+ mutationResult: MutationResult<A, E>
79
+ ): Res<A, E> {
80
+ switch (mutationResult._tag) {
81
+ case "Loading": {
82
+ return { loading: true, data: undefined, error: undefined }
83
+ }
84
+ case "Success": {
85
+ return {
86
+ loading: false,
87
+ data: mutationResult.data,
88
+ error: undefined
89
+ }
90
+ }
91
+ case "Error": {
92
+ return {
93
+ loading: false,
94
+ data: undefined,
95
+ error: mutationResult.error
96
+ }
97
+ }
98
+ case "Initial": {
99
+ return { loading: false, data: undefined, error: undefined }
100
+ }
101
+ }
102
+ }
103
+
104
+ export const makeClient2 = <Locale extends string, R>(
105
+ useIntl: MakeIntlReturn<Locale>["useIntl"],
106
+ useToast: () => {
107
+ error: (message: string) => void
108
+ warning: (message: string) => void
109
+ success: (message: string) => void
110
+ },
111
+ useSafeMutation: MakeMutation2,
112
+ messages: Record<string, string | undefined> = {}
113
+ ) => {
114
+ const useHandleRequestWithToast = () => {
115
+ const toast = useToast()
116
+ const { intl } = useIntl()
117
+
118
+ return handleRequestWithToast
119
+ /**
120
+ * Pass a function that returns a Promise.
121
+ * Returns an execution function which reports errors as Toast.
122
+ */
123
+ function handleRequestWithToast<
124
+ E extends ResponseErrors,
125
+ A,
126
+ R,
127
+ Args extends unknown[]
128
+ >(
129
+ f: (...args: Args) => Effect<A, E, R>,
130
+ action: string,
131
+ options: Opts<A> = { suppressErrorToast: false }
132
+ ) {
133
+ const message = messages[action] ?? action
134
+ const warnMessage = intl.value.formatMessage(
135
+ { id: "handle.with_warnings" },
136
+ { action: message }
137
+ )
138
+ const successMessage = intl.value.formatMessage(
139
+ { id: "handle.success" },
140
+ { action: message }
141
+ )
142
+ const errorMessage = intl.value.formatMessage(
143
+ { id: "handle.with_errors" },
144
+ { action: message }
145
+ )
146
+ return Object.assign(
147
+ flow(
148
+ f,
149
+ Effect.onExit(
150
+ Exit.matchEffect({
151
+ onSuccess: (r) =>
152
+ Effect.gen(function*() {
153
+ if (S.is(Failure)(r)) {
154
+ toast.warning(
155
+ warnMessage + r.message
156
+ ? "\n" + r.message
157
+ : ""
158
+ )
159
+ return
160
+ }
161
+
162
+ toast.success(
163
+ successMessage
164
+ + (S.is(Success)(r) && r.message
165
+ ? "\n" + r.message
166
+ : "")
167
+ )
168
+ }),
169
+ onFailure: (err) =>
170
+ Effect.gen(function*() {
171
+ if (Cause.isInterruptedOnly(err)) {
172
+ return
173
+ }
174
+
175
+ const fail = Cause.failureOption(err)
176
+ if (Option.isSome(fail)) {
177
+ if ((fail as any)._tag === "SuppressErrors") {
178
+ return Effect.succeed(void 0)
179
+ }
180
+
181
+ if (!options.suppressErrorToast) {
182
+ toast.error(`${errorMessage}:\n` + renderError(fail.value))
183
+ }
184
+
185
+ console.warn(fail, fail.toString())
186
+ return
187
+ }
188
+
189
+ const extra = {
190
+ action,
191
+ message: `Unexpected Error trying to ${action}`
192
+ }
193
+ Sentry.captureException(err, {
194
+ extra
195
+ })
196
+ console.error(err, extra)
197
+
198
+ toast.error(
199
+ intl.value.formatMessage(
200
+ { id: "handle.unexpected_error" },
201
+ {
202
+ action: message,
203
+ error: JSON.stringify(err, undefined, 2)
204
+ }
205
+ )
206
+ )
207
+ })
208
+ })
209
+ )
210
+ ),
211
+ { action }
212
+ )
213
+ }
214
+
215
+ function renderError(e: ResponseErrors): string {
216
+ return Match.value(e).pipe(
217
+ Match.tags({
218
+ // HttpErrorRequest: e =>
219
+ // intl.value.formatMessage(
220
+ // { id: "handle.request_error" },
221
+ // { error: `${e.error}` },
222
+ // ),
223
+ // HttpErrorResponse: e =>
224
+ // e.response.status >= 500 ||
225
+ // e.response.body._tag !== "Some" ||
226
+ // !e.response.body.value
227
+ // ? intl.value.formatMessage(
228
+ // { id: "handle.error_response" },
229
+ // {
230
+ // error: `${
231
+ // e.response.body._tag === "Some" && e.response.body.value
232
+ // ? parseError(e.response.body.value)
233
+ // : "Unknown"
234
+ // } (${e.response.status})`,
235
+ // },
236
+ // )
237
+ // : intl.value.formatMessage(
238
+ // { id: "handle.unexpected_error" },
239
+ // {
240
+ // error:
241
+ // JSON.stringify(e.response.body, undefined, 2) +
242
+ // "( " +
243
+ // e.response.status +
244
+ // ")",
245
+ // },
246
+ // ),
247
+ // ResponseError: e =>
248
+ // intl.value.formatMessage(
249
+ // { id: "handle.response_error" },
250
+ // { error: `${e.error}` },
251
+ // ),
252
+ ParseError: (e) => {
253
+ console.warn(e.toString())
254
+ return intl.value.formatMessage({ id: "validation.failed" })
255
+ }
256
+ }),
257
+ Match.orElse((e) =>
258
+ intl.value.formatMessage(
259
+ { id: "handle.unexpected_error" },
260
+ {
261
+ error: `${e.message ?? e._tag ?? e}`
262
+ }
263
+ )
264
+ )
265
+ )
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Pass a function that returns an Effect, e.g from a client action, give it a name, and optionally pass an onSuccess callback.
271
+ * Returns a tuple with state ref and execution function which reports errors as Toast.
272
+ */
273
+ const useAndHandleMutation: {
274
+ <I, E extends ResponseErrors, A, R>(
275
+ self: {
276
+ handler: (i: I) => Effect<A, E, R>
277
+ name: string
278
+ },
279
+ action: string,
280
+ options?: Opts<A>
281
+ ): Resp<I, A, E, R>
282
+ <E extends ResponseErrors, A, R>(
283
+ self: {
284
+ handler: Effect<A, E, R>
285
+ name: string
286
+ },
287
+ action: string,
288
+ options?: Opts<A>
289
+ ): ActResp<E, A, R>
290
+ } = (self: any, action: any, options?: Opts<any>) => {
291
+ const handleRequestWithToast = useHandleRequestWithToast()
292
+ const [a, b] = useSafeMutation(
293
+ {
294
+ handler: Effect.isEffect(self.handler)
295
+ ? (pipe(
296
+ Effect.annotateCurrentSpan({ action }),
297
+ Effect.andThen(self.handler)
298
+ ) as any)
299
+ : (...args: any[]) =>
300
+ pipe(
301
+ Effect.annotateCurrentSpan({ action }),
302
+ Effect.andThen(self.handler(...args))
303
+ ),
304
+ name: self.name
305
+ },
306
+ dropUndefinedT({
307
+ queryInvalidation: options?.queryInvalidation
308
+ })
309
+ )
310
+
311
+ return tuple(
312
+ computed(() => mutationResultToVue(a.value)),
313
+ handleRequestWithToast(b as any, action, options)
314
+ )
315
+ }
316
+
317
+ function makeUseAndHandleMutation(
318
+ defaultOptions?: Opts<any>
319
+ ) {
320
+ return ((self: any, action: any, options: any) => {
321
+ return useAndHandleMutation(
322
+ {
323
+ handler: self.handler,
324
+ name: self.name
325
+ },
326
+ action,
327
+ { ...defaultOptions, ...options }
328
+ )
329
+ }) as {
330
+ <I, E extends ResponseErrors, A, R>(
331
+ self: {
332
+ handler: (i: I) => Effect<A, E, R>
333
+ name: string
334
+ },
335
+ action: string,
336
+ options?: Opts<A>
337
+ ): Resp<I, A, E, R>
338
+ <E extends ResponseErrors, A>(
339
+ self: {
340
+ handler: Effect<A, E, R>
341
+ name: string
342
+ },
343
+ action: string,
344
+ options?: Opts<A>
345
+ ): ActResp<E, A, R>
346
+ }
347
+ }
348
+
349
+ const useSafeMutationWithState = <I, E, A>(self: {
350
+ handler: (i: I) => Effect<A, E, R>
351
+ name: string
352
+ }) => {
353
+ const [a, b] = useSafeMutation(self)
354
+
355
+ return tuple(
356
+ computed(() => mutationResultToVue(a.value)),
357
+ b
358
+ )
359
+ }
360
+
361
+ return {
362
+ useSafeMutationWithState,
363
+ useAndHandleMutation,
364
+ makeUseAndHandleMutation,
365
+ useHandleRequestWithToast
366
+ }
367
+ }
368
+
369
+ export const mapHandler: {
370
+ <I, E, R, A, E2, A2, R2>(
371
+ self: {
372
+ handler: (i: I) => Effect<A, E, R>
373
+ name: string
374
+ mapPath: (i: I) => string
375
+ },
376
+ map: (i: I) => (handler: Effect<A, E, R>) => Effect<A2, E2, R2>
377
+ ): {
378
+ handler: (i: I) => Effect<A2, E2, R2>
379
+ name: string
380
+ mapPath: (i: I) => string
381
+ }
382
+ <E, A, R, E2, A2, R2>(
383
+ self: {
384
+ handler: Effect<A, E, R>
385
+ name: string
386
+ mapPath: string
387
+ },
388
+ map: (handler: Effect<A, E, R>) => Effect<A2, E2, R2>
389
+ ): {
390
+ handler: Effect<A2, E2, R2>
391
+ name: string
392
+ mapPath: string
393
+ }
394
+ } = (self: any, map: any): any => ({
395
+ ...self,
396
+ handler: typeof self.handler === "function"
397
+ ? (i: any) => map(i)((self.handler as (i: any) => Effect<any, any, any>)(i))
398
+ : map(self.handler)
399
+ })
400
+
401
+ export function composeQueries<
402
+ R extends Record<string, Result.Result<any, any>>
403
+ >(
404
+ results: R,
405
+ renderPreviousOnFailure?: boolean
406
+ ): Result.Result<
407
+ {
408
+ [Property in keyof R]: R[Property] extends Result.Result<infer A, any> ? A
409
+ : never
410
+ },
411
+ {
412
+ [Property in keyof R]: R[Property] extends Result.Result<any, infer E> ? E
413
+ : never
414
+ }[keyof R]
415
+ > {
416
+ const values = renderPreviousOnFailure
417
+ ? Object.values(results).map(orPrevious)
418
+ : Object.values(results)
419
+ const error = values.find(Result.isFailure)
420
+ if (error) {
421
+ return error
422
+ }
423
+ const initial = Array.findFirst(values, (x) => x._tag === "Initial" ? Option.some(x) : Option.none())
424
+ if (initial.value !== undefined) {
425
+ return initial.value
426
+ }
427
+ const loading = Array.findFirst(values, (x) => Result.isInitial(x) && x.waiting ? Option.some(x) : Option.none())
428
+ if (loading.value !== undefined) {
429
+ return loading.value
430
+ }
431
+
432
+ const isRefreshing = values.some((x) => x.waiting)
433
+
434
+ const r = Object.entries(results).reduce((prev, [key, value]) => {
435
+ prev[key] = Result.value(value).value
436
+ return prev
437
+ }, {} as any)
438
+ return Result.success(r, isRefreshing)
439
+ }
440
+
441
+ function orPrevious<E, A>(result: Result.Result<A, E>) {
442
+ return Result.isFailure(result) && Option.isSome(result.previousValue)
443
+ ? Result.success(result.previousValue.value, result.waiting)
444
+ : result
445
+ }