@effect-app/vue 2.14.0 → 2.15.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/_cjs/index.cjs +0 -22
  3. package/_cjs/index.cjs.map +1 -1
  4. package/_cjs/makeClient.cjs +169 -72
  5. package/_cjs/makeClient.cjs.map +1 -1
  6. package/_cjs/mutate.cjs +84 -101
  7. package/_cjs/mutate.cjs.map +1 -1
  8. package/_cjs/query.cjs +9 -52
  9. package/_cjs/query.cjs.map +1 -1
  10. package/dist/index.d.ts +0 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -3
  13. package/dist/makeClient.d.ts +87 -70
  14. package/dist/makeClient.d.ts.map +1 -1
  15. package/dist/makeClient.js +164 -84
  16. package/dist/mutate.d.ts +18 -26
  17. package/dist/mutate.d.ts.map +1 -1
  18. package/dist/mutate.js +67 -84
  19. package/dist/query.d.ts +10 -24
  20. package/dist/query.d.ts.map +1 -1
  21. package/dist/query.js +13 -55
  22. package/package.json +8 -48
  23. package/src/index.ts +0 -2
  24. package/src/makeClient.ts +463 -227
  25. package/src/mutate.ts +111 -141
  26. package/src/query.ts +30 -116
  27. package/_cjs/hooks.cjs +0 -28
  28. package/_cjs/hooks.cjs.map +0 -1
  29. package/_cjs/makeClient2.cjs +0 -221
  30. package/_cjs/makeClient2.cjs.map +0 -1
  31. package/_cjs/mutate2.cjs +0 -118
  32. package/_cjs/mutate2.cjs.map +0 -1
  33. package/_cjs/query2.cjs +0 -105
  34. package/_cjs/query2.cjs.map +0 -1
  35. package/dist/hooks.d.ts +0 -3
  36. package/dist/hooks.d.ts.map +0 -1
  37. package/dist/hooks.js +0 -4
  38. package/dist/makeClient2.d.ts +0 -74
  39. package/dist/makeClient2.d.ts.map +0 -1
  40. package/dist/makeClient2.js +0 -187
  41. package/dist/mutate2.d.ts +0 -42
  42. package/dist/mutate2.d.ts.map +0 -1
  43. package/dist/mutate2.js +0 -88
  44. package/dist/query2.d.ts +0 -23
  45. package/dist/query2.d.ts.map +0 -1
  46. package/dist/query2.js +0 -97
  47. package/src/hooks.ts +0 -4
  48. package/src/makeClient2.ts +0 -353
  49. package/src/mutate2.ts +0 -197
  50. package/src/query2.ts +0 -205
