@effect-app/vue 0.130.0 → 0.131.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 (54) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/_cjs/_global.cjs +1 -2
  3. package/_cjs/_global.cjs.map +1 -1
  4. package/_cjs/form.cjs +12 -14
  5. package/_cjs/form.cjs.map +1 -1
  6. package/_cjs/hooks.cjs +27 -190
  7. package/_cjs/hooks.cjs.map +1 -1
  8. package/_cjs/internal.cjs +6 -5
  9. package/_cjs/internal.cjs.map +1 -1
  10. package/_cjs/mutate.cjs +105 -0
  11. package/_cjs/mutate.cjs.map +1 -0
  12. package/_cjs/query.cjs +53 -28
  13. package/_cjs/query.cjs.map +1 -1
  14. package/_cjs/routeParams.cjs +9 -12
  15. package/_cjs/routeParams.cjs.map +1 -1
  16. package/_cjs/runtime.cjs +19 -22
  17. package/_cjs/runtime.cjs.map +1 -1
  18. package/_src/_global.ts +1 -10
  19. package/_src/form.ts +9 -12
  20. package/_src/hooks.ts +2 -323
  21. package/_src/internal.ts +9 -4
  22. package/_src/mutate.ts +163 -0
  23. package/_src/query.ts +103 -32
  24. package/_src/routeParams.ts +6 -3
  25. package/_src/runtime.ts +18 -13
  26. package/_src/swrv.bak +196 -0
  27. package/dist/_global.d.ts.map +1 -1
  28. package/dist/_global.js +2 -10
  29. package/dist/form.d.ts.map +1 -1
  30. package/dist/form.js +15 -17
  31. package/dist/hooks.d.ts.map +1 -1
  32. package/dist/hooks.js +3 -195
  33. package/dist/internal.d.ts +5 -4
  34. package/dist/internal.d.ts.map +1 -1
  35. package/dist/internal.js +5 -3
  36. package/dist/mutate.d.ts +43 -0
  37. package/dist/mutate.d.ts.map +1 -0
  38. package/dist/mutate.js +101 -0
  39. package/dist/query.d.ts.map +1 -1
  40. package/dist/query.js +62 -33
  41. package/dist/routeParams.d.ts.map +1 -1
  42. package/dist/routeParams.js +14 -15
  43. package/dist/runtime.d.ts +24 -23
  44. package/dist/runtime.d.ts.map +1 -1
  45. package/dist/runtime.js +27 -24
  46. package/package.json +13 -4
  47. package/tsconfig.json +1 -0
  48. package/tsconfig.json.bak +1 -0
  49. package/dist/_global.d.ts +0 -9
  50. package/dist/form.d.ts +0 -53
  51. package/dist/hooks.d.ts +0 -80
  52. package/dist/index.d.ts +0 -4
  53. package/dist/query.d.ts +0 -12
  54. package/dist/routeParams.d.ts +0 -14
package/_src/hooks.ts CHANGED
@@ -1,326 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { Done, Initial, Loading } from "effect-app/client"
3
- import type { ApiConfig, FetchResponse } from "effect-app/client"
4
- import type * as HttpClient from "@effect/platform/Http/Client"
5
- import { InterruptedException } from "effect/Cause"
6
- import * as Either from "effect/Either"
7
- import { FiberFailureCauseId, isFiberFailure } from "effect/Runtime"
8
- import * as swrv from "swrv"
9
- import type { fetcherFn, IKey, IResponse } from "swrv/dist/types.js"
10
- import type { ComputedRef, Ref } from "vue"
11
- import { computed, ref, shallowRef } from "vue"
12
- import { run } from "./internal.js"
13
2
 
14
3
  export { isFailed, isInitializing, isSuccess } from "effect-app/client"
