@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
package/src/makeClient.ts CHANGED
@@ -1,16 +1,21 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import * as Sentry from "@sentry/browser"
3
- import type { Either } from "effect-app"
4
- import { Cause, Effect, Match, Runtime, S } from "effect-app"
5
- import { type SupportedErrors } from "effect-app/client"
6
- import { flow, pipe, tuple } from "effect-app/Function"
3
+ import { Cause, Effect, Exit, Match, Option, Runtime, S, Struct } from "effect-app"
4
+ import { CauseException, type SupportedErrors } from "effect-app/client"
5
+ import type { RequestHandler, RequestHandlerWithInput, TaggedRequestClassAny } from "effect-app/client/clientFor"
6
+ import { constant, pipe, tuple } from "effect-app/Function"
7
7
  import type { OperationFailure } from "effect-app/Operations"
8
8
  import { OperationSuccess } from "effect-app/Operations"
9
+ import type { Schema } from "effect-app/Schema"
9
10
  import { dropUndefinedT } from "effect-app/utils"
10
- import { computed, type ComputedRef } from "vue"
11
+ import type { ComputedRef, Ref, ShallowRef } from "vue"
12
+ import { computed, ref, watch } from "vue"
13
+ import { buildFieldInfoFromFieldsRoot } from "./form.js"
14
+ import { getRuntime } from "./lib.js"
11
15
  import type { MakeIntlReturn } from "./makeIntl.js"
12
- import type { MakeMutation, MutationOptions, Res } from "./mutate.js"
13
- import { mutationResultToVue } from "./mutate.js"
16
+ import { makeMutation2, mutationResultToVue } from "./mutate.js"
17
+ import type { MutationOptions, Res } from "./mutate.js"
18
+ import { makeQuery2 } from "./query.js"
14
19
 
15
20
  /**
16
21
  * Use this after handling an error yourself, still continueing on the Error track, but the error will not be reported.
@@ -21,86 +26,55 @@ export class SuppressErrors extends Cause.YieldableError {
21
26
 
22
27
  export type ResponseErrors = S.ParseResult.ParseError | SupportedErrors | SuppressErrors | OperationFailure
23
28
 
24
- export interface Opts<A, I = void> extends MutationOptions<A, I> {
25
- suppressErrorToast?: boolean
26
- suppressSuccessToast?: boolean
27
- successToast?: (a: A) => any
29
+ export interface Opts<
30
+ A,
31
+ E,
32
+ I = void,
33
+ ESuccess = never,
34
+ RSuccess = never,
35
+ EError = never,
36
+ RError = never,
37
+ EDefect = never,
38
+ RDefect = never
39
+ > extends MutationOptions {
40
+ /** set to `undefined` to use default message */
41
+ successMessage?: ((a: A, i: I) => Effect<string | undefined, ESuccess, RSuccess>) | undefined
42
+ /** set to `undefined` to use default message */
43
+ failMessage?: ((e: E, i: I) => Effect<string | undefined, EError, RError>) | undefined
44
+ /** set to `undefined` to use default message */
45
+ defectMessage?: ((e: Cause.Cause<E>, i: I) => Effect<string | undefined, EDefect, RDefect>) | undefined
28
46
  }
29
47
 