@@ -1,353 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import * as Sentry from "@sentry/browser"
3
- import { Cause, Effect, Exit, Match, Option, Runtime, S, Struct } from "effect-app"
4
- import type { RequestHandler, RequestHandlerWithInput, TaggedRequestClassAny } from "effect-app/client/clientFor"
5
- import { flow, pipe, tuple } from "effect-app/Function"
6
- import { OperationSuccess } from "effect-app/Operations"
7
- import type { Schema } from "effect-app/Schema"
8
- import { dropUndefinedT } from "effect-app/utils"
9
- import type { ComputedRef, Ref, ShallowRef } from "vue"
10
- import { computed, ref, watch } from "vue"
11
- import { buildFieldInfoFromFieldsRoot } from "./form.js"
12
- import { getRuntime } from "./lib.js"
13
- import type { Opts, ResponseErrors } from "./makeClient.js"
14
- import type { MakeIntlReturn } from "./makeIntl.js"
15
- import { mutationResultToVue } from "./mutate.js"
16
- import type { Res } from "./mutate.js"
17
- import { makeMutation2 } from "./mutate2.js"
18
- import { makeQuery2 } from "./query2.js"
19
-
20
- type WithAction<A> = A & {
21
- action: string
22
- }
23
-
24
- // computed() takes a getter function and returns a readonly reactive ref
25
- // object for the returned value from the getter.
26
- type Resp<I, A, E, R> = readonly [
27
- ComputedRef<Res<A, E>>,
28
- WithAction<(I: I) => Effect<Exit<A, E>, never, R>>
29
- ]
30
-
31
- type ActResp<A, E, R> = readonly [
32
- ComputedRef<Res<A, E>>,
33
- WithAction<Effect<Exit<A, E>, never, R>>
34
- ]
35
-
36
- export const makeClient2 = <Locale extends string, R>(
37
- useIntl: MakeIntlReturn<Locale>["useIntl"],
38
- useToast: () => {
39
- error: (message: string) => void
40
- warning: (message: string) => void
41
- success: (message: string) => void
42
- },
43
- runtime: ShallowRef<Runtime.Runtime<R> | undefined>,
44
- messages: Record<string, string | undefined> = {}
45
- ) => {
46
- const useSafeMutation = makeMutation2()
47
- const useSafeQuery = makeQuery2(runtime)
48
- const useHandleRequestWithToast = () => {
49
- const toast = useToast()
50
- const { intl } = useIntl()
51
-
52
- return handleRequestWithToast
53
- /**
54
- * Pass a function that returns a Promise.
55
- * Returns an execution function which reports errors as Toast.
56
- */
57
- function handleRequestWithToast<
58
- E extends ResponseErrors,
59
- A,
60
- R,
61
- Args extends unknown[]
62
- >(
63
- f: Effect<A, E, R> | ((...args: Args) => Effect<A, E, R>),
64
- action: string,
65
- options: Opts<A> = { suppressErrorToast: false }
66
- ) {
67
- const message = messages[action] ?? action
68
- const warnMessage = intl.value.formatMessage(
69
- { id: "handle.with_warnings" },
70
- { action: message }
71
- )
72
- const successMessage = intl.value.formatMessage(
73
- { id: "handle.success" },
74
- { action: message }
75
- )
76
- const errorMessage = intl.value.formatMessage(
77
- { id: "handle.with_errors" },
78
- { action: message }
79
- )
80
- const handleEffect = (self: Effect<A, E, R>) =>
81
- self.pipe(
82
- Effect.exit,
83
- Effect.tap(
84
- Exit.matchEffect({
85
- onSuccess: (r) =>
86
- Effect.gen(function*() {
87
- if (options.suppressSuccessToast) {
88
- return
89
- }
90
- toast.success(
91
- successMessage
92
- + (S.is(OperationSuccess)(r) && r.message
93
- ? "\n" + r.message
94
- : "")
95
- )
96
- }),
97
- onFailure: (err) =>
98
- Effect.gen(function*() {
99
- if (Cause.isInterruptedOnly(err)) {
100
- return
101
- }
102
-
103
- const fail = Cause.failureOption(err)
104
- if (Option.isSome(fail)) {
105
- if (fail.value._tag === "SuppressErrors") {
106
- return Effect.succeed(void 0)
107
- }
108
-
109
- if (fail.value._tag === "OperationFailure") {
110
- toast.warning(
111
- warnMessage + fail.value.message
112
- ? "\n" + fail.value.message
113
- : ""
114
- )
115
- return
116
- }
117
-
118
- if (!options.suppressErrorToast) {
119
- toast.error(`${errorMessage}:\n` + renderError(fail.value))
120
- }
121
-
122
- console.warn(fail, fail.toString())
123
- return
124
- }
125
-
126
- const extra = {
127
- action,
128
- message: `Unexpected Error trying to ${action}`
129
- }
130
- Sentry.captureException(err, { extra })
131
- console.error(err, extra)
132
-
133
- toast.error(
134
- intl.value.formatMessage(
135
- { id: "handle.unexpected_error" },
136
- {
137
- action: message,
138
- error: JSON.stringify(err, undefined, 2)
139
- }
140
- )
141
- )
142
- })
143
- })
144
- )
145
- )
146
- return Object.assign(
147
- Effect.isEffect(f)
148
- ? pipe(
149
- f,
150
- handleEffect
151
- )
152
- : flow(
153
- f,
154
- handleEffect
155
- ),
156
- { action }
157
- )
158
- }
159
-
160
- function renderError(e: ResponseErrors): string {
161
- return Match.value(e).pipe(
162
- Match.tags({
163
- // HttpErrorRequest: e =>
164
- // intl.value.formatMessage(
165
- // { id: "handle.request_error" },
166
- // { error: `${e.error}` },
167
- // ),
168
- // HttpErrorResponse: e =>
169
- // e.response.status >= 500 ||
170
- // e.response.body._tag !== "Some" ||
171
- // !e.response.body.value
172
- // ? intl.value.formatMessage(
173
- // { id: "handle.error_response" },
174
- // {
175
- // error: `${
176
- // e.response.body._tag === "Some" && e.response.body.value
177
- // ? parseError(e.response.body.value)
178
- // : "Unknown"
179
- // } (${e.response.status})`,
180
- // },
181
- // )
182
- // : intl.value.formatMessage(
183
- // { id: "handle.unexpected_error" },
184
- // {
185
- // error:
186
- // JSON.stringify(e.response.body, undefined, 2) +
187
- // "( " +
188
- // e.response.status +
189
- // ")",
190
- // },
191
- // ),
192
- // ResponseError: e =>
193
- // intl.value.formatMessage(
194
- // { id: "handle.response_error" },
195
- // { error: `${e.error}` },
196
- // ),
197
- ParseError: (e) => {
198
- console.warn(e.toString())
199
- return intl.value.formatMessage({ id: "validation.failed" })
200
- }
201
- }),
202
- Match.orElse((e) =>
203
- intl.value.formatMessage(
204
- { id: "handle.unexpected_error" },
205
- {
206
- error: `${e.message ?? e._tag ?? e}`
207
- }
208
- )
209
- )
210
- )
211
- }
212
- }
213
-
214
- /**
215
- * Pass a function that returns an Effect, e.g from a client action, give it a name, and optionally pass an onOperationSuccess callback.
216
- * Returns a tuple with state ref and execution function which reports errors as Toast.
217
- */
218
- const useAndHandleMutation: {
219
- <I, E extends ResponseErrors, A, R, Request extends TaggedRequestClassAny>(
220
- self: RequestHandlerWithInput<I, A, E, R, Request>,
221
- action: string,
222
- options?: Opts<A>
223
- ): Resp<I, void, never, R>
224
- <E extends ResponseErrors, A, R, Request extends TaggedRequestClassAny>(
225
- self: RequestHandler<A, E, R, Request>,
226
- action: string,
227
- options?: Opts<A>
228
- ): ActResp<void, never, R>
229
- } = (self: any, action: any, options?: Opts<any>): any => {
230
- const handleRequestWithToast = useHandleRequestWithToast()
231
- const [a, b] = useSafeMutation(
232
- {
233
- ...self,
234
- handler: Effect.isEffect(self.handler)
235
- ? (pipe(
236
- Effect.annotateCurrentSpan({ action }),
237
- Effect.andThen(self.handler)
238
- ) as any)
239
- : (...args: any[]) =>
240
- pipe(
241
- Effect.annotateCurrentSpan({ action }),
242
- Effect.andThen(self.handler(...args))
243
- )
244
- },
245
- options ? dropUndefinedT(options) : undefined
246
- )
247
-
248
- return tuple(
249
- computed(() => mutationResultToVue(a.value)),
250
- handleRequestWithToast(b as any, action, options)
251
- )
252
- }
253
-
254
- function makeUseAndHandleMutation(
255
- defaultOptions?: Opts<any>
256
- ) {
257
- return ((self: any, action: any, options: any) => {
258
- return useAndHandleMutation(
259
- self,
260
- action,
261
- { ...defaultOptions, ...options }
262
- )
263
- }) as unknown as {
264
- <I, E extends ResponseErrors, A, R, Request extends TaggedRequestClassAny>(
265
- self: RequestHandlerWithInput<I, A, E, R, Request>,
266
- action: string,
267
- options?: Opts<A>
268
- ): Resp<I, A, E, R>
269
- <E extends ResponseErrors, A, Request extends TaggedRequestClassAny>(
270
- self: RequestHandler<A, E, R, Request>,
271
- action: string,
272
- options?: Opts<A>
273
- ): ActResp<A, E, R>
274
- }
275
- }
276
-
277
- const useSafeMutationWithState = <I, E, A, Request extends TaggedRequestClassAny>(
278
- self: RequestHandlerWithInput<I, A, E, R, Request>
279
- ) => {
280
- const [a, b] = useSafeMutation(self)
281
-
282
- return tuple(
283
- computed(() => mutationResultToVue(a.value)),
284
- b
285
- )
286
- }
287
-
288
- const buildFormFromSchema = <
289
- From extends Record<PropertyKey, any>,
290
- To extends Record<PropertyKey, any>,
291
- C extends Record<PropertyKey, any>,
292
- OnSubmitA
293
- >(
294
- s:
295
- & Schema<
296
- To,
297
- From,
298
- R
299
- >
300
- & { new(c: C): any; fields: S.Struct.Fields },
301
- state: Ref<Omit<From, "_tag">>,
302
- onSubmit: (a: To) => Effect<OnSubmitA, never, R>
303
- ) => {
304
- const fields = buildFieldInfoFromFieldsRoot(s).fields
305
- const schema = S.Struct(Struct.omit(s.fields, "_tag")) as any
306
- const parse = S.decodeUnknown<any, any, R>(schema)
307
- const isDirty = ref(false)
308
- const isValid = ref(true)
309
- const runPromise = Runtime.runPromise(getRuntime(runtime))
310
-
311
- const submit1 =
312
- (onSubmit: (a: To) => Effect<OnSubmitA, never, R>) => async <T extends Promise<{ valid: boolean }>>(e: T) => {
313
- const r = await e
314
- if (!r.valid) return
315
- return runPromise(onSubmit(new s(await runPromise(parse(state.value)))))
316
- }
317
- const submit = submit1(onSubmit)
318
-
319
- watch(
320
- state,
321
- (v) => {
322
- // TODO: do better
323
- isDirty.value = JSON.stringify(v) !== JSON.stringify(state.value)
324
- },
325
- { deep: true }
326
- )
327
-
328
- const submitFromState = Effect.gen(function*() {
329
- if (!isValid.value) return
330
- return yield* onSubmit(yield* parse(state.value))
331
- }) // () => submit(Promise.resolve({ valid: isValid.value }))
332
-
333
- return {
334
- fields,
335
- /** optimized for Vuetify v-form submit callback */
336
- submit,
337
- /** optimized for Native form submit callback or general use */
338
- submitFromState,
339
- isDirty,
340
- isValid
341
- }
342
- }
343
-
344
- return {
345
- useSafeMutationWithState,
346
- useAndHandleMutation,
347
- makeUseAndHandleMutation,
348
- useHandleRequestWithToast,
349
- buildFormFromSchema,
350
- useSafeQuery,
351
- useSafeMutation
352
- }
353
- }
package/src/mutate2.ts DELETED
@@ -1,197 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import * as Result from "@effect-rx/rx/Result"
3
- import type { InvalidateOptions, InvalidateQueryFilters } from "@tanstack/vue-query"
4
- import { useQueryClient } from "@tanstack/vue-query"
5
- import { Cause, Effect, Exit, Option } from "effect-app"
6
- import type { RequestHandler, RequestHandlerWithInput, TaggedRequestClassAny } from "effect-app/client/clientFor"
7
- import { tuple } from "effect-app/Function"
8
- import type { ComputedRef, Ref } from "vue"
9
- import { computed, ref, shallowRef } from "vue"
10
- import { reportRuntimeError } from "./lib.js"
11
- import { getQueryKey } from "./mutate.js"
12
-
13
- export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
14
- export function make<A, E, R>(self: Effect<A, E, R>) {
15
- const result = shallowRef(Result.initial() as Result.Result<A, E>)
16
-
17
- const execute = Effect
18
- .sync(() => {
19
- result.value = Result.waiting(result.value)
20
- })
21
- .pipe(
22
- Effect.andThen(self),
23
- Effect.exit,
24
- Effect.andThen(Result.fromExit),
25
- Effect.flatMap((r) => Effect.sync(() => result.value = r))
26
- )
27
-
28
- const latestSuccess = computed(() => Option.getOrUndefined(Result.value(result.value)))
29
-
30
- return tuple(result, latestSuccess, execute)
31
- }
32
-
33
- export interface MutationInitial {
34
- readonly _tag: "Initial"
35
- }
36
-
37
- export interface MutationLoading {
38
- readonly _tag: "Loading"
39
- }
40
-
41
- export interface MutationSuccess<A> {
42
- readonly _tag: "Success"
43
- readonly data: A
44
- }
45
-
46
- export interface MutationError<E> {
47
- readonly _tag: "Error"
48
- readonly error: E
49
- }
50
-
51
- export type MutationResult<A, E> = MutationInitial | MutationLoading | MutationSuccess<A> | MutationError<E>
52
-
53
- export type MaybeRef<T> = Ref<T> | ComputedRef<T> | T
54
- type MaybeRefDeep<T> = MaybeRef<
55
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
56
- T extends Function ? T
57
- : T extends object ? {
58
- [Property in keyof T]: MaybeRefDeep<T[Property]>
59
- }
60
- : T
61
- >
62
-
63
- export interface MutationOptions<A, I = void> {
64
- queryInvalidation?: (defaultKey: string[], name: string) => {
65
- filters?: MaybeRefDeep<InvalidateQueryFilters> | undefined
66
- options?: MaybeRefDeep<InvalidateOptions> | undefined
67
- }[]
68
- /** @deprecated use mapHandler with Effect.andThen/tap accordingly */
69
- onSuccess?: (a: A, i: I) => Promise<unknown>
70
- }
71
-
72
- // TODO: more efficient invalidation, including args etc
73
- // return Effect.promise(() => queryClient.invalidateQueries({
74
- // predicate: (_) => nses.includes(_.queryKey.filter((_) => _.startsWith("$")).join("/"))
75
- // }))
76
- /*
77
- // const nses: string[] = []
78
- // for (let i = 0; i < ns.length; i++) {
79
- // nses.push(ns.slice(0, i + 1).join("/"))
80
- // }
81
- */
82
-
83
- export const makeMutation2 = () => {
84
- /**
85
- * Pass a function that returns an Effect, e.g from a client action, or an Effect
86
- * Returns a tuple with state ref and execution function which reports errors as Toast.
87
- */
88
- const useSafeMutation: {
89
- <I, E, A, R, Request extends TaggedRequestClassAny>(
90
- self: RequestHandlerWithInput<I, A, E, R, Request>,
91
- options?: MutationOptions<A, I>
92
- ): readonly [
93
- Readonly<Ref<MutationResult<A, E>>>,
94
- (i: I) => Effect<A, E, R>
95
- ]
96
- <E, A, R, Request extends TaggedRequestClassAny>(
97
- self: RequestHandler<A, E, R, Request>,
98
- options?: MutationOptions<A>
99
- ): readonly [
100
- Readonly<Ref<MutationResult<A, E>>>,
101
- Effect<A, E, R>
102
- ]
103
- } = <I, E, A, R, Request extends TaggedRequestClassAny>(
104
- self: RequestHandlerWithInput<I, A, E, R, Request> | RequestHandler<A, E, R, Request>,
105
- options?: MutationOptions<A, I>
106
- ) => {
107
- const queryClient = useQueryClient()
108
- const state: Ref<MutationResult<A, E>> = ref<MutationResult<A, E>>({ _tag: "Initial" }) as any
109
-
110
- const invalidateQueries = (
111
- filters?: MaybeRefDeep<InvalidateQueryFilters>,
112
- options?: MaybeRefDeep<InvalidateOptions>
113
- ) => Effect.promise(() => queryClient.invalidateQueries(filters, options))
114
-
115
- function handleExit(exit: Exit.Exit<A, E>) {
116
- return Effect.sync(() => {
117
- if (Exit.isSuccess(exit)) {
118
- state.value = { _tag: "Success", data: exit.value }
119
- return
120
- }
121
-
122
- const err = Cause.failureOption(exit.cause)
123
- if (Option.isSome(err)) {
124
- state.value = { _tag: "Error", error: err.value }
125
- return
126
- }
127
- })
128
- }
129
-
130
- const invalidateCache = Effect.suspend(() => {
131
- const queryKey = getQueryKey(self)
132
-
133
- if (options?.queryInvalidation) {
134
- const opts = options.queryInvalidation(queryKey, self.name)
135
- if (!opts.length) {
136
- return Effect.void
137
- }
138
- return Effect
139
- .andThen(
140
- Effect.annotateCurrentSpan({ queryKey, opts }),
141
- Effect.forEach(opts, (_) => invalidateQueries(_.filters, _.options), { concurrency: "inherit" })
142
- )
143
- .pipe(Effect.withSpan("client.query.invalidation", { captureStackTrace: false }))
144
- }
145
-
146
- if (!queryKey) return Effect.void
147
-
148
- return Effect
149
- .andThen(
150
- Effect.annotateCurrentSpan({ queryKey }),
151
- invalidateQueries({ queryKey })
152
- )
153
- .pipe(Effect.withSpan("client.query.invalidation", { captureStackTrace: false }))
154
- })
155
-
156
- const onSuccess = options?.onSuccess
157
-
158
- const handler = self.handler
159
- const r = Effect.isEffect(handler)
160
- ? tuple(
161
- state,
162
- Effect
163
- .sync(() => {
164
- state.value = { _tag: "Loading" }
165
- })
166
- .pipe(
167
- Effect.zipRight(handler),
168
- Effect.tap(invalidateCache),
169
- Effect.tap((a) => onSuccess ? Effect.promise(() => onSuccess(a, undefined as I)) : Effect.void),
170
- Effect.tapDefect(reportRuntimeError),
171
- Effect.onExit(handleExit),
172
- Effect.withSpan(`mutation ${self.name}`, { captureStackTrace: false })
173
- )
174
- )
175
- : tuple(state, (fst: I) => {
176
- const effect = handler(fst)
177
- return Effect
178
- .sync(() => {
179
- state.value = { _tag: "Loading" }
180
- })
181
- .pipe(
182
- Effect.zipRight(effect),
183
- Effect.tapBoth({ onFailure: () => invalidateCache, onSuccess: () => invalidateCache }),
184
- Effect.tap((a) => onSuccess ? Effect.promise(() => onSuccess(a, fst)) : Effect.void),
185
- Effect.tapDefect(reportRuntimeError),
186
- Effect.onExit(handleExit),
187
- Effect.withSpan(`mutation ${self.name}`, { captureStackTrace: false })
188
- )
189
- })
190
-
191
- return r as any
192
- }
193
- return useSafeMutation
194
- }
195
-
196
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
197
- export interface MakeMutation2 extends ReturnType<typeof makeMutation2> {}