15
-
16
- type useSWRVType = {
17
- <Data, Error>(key: IKey): IResponse<Data, Error>
18
- <Data, Error>(
19
- key: IKey,
20
- fn?: fetcherFn<Data>,
21
- config?: swrv.IConfig<Data, fetcherFn<Data>>
22
- ): IResponse<Data, Error>
23
- }
24
- type MutateType = {
25
- <Data>(
26
- key: string,
27
- res: Data | Promise<Data>,
28
- cache?: swrv.SWRVCache<Omit<IResponse<any, any>, "mutate">>,
29
- ttl?: number
30
- ): Promise<{
31
- data: any
32
- error: any
33
- isValidating: any
34
- }>
35
- }
36
- // madness - workaround different import behavior on server and client
37
- const useSWRV = (swrv.default.default ? swrv.default.default : swrv.default) as unknown as useSWRVType
38
- export const mutate = (swrv.default.mutate ? swrv.default.mutate : swrv.mutate) as unknown as MutateType
39
-
40
- function swrToQuery<E, A>(
41
- r: { error: E | undefined; data: A | undefined; isValidating: boolean }
42
- ): QueryResult<E, A> {
43
- if (r.error) {
44
- return r.isValidating
45
- ? Refreshing.fail<E, A>(r.error, r.data)
46
- : Done.fail<E, A>(r.error, r.data)
47
- }
48
- if (r.data !== undefined) {
49
- return r.isValidating
50
- ? Refreshing.succeed<A, E>(r.data)
51
- : Done.succeed<A, E>(r.data)
52
- }
53
-
54
- return r.isValidating ? new Loading() : new Initial()
55
- }
56
-
57
- export function useMutate<E, A>(
58
- self: { handler: Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>; mapPath: string }
59
- ) {
60
- const fn = () =>
61
- run.value(self.handler).then((_) => _.body).catch((_) => {
62
- if (!isFiberFailure(_)) throw _
63
- const cause = _[FiberFailureCauseId]
64
- throw cause.squash
65
- })
66
- return () => mutate(self.mapPath, fn)
67
- }
68
-
69
- export function useMutateWithArg<Arg, E, A>(
70
- self: {
71
- handler: (arg: Arg) => Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>
72
- mapPath: (arg: Arg) => string
73
- }
74
- ) {
75
- const fn = (arg: Arg) =>
76
- run.value(self.handler(arg)).then((_) => _.body).catch((_) => {
77
- if (!isFiberFailure(_)) throw _
78
- const cause = _[FiberFailureCauseId]
79
- throw cause.squash
80
- })
81
- return (arg: Arg) => mutate(self.mapPath(arg), fn(arg))
82
- }
83
-
84
- export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
85
-
86
- export function useSafeQuery<E, A>(
87
- self: {
88
- handler: Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>
89
- mapPath: string
90
- },
91
- config?: swrv.IConfig<A, fetcherFn<A>> | undefined
92
- ): readonly [ComputedRef<QueryResult<E, A>>, ComputedRef<A | undefined>, () => Promise<void>, IResponse<A, E>]
93
- export function useSafeQuery<Arg, E, A>(
94
- self: {
95
- handler: (arg: Arg) => Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>
96
- mapPath: (arg: Arg) => string
97
- },
98
- arg: Arg | WatchSource<Arg>,
99
- config?: swrv.IConfig<A, fetcherFn<A>> | undefined
100
- ): readonly [ComputedRef<QueryResult<E, A>>, ComputedRef<A | undefined>, () => Promise<void>, IResponse<A, E>]
101
- export function useSafeQuery(
102
- self: any, // {
103
- // handler: (arg: Arg) => Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>
104
- // mapPath: (arg: Arg) => string
105
- // } | {
106
- // handler: Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>
107
- // mapPath: string
108
- // },
109
- arg?: any, // Arg | WatchSource<Arg> | swrv.IConfig<A, fetcherFn<A>> | undefined,
110
- config?: any // swrv.IConfig<A, fetcherFn<A>> | undefined
111
- ) {
112
- return Effect.isEffect(self.handler)
113
- ? useSafeQuery_(self.mapPath, () => self.handler, arg)
114
- : useSafeQueryWithArg_(self.handler, self.mapPath, arg, config)
115
- }
116
-
117
- export function useSafeQueryWithArg_<Arg, E, A>(
118
- self: (arg: Arg) => Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>,
119
- mapPath: (arg: Arg) => string,
120
- arg: Arg | WatchSource<Arg>,
121
- config?: swrv.IConfig<A, fetcherFn<A>> | undefined
122
- ) {
123
- const arr = arg
124
- const r: { value: Arg } = typeof arr === "function"
125
- ? {
126
- get value() {
127
- return (arr as any)()
128
- }
129
- } as any
130
- : ref(arg)
131
- return useSafeQuery_(computed(() => mapPath(r.value)), () => self(r.value), config)
132
- }
133
-
134
- export function useSafeQuery_<E, A>(
135
- key: string | WatchSource<string>,
136
- self: () => Effect<FetchResponse<A>, E, ApiConfig | HttpClient.Client.Default>,
137
- config?: swrv.IConfig<A, fetcherFn<A>> | undefined
138
- ) {
139
- // const [result, latestSuccess, execute] = make(self)
140
-
141
- // TODO: support with interruption
142
- // const sem = Semaphore.unsafeMake(1)
143
- // const lock = sem.withPermits(1)
144
- // let fib: Fiber.FiberContext<E, FetchResponse<A>> | undefined = undefined
145
- // const execute = self
146
- // const runNew = execute.fork()
147
- // .tap(newFiber =>
148
- // Effect.sync(() => {
149
- // fib = newFiber
150
- // })
151
- // )
152
-
153
- // const ex = lock(
154
- // Effect.suspend(() => {
155
- // return fib
156
- // ? Fiber.interrupt(fib).zipRight(runNew)
157
- // : runNew
158
- // })
159
- // ).flatMap(Fiber.await)
160
- // function execWithInterruption() {
161
- // return ex.provide(Layers)
162
- // .runPromise()
163
- // .catch(err => {
164
- // if (!Cause.isInterruptedException(err)) throw err
165
- // return undefined
166
- // })
167
- // }
168
-
169
- // const swr = useSWRV<A, E>(key, () => execWithInterruption().then(_ => _?.body as any)) // Effect.runPromise(self.provide(Layers))
170
- const swr = useSWRV<A, E>(key, () =>
171
- run
172
- .value(self())
173
- .then((_) => _.body)
174
- .catch((_) => {
175
- if (!isFiberFailure(_)) throw _
176
- const cause = _[FiberFailureCauseId]
177
- throw cause.squash
178
- }), config)
179
- const result = computed(() =>
180
- swrToQuery({ data: swr.data.value, error: swr.error.value, isValidating: swr.isValidating.value })
181
- ) // ref<QueryResult<E, A>>()
182
- const latestSuccess = computed(() => {
183
- const value = result.value
184
- return value.isSuccess()
185
- ? value.current.isRight()
186
- ? value.current.right
187
- : value.previous.isSome()
188
- ? value.previous.value
189
- : undefined
190
- : undefined
191
- })
192
-
193
- return tuple(result, latestSuccess, () => swr.mutate(), swr)
194
- }
195
-
196
- export function make<R, E, A>(self: Effect<FetchResponse<A>, E, R>) {
197
- const result = shallowRef(new Initial() as QueryResult<E, A>)
198
-
199
- const execute = Effect
200
- .sync(() => {
201
- result.value = result.value.isInitializing()
202
- ? new Loading()
203
- : new Refreshing(result.value)
204
- })
205
- .zipRight(self.map((_) => _.body).asQueryResult)
206
- .flatMap((r) => Effect.sync(() => result.value = r))
207
-
208
- const latestSuccess = computed(() => {
209
- const value = result.value
210
- return value.hasValue()
211
- ? value.current.isRight()
212
- ? value.current.right
213
- : value.previous.isSome()
214
- ? value.previous.value
215
- : undefined
216
- : undefined
217
- })
218
-
219
- return tuple(result, latestSuccess, execute)
220
- }
221
-
222
- export interface MutationInitial {
223
- readonly _tag: "Initial"
224
- }
225
-
226
- export interface MutationLoading {
227
- readonly _tag: "Loading"
228
- }
229
-
230
- export interface MutationSuccess<A> {
231
- readonly _tag: "Success"
232
- readonly data: A
233
- }
234
-
235
- export interface MutationError<E> {
236
- readonly _tag: "Error"
237
- readonly error: E
238
- }
239
-
240
- export type MutationResult<E, A> = MutationInitial | MutationLoading | MutationSuccess<A> | MutationError<E>
241
-
242
- /**
243
- * Pass a function that returns an Effect, e.g from a client action, or an Effect
244
- * Returns a tuple with state ref and execution function which reports errors as Toast.
245
- */
246
- export const useMutation: {
247
- <I, E, A>(self: { handler: (i: I) => Effect<A, E, ApiConfig | HttpClient.Client.Default> }): readonly [
248
- Readonly<Ref<MutationResult<E, A>>>,
249
- (
250
- i: I,
251
- abortSignal?: AbortSignal
252
- ) => Promise<Either.Either<E, A>>
253
- ]
254
- <E, A>(self: { handler: Effect<A, E, ApiConfig | HttpClient.Client.Default> }): readonly [
255
- Readonly<Ref<MutationResult<E, A>>>,
256
- (
257
- abortSignal?: AbortSignal
258
- ) => Promise<Either.Either<E, A>>
259
- ]
260
- } = <I, E, A>(
261
- self: {
262
- handler:
263
- | ((i: I) => Effect<A, E, ApiConfig | HttpClient.Client.Default>)
264
- | Effect<A, E, ApiConfig | HttpClient.Client.Default>
265
- }
266
- ) => {
267
- const state: Ref<MutationResult<E, A>> = ref<MutationResult<E, A>>({ _tag: "Initial" }) as any
268
-
269
- function handleExit(exit: Exit<A, E>): Effect<Either.Either<E, A>, never, never> {
270
- return Effect.sync(() => {
271
- if (exit.isSuccess()) {
272
- state.value = { _tag: "Success", data: exit.value }
273
- return Either.right(exit.value)
274
- }
275
-
276
- const err = exit.cause.failureOption
277
- if (err.isSome()) {
278
- state.value = { _tag: "Error", error: err.value }
279
- return Either.left(err.value)
280
- }
281
-
282
- const died = exit.cause.dieOption
283
- if (died.value) {
284
- throw died.value
285
- }
286
- const interrupted = exit.cause.interruptOption
287
- if (interrupted.value) {
288
- throw new InterruptedException()
289
- }
290
- throw new Error("Invalid state")
291
- })
292
- }
293
-
294
- const exec = (fst?: I | AbortSignal, snd?: AbortSignal) => {
295
- let effect: Effect<A, E, ApiConfig | HttpClient.Client.Default>
296
- let abortSignal: AbortSignal | undefined
297
- if (Effect.isEffect(self.handler)) {
298
- effect = self.handler as any
299
- abortSignal = fst as AbortSignal | undefined
300
- } else {
301
- effect = self.handler(fst as I)
302
- abortSignal = snd
303
- }
304
-
305
- return run.value(
306
- Effect
307
- .sync(() => {
308
- state.value = { _tag: "Loading" }
309
- })
310
- .zipRight(effect)
311
- .exit
312
- .flatMap(handleExit)
313
- .fork
314
- .flatMap((f) => {
315
- const cancel = () => run.value(f.interrupt)
316
- abortSignal?.addEventListener("abort", () => void cancel().catch(console.error))
317
- return f.join
318
- })
319
- )
320
- }
321
-
322
- return tuple(
323
- state,
324
- exec
325
- )
326
- }
4
+ export * from "./mutate.js"
5
+ export * from "./query.js"
package/_src/internal.ts CHANGED
@@ -1,14 +1,19 @@
1
- import type { ApiConfig } from "effect-app/client"
2
1
  import type * as HttpClient from "@effect/platform/Http/Client"