30
- /** @deprecated - use mapHandler */
31
- export const withSuccess: {
32
- <I, A, E, X, R>(
33
- self: {
34
- handler: (i: I) => Effect<A, E, R>
35
- name: string
36
- },
37
- onSuccess: (a: A, i: I) => Promise<X>
38
- ): {
39
- handler: (i: I) => Effect<X, E, R>
40
- name: string
41
- }
42
- <A, E, X, R>(
43
- self: {
44
- handler: Effect<A, E, R>
45
- name: string
46
- },
47
- onSuccess: (_: A) => Promise<X>
48
- ): {
49
- handler: Effect<X, E, R>
50
- name: string
51
- }
52
- } = (self: any, onSuccess: any): any => ({
53
- ...self,
54
- handler: typeof self.handler === "function"
55
- ? (i: any) =>
56
- pipe(
57
- (
58
- self.handler as (
59
- i: any
60
- ) => Effect<any, any, any>
61
- )(i),
62
- Effect.flatMap((_) =>
63
- Effect.promise(() => onSuccess(_, i)).pipe(
64
- Effect.withSpan("onSuccess")
65
- )
66
- )
67
- )
68
- : Effect.flatMap(self.handler, (_) => Effect.promise(() => onSuccess(_)).pipe(Effect.withSpan("onSuccess")))
69
- })
70
-
71
- /** @deprecated - use mapHandler */
72
- export const withSuccessE: {
73
- <I, E, A, E2, X, R>(
74
- self: {
75
- handler: (i: I) => Effect<A, E, R>
76
- name: string
77
- },
78
- onSuccessE: (_: A, i: I) => Effect<X, E2>
79
- ): {
80
- handler: (i: I) => Effect<X, E | E2, R>
81
- name: string
82
- }
83
- <E, A, E2, X, R>(
84
- self: {
85
- handler: Effect<A, E, R>
86
- name: string
87
- },
88
- onSuccessE: (_: A) => Effect<X, E2>
89
- ): {
90
- handler: Effect<X, E | E2, R>
91
- name: string
92
- }
93
- } = (self: any, onSuccessE: any): any => {
94
- return {
95
- ...self,
96
- handler: typeof self.handler === "function"
97
- ? (i: any) =>
98
- pipe(
99
- self.handler(i),
100
- Effect.flatMap((_) => onSuccessE(_, i))
101
- )
102
- : Effect.flatMap(self.handler, (_) => onSuccessE(_))
103
- }
48
+ export interface LowOpts<
49
+ A,
50
+ E,
51
+ I = void,
52
+ ESuccess = never,
53
+ RSuccess = never,
54
+ EError = never,
55
+ RError = never,
56
+ EDefect = never,
57
+ RDefect = never
58
+ > {
59
+ onSuccess: (a: A, i: I) => Effect<void, ESuccess, RSuccess>
60
+ onFail: (e: E, i: I) => Effect<void, EError, RError>
61
+ onDefect: (e: Cause.Cause<E>, i: I) => Effect<void, EDefect, RDefect>
62
+ }
63
+
64
+ export interface LowOptsOptional<
65
+ A,
66
+ E,
67
+ I = void,
68
+ ESuccess = never,
69
+ RSuccess = never,
70
+ EError = never,
71
+ RError = never,
72
+ EDefect = never,
73
+ RDefect = never
74
+ > extends MutationOptions {
75
+ onSuccess?: (a: A, i: I) => Effect<void, ESuccess, RSuccess>
76
+ onFail?: (e: E, i: I) => Effect<void, EError, RError>
77
+ onDefect?: (e: Cause.Cause<E>, i: I) => Effect<void, EDefect, RDefect>
104
78
  }
105
79
 
106
80
  type WithAction<A> = A & {
@@ -109,26 +83,113 @@ type WithAction<A> = A & {
109
83
 
110
84
  // computed() takes a getter function and returns a readonly reactive ref
111
85
  // object for the returned value from the getter.
112
- type Resp<I, E, A> = readonly [
86
+ type Resp<I, A, E, R> = readonly [
113
87
  ComputedRef<Res<A, E>>,
114
- WithAction<(I: I) => Promise<void>>
88
+ WithAction<(I: I) => Effect<Exit<A, E>, never, R>>
115
89
  ]
116
90
 
117
- type ActResp<E, A> = readonly [
91
+ type ActResp<A, E, R> = readonly [
118
92
  ComputedRef<Res<A, E>>,
119
- WithAction<() => Promise<void>>
93
+ WithAction<Effect<Exit<A, E>, never, R>>
120
94
  ]
121
95
 
122
- export const makeClient = <Locale extends string, R>(
96
+ export const suppressToast = constant(Effect.succeed(undefined))
97
+
98
+ export function handleRequest<
99
+ E extends ResponseErrors,
100
+ A,
101
+ R,
102
+ I = void,
103
+ ESuccess = never,
104
+ RSuccess = never,
105
+ EError = never,
106
+ RError = never,
107
+ EDefect = never,
108
+ RDefect = never
109
+ >(
110
+ f: Effect<A, E, R> | ((i: I) => Effect<A, E, R>),
111
+ action: string,
112
+ options: {
113
+ onSuccess: (a: A, i: I) => Effect<void, ESuccess, RSuccess>
114
+ onFail: (e: E, i: I) => Effect<void, EError, RError>
115
+ onDefect: (e: Cause.Cause<E>, i: I) => Effect<void, EDefect, RDefect>
116
+ }
117
+ ) {
118
+ const handleEffect = (i: any) => (self: Effect<A, E, R>) =>
119
+ self.pipe(
120
+ Effect.exit,
121
+ Effect.tap(
122
+ Exit.matchEffect({
123
+ onSuccess: (r) => options.onSuccess(r, i),
124
+ onFailure: (cause) =>
125
+ Effect.gen(function*() {
126
+ if (Cause.isInterruptedOnly(cause)) {
127
+ console.info(`Interrupted while trying to ${action}`)
128
+ return
129
+ }
130
+
131
+ const fail = Cause.failureOption(cause)
132
+ if (Option.isSome(fail)) {
133
+ if (fail.value._tag === "SuppressErrors") {
134
+ console.info(`Suppressed error trying to ${action}`, fail.value)
135
+ return
136
+ }
137
+ const message = `Failure trying to ${action}`
138
+ console.warn(message, fail.value)
139
+ Sentry.captureMessage(message, { extra: { action, error: fail.value } })
140
+ yield* options.onFail(fail.value, i)
141
+ return
142
+ }
143
+
144
+ const extra = {
145
+ action,
146
+ message: `Unexpected Error trying to ${action}`
147
+ }
148
+ console.error(extra.message, cause)
149
+ Sentry.captureException(new CauseException(cause, "defect"), { extra })
150
+
151
+ yield* options.onDefect(cause, i)
152
+ })
153
+ })
154
+ ),
155
+ Effect.tapErrorCause((cause) =>
156
+ Effect.sync(() => {
157
+ const extra = {
158
+ action,
159
+ message: `Unexpected Error trying to handle errors for ${action}`
160
+ }
161
+ Sentry.captureException(new CauseException(cause, "unhandled"), { extra })
162
+ console.error(Cause.pretty(cause), extra)
163
+ })
164
+ )
165
+ )
166
+ return Object.assign(
167
+ Effect.isEffect(f)
168
+ ? pipe(
169
+ f,
170
+ handleEffect(void 0)
171
+ )
172
+ : (i: I) =>
173
+ pipe(
174
+ f(i),
175
+ handleEffect(i)
176
+ ),
177
+ { action }
178
+ )
179
+ }
180
+
181
+ export const makeClient2 = <Locale extends string, R>(
123
182
  useIntl: MakeIntlReturn<Locale>["useIntl"],
124
183
  useToast: () => {
125
184
  error: (message: string) => void
126
185
  warning: (message: string) => void
127
186
  success: (message: string) => void
128
187
  },
129
- useSafeMutation: MakeMutation<R>,
188
+ runtime: ShallowRef<Runtime.Runtime<R> | undefined>,
130
189
  messages: Record<string, string | undefined> = {}
131
190
  ) => {
191
+ const useSafeMutation = makeMutation2()
192
+ const useSafeQuery = makeQuery2(runtime)
132
193
  const useHandleRequestWithToast = () => {
133
194
  const toast = useToast()
134
195
  const { intl } = useIntl()
@@ -141,91 +202,78 @@ export const makeClient = <Locale extends string, R>(
141
202
  function handleRequestWithToast<
142
203
  E extends ResponseErrors,
143
204
  A,
144
- Args extends unknown[]
205
+ R,
206
+ I = void,
207
+ ESuccess = never,
208
+ RSuccess = never,
209
+ EError = never,
210
+ RError = never,
211
+ EDefect = never,
212
+ RDefect = never
145
213
  >(
146
- f: (...args: Args) => Promise<Either<A, E>>,
214
+ f: Effect<A, E, R> | ((i: I) => Effect<A, E, R>),
147
215
  action: string,
148
- options: Opts<A> = { suppressErrorToast: false }
216
+ options: Opts<A, E, I, ESuccess, RSuccess, EError, RError, EDefect, RDefect> = {}
149
217
  ) {
150
- const message = messages[action] ?? action
151
- const warnMessage = intl.value.formatMessage(
218
+ const actionMessage = messages[action] ?? action
219
+ const defaultWarnMessage = intl.value.formatMessage(
152
220
  { id: "handle.with_warnings" },
153
- { action: message }
221
+ { action: actionMessage }
154
222
  )
155
- const successMessage = intl.value.formatMessage(
223
+ const defaultSuccessMessage = intl.value.formatMessage(
156
224
  { id: "handle.success" },
157
- { action: message }
225
+ { action: actionMessage }
158
226
  )
159
- const errorMessage = intl.value.formatMessage(
227
+ const defaultErrorMessage = intl.value.formatMessage(
160
228
  { id: "handle.with_errors" },
161
- { action: message }
229
+ { action: actionMessage }
162
230
  )
163
- return Object.assign(
164
- flow(f, (p) =>
165
- p.then(
166
- (r) => {
167
- if (r._tag === "Right") {
168
- if (options.suppressSuccessToast) {
169
- return
170
- }
171
- return Promise
172
- .resolve(
173
- toast.success(
174
- successMessage
175
- + (S.is(OperationSuccess)(r.right) && r.right.message
176
- ? "\n" + r.right.message
177
- : "")
178
- )
179
- )
180
- .then((_) => {})
181
- }
182
- if (r.left._tag === "SuppressErrors") {
183
- return Promise.resolve(void 0)
184
- }
185
231
 
186
- if (r.left._tag === "OperationFailure") {
187
- return toast.warning(
188
- warnMessage + r.left.message
189
- ? "\n" + r.left.message
190
- : ""
191
- )
192
- }
193
- if (!options.suppressErrorToast) {
194
- toast.error(`${errorMessage}:\n` + renderError(r.left))
195
- }
232
+ return handleRequest<E, A, R, any, ESuccess, RSuccess, EError, RError, EDefect, RDefect>(f, action, {
233
+ onSuccess: (a, i) =>
234
+ Effect.gen(function*() {
235
+ const message = options.successMessage ? yield* options.successMessage(a, i) : defaultSuccessMessage
236
+ + (S.is(OperationSuccess)(a) && a.message
237
+ ? "\n" + a.message
238
+ : "")
239
+ if (message) {
240
+ toast.success(message)
241
+ }
242
+ }),
243
+ onFail: (e, i) =>
244
+ Effect.gen(function*() {
245
+ if (!options.failMessage && e._tag === "OperationFailure") {
246
+ toast.warning(
247
+ defaultWarnMessage + e.message
248
+ ? "\n" + e.message
249
+ : ""
250
+ )
251
+ return
252
+ }
196
253
 
197
- console.warn(r.left, r.left.toString())
198
- },
199
- (err) => {
200
- if (
201
- Cause.isInterruptedException(err)
202
- || (Runtime.isFiberFailure(err)
203
- && Cause.isInterruptedOnly(err[Runtime.FiberFailureCauseId]))
204
- ) {
205
- return
206
- }
207
- const extra = {
208
- action,
209
- message: `Unexpected Error trying to ${action}`
210
- }
211
- Sentry.captureException(err, {
212
- extra
213
- })
214
- console.error(err, extra)
215
-
216
- return toast.error(
217
- intl.value.formatMessage(
218
- { id: "handle.unexpected_error" },
219
- {
220
- action: message,
221
- error: JSON.stringify(err, undefined, 2)
222
- }
223
- )
254
+ const message = options.failMessage
255
+ ? yield* options.failMessage(e, i)
256
+ : `${defaultErrorMessage}:\n` + renderError(e)
257
+ if (message) {
258
+ toast.error(message)
259
+ }
260
+ }),
261
+ onDefect: (cause, i) =>
262
+ Effect.gen(function*() {
263
+ const message = options.defectMessage
264
+ ? yield* options.defectMessage(cause, i)
265
+ : intl.value.formatMessage(
266
+ { id: "handle.unexpected_error" },
267
+ {
268
+ action: actionMessage,
269
+ error: Cause.pretty(cause)
270
+ }
224
271
  )
272
+ if (message) {
273
+ toast.error(message)
225
274
  }
226
- )),
227
- { action }
228
- )
275
+ })
276
+ })
229
277
  }
230
278
 
231
279
  function renderError(e: ResponseErrors): string {
@@ -283,44 +331,58 @@ export const makeClient = <Locale extends string, R>(
283
331
  }
284
332
 
285
333
  /**
286
- * Pass a function that returns an Effect, e.g from a client action, give it a name, and optionally pass an onSuccess callback.
287
- * Returns a tuple with state ref and execution function which reports errors as Toast.
334
+ * Pass a function that returns an Effect, e.g from a client action, give it a name.
335
+ * Returns a tuple with state ref and execution function which reports success and errors as Toast.
288
336
  */
289
337
  const useAndHandleMutation: {
290
- <I, E extends ResponseErrors, A>(
291
- self: {
292
- handler: (i: I) => Effect<A, E, R>
293
- name: string
294
- },
338
+ <
339
+ I,
340
+ E extends ResponseErrors,
341
+ A,
342
+ R,
343
+ Request extends TaggedRequestClassAny,
344
+ ESuccess = never,
345
+ RSuccess = never,
346
+ EError = never,
347
+ RError = never,
348
+ EDefect = never,
349
+ RDefect = never
350
+ >(
351
+ self: RequestHandlerWithInput<I, A, E, R, Request>,
295
352
  action: string,
296
- options?: Opts<A, I>
297
- ): Resp<I, A, E>
298
- <E extends ResponseErrors, A>(
299
- self: {
300
- handler: Effect<A, E, R>
301
- name: string
302
- },
353
+ options?: Opts<A, E, I, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
354
+ ): Resp<I, void, never, R>
355
+ <
356
+ E extends ResponseErrors,
357
+ A,
358
+ R,
359
+ Request extends TaggedRequestClassAny,
360
+ ESuccess = never,
361
+ RSuccess = never,
362
+ EError = never,
363
+ RError = never,
364
+ EDefect = never,
365
+ RDefect = never
366
+ >(
367
+ self: RequestHandler<A, E, R, Request>,
303
368
  action: string,
304
- options?: Opts<A>
305
- ): ActResp<E, A>
306
- } = (self: any, action: any, options?: Opts<any>) => {
369
+ options?: Opts<A, E, void, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
370
+ ): ActResp<void, never, R>
371
+ } = (self: any, action: any, options?: Opts<any, any, any, any, any, any, any, any, any>): any => {
307
372
  const handleRequestWithToast = useHandleRequestWithToast()
308
- const [a, b] = useSafeMutation(
309
- {
310
- handler: Effect.isEffect(self.handler)
311
- ? (pipe(
373
+ const [a, b] = useSafeMutation({
374
+ ...self,
375
+ handler: Effect.isEffect(self.handler)
376
+ ? (pipe(
377
+ Effect.annotateCurrentSpan({ action }),
378
+ Effect.andThen(self.handler)
379
+ ) as any)
380
+ : (...args: any[]) =>
381
+ pipe(
312
382
  Effect.annotateCurrentSpan({ action }),
313
- Effect.andThen(self.handler)
314
- ) as any)
315
- : (...args: any[]) =>
316
- pipe(
317
- Effect.annotateCurrentSpan({ action }),
318
- Effect.andThen(self.handler(...args))
319
- ),
320
- name: self.name
321
- },
322
- options ? dropUndefinedT(options) : undefined
323
- )
383
+ Effect.andThen(self.handler(...args))
384
+ )
385
+ }, options ? dropUndefinedT(options) : undefined)
324
386
 
325
387
  return tuple(
326
388
  computed(() => mutationResultToVue(a.value)),
@@ -328,49 +390,162 @@ export const makeClient = <Locale extends string, R>(
328
390
  )
329
391
  }
330
392
 
393
+ /**
394
+ * The same as @see useAndHandleMutation, but does not display any toasts by default.
395
+ * Messages for success, error and defect toasts can be provided in the Options.
396
+ */
397
+ const useAndHandleMutationSilently: {
398
+ <
399
+ I,
400
+ E extends ResponseErrors,
401
+ A,
402
+ R,
403
+ Request extends TaggedRequestClassAny,
404
+ ESuccess = never,
405
+ RSuccess = never,
406
+ EError = never,
407
+ RError = never,
408
+ EDefect = never,
409
+ RDefect = never
410
+ >(
411
+ self: RequestHandlerWithInput<I, A, E, R, Request>,
412
+ action: string,
413
+ options?: Opts<A, E, I, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
414
+ ): Resp<I, void, never, R>
415
+ <
416
+ E extends ResponseErrors,
417
+ A,
418
+ R,
419
+ Request extends TaggedRequestClassAny,
420
+ ESuccess = never,
421
+ RSuccess = never,
422
+ EError = never,
423
+ RError = never,
424
+ EDefect = never,
425
+ RDefect = never
426
+ >(
427
+ self: RequestHandler<A, E, R, Request>,
428
+ action: string,
429
+ options?: Opts<A, E, void, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
430
+ ): ActResp<void, never, R>
431
+ } = makeUseAndHandleMutation({
432
+ successMessage: suppressToast,
433
+ failMessage: suppressToast,
434
+ defectMessage: suppressToast
435
+ }) as any
436
+
437
+ /**
438
+ * The same as @see useAndHandleMutation, but does not act on success, error or defect by default.
439
+ * Actions for success, error and defect can be provided in the Options.
440
+ */
441
+ const useAndHandleMutationCustom: {
442
+ <
443
+ I,
444
+ E extends ResponseErrors,
445
+ A,
446
+ R,
447
+ Request extends TaggedRequestClassAny,
448
+ ESuccess = never,
449
+ RSuccess = never,
450
+ EError = never,
451
+ RError = never,
452
+ EDefect = never,
453
+ RDefect = never
454
+ >(
455
+ self: RequestHandlerWithInput<I, A, E, R, Request>,
456
+ action: string,
457
+ options?: LowOptsOptional<A, E, I, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
458
+ ): Resp<I, void, never, R>
459
+ <
460
+ E extends ResponseErrors,
461
+ A,
462
+ R,
463
+ Request extends TaggedRequestClassAny,
464
+ ESuccess = never,
465
+ RSuccess = never,
466
+ EError = never,
467
+ RError = never,
468
+ EDefect = never,
469
+ RDefect = never
470
+ >(
471
+ self: RequestHandler<A, E, R, Request>,
472
+ action: string,
473
+ options?: LowOptsOptional<A, E, void, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
474
+ ): ActResp<void, never, R>
475
+ } = (self: any, action: string, options: any) => {
476
+ const [a, b] = useSafeMutation({
477
+ ...self,
478
+ handler: Effect.isEffect(self.handler)
479
+ ? (pipe(
480
+ Effect.annotateCurrentSpan({ action }),
481
+ Effect.andThen(self.handler)
482
+ ) as any)
483
+ : (...args: any[]) =>
484
+ pipe(
485
+ Effect.annotateCurrentSpan({ action }),
486
+ Effect.andThen(self.handler(...args))
487
+ )
488
+ }, options ? dropUndefinedT(options) : undefined)
489
+
490
+ return tuple(
491
+ computed(() => mutationResultToVue(a.value)),
492
+ handleRequest(b as any, action, {
493
+ onSuccess: suppressToast,
494
+ onDefect: suppressToast,
495
+ onFail: suppressToast,
496
+ ...options
497
+ })
498
+ ) as any
499
+ }
500
+
331
501
  function makeUseAndHandleMutation(
332
- onSuccess?: () => Promise<void>,
333
- defaultOptions?: Opts<any>
502
+ defaultOptions?: Opts<any, any, any, any, any, any, any, any, any>
334
503
  ) {
335
504
  return ((self: any, action: any, options: any) => {
336
505
  return useAndHandleMutation(
337
- {
338
- handler: typeof self.handler === "function"
339
- ? onSuccess
340
- ? (i: any) => Effect.tap(self.handler(i), () => Effect.promise(onSuccess))
341
- : self.handler
342
- : onSuccess
343
- ? (Effect.tap(self.handler, () => Effect.promise(onSuccess)) as any)
344
- : self.handler,
345
- name: self.name
346
- },
506
+ self,
347
507
  action,
348
508
  { ...defaultOptions, ...options }
349
509
  )
350
- }) as {
351
- <I, E extends ResponseErrors, A>(
352
- self: {
353
- handler: (i: I) => Effect<A, E, R>
354
- name: string
355
- },
510
+ }) as unknown as {
511
+ <
512
+ I,
513
+ E extends ResponseErrors,
514
+ A,
515
+ R,
516
+ Request extends TaggedRequestClassAny,
517
+ ESuccess = never,
518
+ RSuccess = never,
519
+ EError = never,
520
+ RError = never,
521
+ EDefect = never,
522
+ RDefect = never
523
+ >(
524
+ self: RequestHandlerWithInput<I, A, E, R, Request>,
356
525
  action: string,
357
- options?: Opts<A, I>
358
- ): Resp<I, A, E>
359
- <E extends ResponseErrors, A>(
360
- self: {
361
- handler: Effect<A, E, R>
362
- name: string
363
- },
526
+ options?: Opts<A, E, I, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
527
+ ): Resp<I, A, E, R>
528
+ <
529
+ E extends ResponseErrors,
530
+ A,
531
+ Request extends TaggedRequestClassAny,
532
+ ESuccess = never,
533
+ RSuccess = never,
534
+ EError = never,
535
+ RError = never,
536
+ EDefect = never,
537
+ RDefect = never
538
+ >(
539
+ self: RequestHandler<A, E, R, Request>,
364
540
  action: string,
365
- options?: Opts<A>
366
- ): ActResp<E, A>
541
+ options?: Opts<A, E, void, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
542
+ ): ActResp<A, E, R>
367
543
  }
368
544
  }
369
545
 
370
- const useSafeMutationWithState = <I, E, A>(self: {
371
- handler: (i: I) => Effect<A, E, R>
372
- name: string
373
- }) => {
546
+ const useSafeMutationWithState = <I, E, A, Request extends TaggedRequestClassAny>(
547
+ self: RequestHandlerWithInput<I, A, E, R, Request>
548
+ ) => {
374
549
  const [a, b] = useSafeMutation(self)
375
550
 
376
551
  return tuple(
@@ -379,10 +554,71 @@ export const makeClient = <Locale extends string, R>(
379
554
  )
380
555
  }
381
556
 
557
+ const buildFormFromSchema = <
558
+ From extends Record<PropertyKey, any>,
559
+ To extends Record<PropertyKey, any>,
560
+ C extends Record<PropertyKey, any>,
561
+ OnSubmitA
562
+ >(
563
+ s:
564
+ & Schema<
565
+ To,
566
+ From,
567
+ R
568
+ >
569
+ & { new(c: C): any; fields: S.Struct.Fields },
570
+ state: Ref<Omit<From, "_tag">>,
571
+ onSubmit: (a: To) => Effect<OnSubmitA, never, R>
572
+ ) => {
573
+ const fields = buildFieldInfoFromFieldsRoot(s).fields
574
+ const schema = S.Struct(Struct.omit(s.fields, "_tag")) as any
575
+ const parse = S.decodeUnknown<any, any, R>(schema)
576
+ const isDirty = ref(false)
577
+ const isValid = ref(true)
578
+ const runPromise = Runtime.runPromise(getRuntime(runtime))
579
+
580
+ const submit1 =
581
+ (onSubmit: (a: To) => Effect<OnSubmitA, never, R>) => async <T extends Promise<{ valid: boolean }>>(e: T) => {
582
+ const r = await e
583
+ if (!r.valid) return
584
+ return runPromise(onSubmit(new s(await runPromise(parse(state.value)))))
585
+ }
586
+ const submit = submit1(onSubmit)
587
+
588
+ watch(
589
+ state,
590
+ (v) => {
591
+ // TODO: do better
592
+ isDirty.value = JSON.stringify(v) !== JSON.stringify(state.value)
593
+ },
594
+ { deep: true }
595
+ )
596
+
597
+ const submitFromState = Effect.gen(function*() {
598
+ if (!isValid.value) return
599
+ return yield* onSubmit(yield* parse(state.value))
600
+ }) // () => submit(Promise.resolve({ valid: isValid.value }))
601
+
602
+ return {
603
+ fields,
604
+ /** optimized for Vuetify v-form submit callback */
605
+ submit,
606
+ /** optimized for Native form submit callback or general use */
607
+ submitFromState,
608
+ isDirty,
609
+ isValid
610
+ }
611
+ }
612
+
382
613
  return {
383
614
  useSafeMutationWithState,
384
615
  useAndHandleMutation,
616
+ useAndHandleMutationSilently,
617
+ useAndHandleMutationCustom,
385
618
  makeUseAndHandleMutation,
386
- useHandleRequestWithToast
619
+ useHandleRequestWithToast,
620
+ buildFormFromSchema,
621
+ useSafeQuery,
622
+ useSafeMutation
387
623
  }
388
624
  }