3
- import type { Runtime } from "effect/Runtime"
2
+ import type { Effect } from "effect-app"
3
+ import { pipe, Runtime } from "effect-app"
4
+ import type { ApiConfig } from "effect-app/client"
4
5
 
5
6
  export const run = {
6
7
  value<E, A>(_: Effect<A, E, ApiConfig | HttpClient.Client.Default>): Promise<A> {
7
8
  throw new Error("Runtime not initialized, please run `initRuntime` first")
8
9
  }
9
10
  }
10
- export function initRuntime<A>(rt: Runtime<A | ApiConfig | HttpClient.Client.Default>) {
11
+ export function initRuntime<A>(rt: Runtime.Runtime<A | ApiConfig | HttpClient.Client.Default>) {
12
+ const runPromise = Runtime.runPromise(rt)
11
13
  run.value = function<E, A>(self: Effect<A, E, ApiConfig | HttpClient.Client.Default>): Promise<A> {
12
- return rt.runPromise(self)
14
+ return runPromise(self)
13
15
  }
14
16
  }
17
+
18
+ export const makeQueryKey = (name: string) =>
19
+ pipe(name.split("/"), (split) => split.map((_) => "$" + _)).join("/").split(".")
package/_src/mutate.ts ADDED
@@ -0,0 +1,163 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { tuple } from "@effect-app/core/Function"
3
+ import type * as HttpClient from "@effect/platform/Http/Client"
4
+ import { useQueryClient } from "@tanstack/vue-query"
5
+ import { Cause, Effect, Exit, Fiber, Option } from "effect-app"
6
+ import { hasValue, Initial, isInitializing, Loading, queryResult, Refreshing } from "effect-app/client"
7
+ import type { ApiConfig, FetchResponse, QueryResult } from "effect-app/client"
8
+ import { InterruptedException } from "effect/Cause"
9
+ import * as Either from "effect/Either"
10
+ import type { ComputedRef, Ref } from "vue"
11
+ import { computed, ref, shallowRef } from "vue"
12
+ import { makeQueryKey, run } from "./internal.js"
13
+
14
+ export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
15
+
16
+ export function make<R, E, A>(self: Effect<FetchResponse<A>, E, R>) {
17
+ const result = shallowRef(new Initial() as QueryResult<E, A>)
18
+
19
+ const execute = Effect
20
+ .sync(() => {
21
+ result.value = isInitializing(result.value)
22
+ ? new Loading()
23
+ : new Refreshing(result.value)
24
+ })
25
+ .andThen(queryResult(self.map((_) => _.body)))
26
+ .flatMap((r) => Effect.sync(() => result.value = r))
27
+
28
+ const latestSuccess = computed(() => {
29
+ const value = result.value
30
+ return hasValue(value)
31
+ ? Either.isRight(value.current)
32
+ ? value.current.right
33
+ : Option.isSome(value.previous)
34
+ ? value.previous.value
35
+ : undefined
36
+ : undefined
37
+ })
38
+
39
+ return tuple(result, latestSuccess, execute)
40
+ }
41
+
42
+ export interface MutationInitial {
43
+ readonly _tag: "Initial"
44
+ }
45
+
46
+ export interface MutationLoading {
47
+ readonly _tag: "Loading"
48
+ }
49
+
50
+ export interface MutationSuccess<A> {
51
+ readonly _tag: "Success"
52
+ readonly data: A
53
+ }
54
+
55
+ export interface MutationError<E> {
56
+ readonly _tag: "Error"
57
+ readonly error: E
58
+ }
59
+
60
+ export type MutationResult<E, A> = MutationInitial | MutationLoading | MutationSuccess<A> | MutationError<E>
61
+
62
+ /**
63
+ * Pass a function that returns an Effect, e.g from a client action, or an Effect
64
+ * Returns a tuple with state ref and execution function which reports errors as Toast.
65
+ */
66
+ export const useSafeMutation: {
67
+ <I, E, A>(self: { handler: (i: I) => Effect<A, E, ApiConfig | HttpClient.Client.Default>; name: string }): readonly [
68
+ Readonly<Ref<MutationResult<E, A>>>,
69
+ (
70
+ i: I,
71
+ abortSignal?: AbortSignal
72
+ ) => Promise<Either.Either<E, A>>
73
+ ]
74
+ <E, A>(self: { handler: Effect<A, E, ApiConfig | HttpClient.Client.Default>; name: string }): readonly [
75
+ Readonly<Ref<MutationResult<E, A>>>,
76
+ (
77
+ abortSignal?: AbortSignal
78
+ ) => Promise<Either.Either<E, A>>
79
+ ]
80
+ } = <I, E, A>(
81
+ self: {
82
+ handler:
83
+ | ((i: I) => Effect<A, E, ApiConfig | HttpClient.Client.Default>)
84
+ | Effect<A, E, ApiConfig | HttpClient.Client.Default>
85
+ name: string
86
+ }
87
+ ) => {
88
+ const queryClient = useQueryClient()
89
+ const state: Ref<MutationResult<E, A>> = ref<MutationResult<E, A>>({ _tag: "Initial" }) as any
90
+
91
+ function handleExit(exit: Exit.Exit<A, E>): Effect<Either.Either<E, A>, never, never> {
92
+ return Effect.sync(() => {
93
+ if (Exit.isSuccess(exit)) {
94
+ state.value = { _tag: "Success", data: exit.value }
95
+ return Either.right(exit.value)
96
+ }
97
+
98
+ const err = Cause.failureOption(exit.cause)
99
+ if (Option.isSome(err)) {
100
+ state.value = { _tag: "Error", error: err.value }
101
+ return Either.left(err.value)
102
+ }
103
+
104
+ const died = Cause.dieOption(exit.cause)
105
+ if (Option.isSome(died)) {
106
+ throw died.value
107
+ }
108
+ const interrupted = Cause.interruptOption(exit.cause)
109
+ if (Option.isSome(interrupted)) {
110
+ throw new InterruptedException()
111
+ }
112
+ throw new Error("Invalid state")
113
+ })
114
+ }
115
+
116
+ const exec = (fst?: I | AbortSignal, snd?: AbortSignal) => {
117
+ let effect: Effect<A, E, ApiConfig | HttpClient.Client.Default>
118
+ let abortSignal: AbortSignal | undefined
119
+ if (Effect.isEffect(self.handler)) {
120
+ effect = self.handler as any
121
+ abortSignal = fst as AbortSignal | undefined
122
+ } else {
123
+ effect = self.handler(fst as I)
124
+ abortSignal = snd
125
+ }
126
+
127
+ return run.value(
128
+ Effect
129
+ .sync(() => {
130
+ state.value = { _tag: "Loading" }
131
+ })
132
+ .andThen(effect)
133
+ .tap(() =>
134
+ Effect.suspend(() => {
135
+ const key = makeQueryKey(self.name)
136
+ const ns = key.filter((_) => _.startsWith("$"))
137
+ const nses: string[] = []
138
+ for (let i = 0; i < ns.length; i++) {
139
+ nses.push(ns.slice(0, i + 1).join("/"))
140
+ }
141
+ return Effect.promise(() => queryClient.invalidateQueries({ queryKey: [ns[0]] }))
142
+ // TODO: more efficient invalidation, including args etc
143
+ // return Effect.promise(() => queryClient.invalidateQueries({
144
+ // predicate: (_) => nses.includes(_.queryKey.filter((_) => _.startsWith("$")).join("/"))
145
+ // }))
146
+ })
147
+ )
148
+ .pipe(Effect.exit)
149
+ .flatMap(handleExit)
150
+ .pipe(Effect.fork)
151
+ .flatMap((f) => {
152
+ const cancel = () => run.value(Fiber.interrupt(f))
153
+ abortSignal?.addEventListener("abort", () => void cancel().catch(console.error))
154
+ return Fiber.join(f)
155
+ })
156
+ )
157
+ }
158
+
159
+ return tuple(
160
+ state,
161
+ exec
162
+ )
163
+ }