@effect-app/vue 4.0.0-beta.17 → 4.0.0-beta.171

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 (96) hide show
  1. package/CHANGELOG.md +1098 -0
  2. package/dist/commander.d.ts +370 -0
  3. package/dist/commander.d.ts.map +1 -0
  4. package/dist/commander.js +591 -0
  5. package/dist/confirm.d.ts +19 -0
  6. package/dist/confirm.d.ts.map +1 -0
  7. package/dist/confirm.js +24 -0
  8. package/dist/errorReporter.d.ts +4 -4
  9. package/dist/errorReporter.d.ts.map +1 -1
  10. package/dist/errorReporter.js +12 -18
  11. package/dist/form.d.ts +13 -4
  12. package/dist/form.d.ts.map +1 -1
  13. package/dist/form.js +41 -12
  14. package/dist/index.d.ts +1 -1
  15. package/dist/intl.d.ts +15 -0
  16. package/dist/intl.d.ts.map +1 -0
  17. package/dist/intl.js +9 -0
  18. package/dist/lib.d.ts +8 -9
  19. package/dist/lib.d.ts.map +1 -1
  20. package/dist/lib.js +34 -7
  21. package/dist/makeClient.d.ts +120 -277
  22. package/dist/makeClient.d.ts.map +1 -1
  23. package/dist/makeClient.js +155 -359
  24. package/dist/makeContext.d.ts +1 -1
  25. package/dist/makeContext.d.ts.map +1 -1
  26. package/dist/makeIntl.d.ts +1 -1
  27. package/dist/makeIntl.d.ts.map +1 -1
  28. package/dist/makeUseCommand.d.ts +8 -0
  29. package/dist/makeUseCommand.d.ts.map +1 -0
  30. package/dist/makeUseCommand.js +13 -0
  31. package/dist/mutate.d.ts +4 -4
  32. package/dist/mutate.d.ts.map +1 -1
  33. package/dist/mutate.js +7 -7
  34. package/dist/query.d.ts +11 -15
  35. package/dist/query.d.ts.map +1 -1
  36. package/dist/query.js +19 -27
  37. package/dist/routeParams.d.ts +1 -1
  38. package/dist/runtime.d.ts +5 -2
  39. package/dist/runtime.d.ts.map +1 -1
  40. package/dist/runtime.js +27 -17
  41. package/dist/toast.d.ts +46 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/toast.js +32 -0
  44. package/dist/withToast.d.ts +26 -0
  45. package/dist/withToast.d.ts.map +1 -0
  46. package/dist/withToast.js +49 -0
  47. package/eslint.config.mjs +2 -2
  48. package/package.json +48 -48
  49. package/src/{experimental/commander.ts → commander.ts} +930 -255
  50. package/src/{experimental/confirm.ts → confirm.ts} +10 -14
  51. package/src/errorReporter.ts +60 -72
  52. package/src/form.ts +55 -16
  53. package/src/intl.ts +12 -0
  54. package/src/lib.ts +50 -13
  55. package/src/makeClient.ts +454 -1023
  56. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +3 -3
  57. package/src/mutate.ts +40 -32
  58. package/src/query.ts +39 -50
  59. package/src/runtime.ts +39 -18
  60. package/src/{experimental/toast.ts → toast.ts} +11 -25
  61. package/src/{experimental/withToast.ts → withToast.ts} +15 -6
  62. package/test/Mutation.test.ts +130 -10
  63. package/test/dist/form.test.d.ts.map +1 -1
  64. package/test/dist/lib.test.d.ts.map +1 -0
  65. package/test/dist/stubs.d.ts +2162 -118
  66. package/test/dist/stubs.d.ts.map +1 -1
  67. package/test/dist/stubs.js +80 -23
  68. package/test/form-validation-errors.test.ts +23 -19
  69. package/test/form.test.ts +20 -2
  70. package/test/lib.test.ts +240 -0
  71. package/test/makeClient.test.ts +217 -38
  72. package/test/stubs.ts +117 -40
  73. package/tsconfig.json +0 -1
  74. package/tsconfig.json.bak +2 -2
  75. package/tsconfig.src.json +34 -34
  76. package/tsconfig.test.json +2 -2
  77. package/vitest.config.ts +5 -5
  78. package/dist/experimental/commander.d.ts +0 -359
  79. package/dist/experimental/commander.d.ts.map +0 -1
  80. package/dist/experimental/commander.js +0 -557
  81. package/dist/experimental/confirm.d.ts +0 -19
  82. package/dist/experimental/confirm.d.ts.map +0 -1
  83. package/dist/experimental/confirm.js +0 -28
  84. package/dist/experimental/intl.d.ts +0 -16
  85. package/dist/experimental/intl.d.ts.map +0 -1
  86. package/dist/experimental/intl.js +0 -5
  87. package/dist/experimental/makeUseCommand.d.ts +0 -8
  88. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  89. package/dist/experimental/makeUseCommand.js +0 -13
  90. package/dist/experimental/toast.d.ts +0 -47
  91. package/dist/experimental/toast.d.ts.map +0 -1
  92. package/dist/experimental/toast.js +0 -41
  93. package/dist/experimental/withToast.d.ts +0 -25
  94. package/dist/experimental/withToast.d.ts.map +0 -1
  95. package/dist/experimental/withToast.js +0 -45
  96. package/src/experimental/intl.ts +0 -9
@@ -12,15 +12,15 @@ export interface CommanderResolved<RT, RTHooks>
12
12
  export const makeUseCommand = Effect.fnUntraced(
13
13
  function*<R = never, RTHooks = never>(rtHooks: Layer.Layer<RTHooks, never, R>) {
14
14
  const cmndr = yield* Commander
15
- const runtime = yield* Effect.services<R>()
15
+ const runtime = yield* Effect.context<R>()
16
16
 
17
17
  const comm = cmndr(runtime, rtHooks)
18
18
 
19
- const command = {
19
+ const command: CommanderResolved<R, RTHooks> = {
20
20
  ...comm,
21
21
  ...CommanderStatic
22
22
  }
23
23
 
24
- return command as CommanderResolved<R, RTHooks>
24
+ return command
25
25
  }
26
26
  )
package/src/mutate.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { type InvalidateOptions, type InvalidateQueryFilters, type QueryClient, useQueryClient } from "@tanstack/vue-query"
3
3
  import { type Cause, Effect, type Exit, Option } from "effect-app"
4
- import { type Req } from "effect-app/client"
4
+ import { makeQueryKey, type Req } from "effect-app/client"
5
5
  import type { ClientForOptions, RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
6
6
  import { tuple } from "effect-app/Function"
7
7
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
8
8
  import { computed, type ComputedRef, shallowRef } from "vue"
9
- import { makeQueryKey } from "./lib.js"
10
9
 
11
10
  export const getQueryKey = (h: { id: string; options?: ClientForOptions }) => {
12
11
  const key = makeQueryKey(h)
@@ -72,7 +71,7 @@ export interface MutationOptionsBase {
72
71
  * By default we invalidate one level of the query key, e.g $project/$configuration.get, we invalidate $project.
73
72
  * This can be overridden by providing a function that returns an array of filters and options.
74
73
  */
75
- queryInvalidation?: (defaultKey: string[], name: string) => {
74
+ queryInvalidation?: (defaultKey: string[], name: string, input?: unknown, output?: Exit.Exit<unknown, unknown>) => {
76
75
  filters?: InvalidateQueryFilters | undefined
77
76
  options?: InvalidateOptions | undefined
78
77
  }[]
@@ -158,40 +157,49 @@ export const invalidateQueries = (
158
157
  )
159
158
  )
160
159
 
161
- const invalidateCache = Effect.suspend(() => {
162
- const queryKey = getQueryKey(self)
160
+ const invalidateCache = (input: unknown, output: Exit.Exit<unknown, unknown>) =>
161
+ Effect.suspend(() => {
162
+ const queryKey = getQueryKey(self)
163
163
 
164
- if (options) {
165
- const opts = options(queryKey, self.id)
166
- if (!opts.length) {
167
- return Effect.void
164
+ if (options) {
165
+ const opts = options(queryKey, self.id, input, output)
166
+ if (!opts.length) {
167
+ return Effect.void
168
+ }
169
+ return Effect
170
+ .andThen(
171
+ Effect.annotateCurrentSpan({ queryKey, opts }),
172
+ Effect.forEach(opts, (_) => invalidateQueries(_.filters, _.options), { concurrency: "inherit" })
173
+ )
174
+ .pipe(Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false }))
168
175
  }
176
+
177
+ if (!queryKey) return Effect.void
178
+
169
179
  return Effect
170
180
  .andThen(
171
- Effect.annotateCurrentSpan({ queryKey, opts }),
172
- Effect.forEach(opts, (_) => invalidateQueries(_.filters, _.options), { concurrency: "inherit" })
181
+ Effect.annotateCurrentSpan({ queryKey }),
182
+ invalidateQueries({ queryKey })
173
183
  )
174
- .pipe(Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false }))
175
- }
176
-
177
- if (!queryKey) return Effect.void
184
+ .pipe(
185
+ Effect.tap(
186
+ // hand over control back to the event loop so that state can be updated..
187
+ // TODO: should we do this in general on any mutation, regardless of invalidation?
188
+ Effect.sleep(0)
189
+ ),
190
+ Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false })
191
+ )
192
+ })
178
193
 
179
- return Effect
180
- .andThen(
181
- Effect.annotateCurrentSpan({ queryKey }),
182
- invalidateQueries({ queryKey })
183
- )
184
- .pipe(
185
- Effect.tap(
186
- // hand over control back to the event loop so that state can be updated..
187
- // TODO: should we do this in general on any mutation, regardless of invalidation?
188
- Effect.sleep(0)
189
- ),
190
- Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false })
194
+ const handle = <A, E, R>(self: Effect.Effect<A, E, R>, input?: unknown) =>
195
+ self.pipe(
196
+ Effect.onExit((exit) =>
197
+ invalidateCache(
198
+ input,
199
+ exit
200
+ )
191
201
  )
192
- })
193
-
194
- const handle = <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.ensuring(self, invalidateCache)
202
+ )
195
203
 
196
204
  return handle
197
205
  }
@@ -221,7 +229,7 @@ export const makeMutation = () => {
221
229
  const queryClient = useQueryClient()
222
230
  const handle = invalidateQueries(queryClient, self, options?.queryInvalidation)
223
231
  const handler = self.handler
224
- const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i))
232
+ const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i), i)
225
233
 
226
234
  return Object.assign(r, { id: self.id }) as any
227
235
  }
@@ -255,7 +263,7 @@ export const useMakeMutation = () => {
255
263
  ) => {
256
264
  const handle = invalidateQueries(queryClient, self, options?.queryInvalidation)
257
265
  const handler = self.handler
258
- const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i))
266
+ const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i), i)
259
267
 
260
268
  return Object.assign(r, { id: self.id }) as any
261
269
  }
package/src/query.ts CHANGED
@@ -3,15 +3,16 @@
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-return */
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
5
5
  import { type DefaultError, type Enabled, type InitialDataFunction, type NonUndefinedGuard, type PlaceholderDataFunction, type QueryKey, type QueryObserverOptions, type QueryObserverResult, type RefetchOptions, useQuery as useTanstackQuery, useQueryClient, type UseQueryDefinedReturnType, type UseQueryReturnType } from "@tanstack/vue-query"
6
- import { Array, Cause, Effect, Exit, flow, Option, S, type ServiceMap } from "effect-app"
7
- import { type Req } from "effect-app/client"
6
+ import { Array, Cause, type Context, Effect, Option, S } from "effect-app"
7
+ import { makeQueryKey, type Req } from "effect-app/client"
8
8
  import type { RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
9
- import { ServiceUnavailableError } from "effect-app/client/errors"
9
+ import { CauseException, ServiceUnavailableError } from "effect-app/client/errors"
10
10
  import { type Span } from "effect/Tracer"
11
11
  import { isHttpClientError } from "effect/unstable/http/HttpClientError"
12
12
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
13
13
  import { computed, type ComputedRef, type MaybeRefOrGetter, ref, shallowRef, watch, type WatchSource } from "vue"
14
- import { makeQueryKey, reportRuntimeError } from "./lib.js"
14
+ import { reportRuntimeError } from "./lib.js"
15
+ import { makeRunPromise } from "./runtime.js"
15
16
 
16
17
  // we must use interface extends, or we get the dreaded typescript error of isn't portable blabla @tanstack/vue-query/build/modern/types.js
17
18
  // but because how they are dealing with some extends clause, we loose all properties except initialData
@@ -74,15 +75,7 @@ export interface CustomDefinedPlaceholderQueryOptions<
74
75
  | PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
75
76
  }
76
77
 
77
- export class KnownFiberFailure<E> extends Error {
78
- readonly error: unknown
79
- constructor(public effectCause: Cause.Cause<E>) {
80
- super("Query failed with cause: " + Cause.squash(effectCause))
81
- this.error = Cause.squash(effectCause)
82
- }
83
- }
84
-
85
- export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
78
+ export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
86
79
  const useQuery_: {
87
80
  <I, A, E, Request extends Req, Name extends string>(
88
81
  q:
@@ -95,8 +88,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
95
88
  ): readonly [
96
89
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
97
90
  ComputedRef<TData | undefined>,
98
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
99
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
91
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
92
+ UseQueryDefinedReturnType<TData, CauseException<E>>
100
93
  ]
101
94
 
102
95
  <TData = A>(
@@ -105,8 +98,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
105
98
  ): readonly [
106
99
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
107
100
  ComputedRef<TData>,
108
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
109
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
101
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
102
+ UseQueryDefinedReturnType<TData, CauseException<E>>
110
103
  ]
111
104
 
112
105
  <TData = A>(
@@ -115,8 +108,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
115
108
  ): readonly [
116
109
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
117
110
  ComputedRef<TData>,
118
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
119
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
111
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
112
+ UseQueryDefinedReturnType<TData, CauseException<E>>
120
113
  ]
121
114
  }
122
115
  } = <I, A, E, Request extends Req, Name extends string>(
@@ -130,25 +123,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
130
123
  options?: any
131
124
  // TODO
132
125
  ) => {
133
- // we wrap into KnownFiberFailure because we want to keep the full cause of the failure.
134
- const runPromise = flow(Effect.runPromiseExitWith(getRuntime()), (_) =>
135
- _.then(
136
- Exit.match({
137
- onFailure: (cause) => Promise.reject(new KnownFiberFailure(cause)),
138
- onSuccess: (value) => Promise.resolve(value)
139
- })
140
- ))
126
+ // we wrap into CauseException because we want to keep the full cause of the failure.
127
+ const runPromise = makeRunPromise(getRuntime())
141
128
  const arr = arg
142
129
  const req: { value: I } = !arg
143
- ? undefined
130
+ ? undefined as any
144
131
  : typeof arr === "function"
145
132
  ? ({
146
133
  get value() {
147
134
  return (arr as any)()
148
135
  }
149
- } as any)
136
+ })
150
137
  : ref(arg)
151
138
  const queryKey = makeQueryKey(q)
139
+ const projectionHash = (q as { queryKeyProjectionHash?: string }).queryKeyProjectionHash
140
+ const baseQueryKey = projectionHash === undefined ? queryKey : [...queryKey, projectionHash]
152
141
  const handler = q.handler
153
142
 
154
143
  const defaultOptions = {
@@ -161,21 +150,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
161
150
  throwOnError: false
162
151
  }
163
152
 
164
- const r = useTanstackQuery<A, KnownFiberFailure<E>, TData>(
153
+ const r = useTanstackQuery<A, CauseException<E>, TData>(
165
154
  Effect.isEffect(handler)
166
155
  ? {
167
156
  ...defaultOptions,
168
157
  ...options,
169
158
  retry: (retryCount, error) => {
170
- if (error instanceof KnownFiberFailure) {
171
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
159
+ if (error instanceof CauseException) {
160
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
172
161
  return false
173
162
  }
174
163
  }
175
164
 
176
165
  return retryCount < 5
177
166
  },
178
- queryKey,
167
+ queryKey: baseQueryKey,
179
168
  queryFn: ({ meta, signal }) =>
180
169
  runPromise(
181
170
  handler
@@ -191,15 +180,15 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
191
180
  ...defaultOptions,
192
181
  ...options,
193
182
  retry: (retryCount, error) => {
194
- if (error instanceof KnownFiberFailure) {
195
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
183
+ if (error instanceof CauseException) {
184
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
196
185
  return false
197
186
  }
198
187
  }
199
188
 
200
189
  return retryCount < 5
201
190
  },
202
- queryKey: [...queryKey, req],
191
+ queryKey: projectionHash === undefined ? [...queryKey, req] : [...queryKey, req, projectionHash],
203
192
  queryFn: ({ meta, signal }) =>
204
193
  runPromise(
205
194
  handler(req.value)
@@ -240,13 +229,13 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
240
229
  }
241
230
 
242
231
  function swrToQuery<E, A>(r: {
243
- error: KnownFiberFailure<E> | undefined
232
+ error: CauseException<E> | undefined
244
233
  data: A | undefined
245
234
  isValidating: boolean
246
235
  }): AsyncResult.AsyncResult<A, E> {
247
236
  if (r.error !== undefined) {
248
237
  return AsyncResult.failureWithPrevious(
249
- r.error.effectCause,
238
+ r.error.originalCause,
250
239
  {
251
240
  previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
252
241
  waiting: r.isValidating
@@ -273,11 +262,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
273
262
  * Effect results are passed to the caller, including errors.
274
263
  */
275
264
  <TData = A>(
276
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
265
+ options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
277
266
  ): readonly [
278
267
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
279
268
  ComputedRef<TData>,
280
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
269
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
281
270
  UseQueryReturnType<any, any>
282
271
  ]
283
272
  <TData = A>(
@@ -285,17 +274,17 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
285
274
  ): readonly [
286
275
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
287
276
  ComputedRef<TData>,
288
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
289
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
277
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
278
+ UseQueryDefinedReturnType<TData, CauseException<E>>
290
279
  ]
291
280
  // optional options, optional A
292
281
  /**
293
282
  * Effect results are passed to the caller, including errors.
294
283
  */
295
- <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>): readonly [
284
+ <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>): readonly [
296
285
  ComputedRef<AsyncResult.AsyncResult<A, E>>,
297
286
  ComputedRef<A | undefined>,
298
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
287
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
299
288
  UseQueryReturnType<any, any>
300
289
  ]
301
290
  }
@@ -312,11 +301,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
312
301
  */
313
302
  <TData = A>(
314
303
  arg: Arg | WatchSource<Arg>,
315
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
304
+ options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
316
305
  ): readonly [
317
306
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
318
307
  ComputedRef<TData>,
319
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
308
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
320
309
  UseQueryReturnType<any, any>
321
310
  ]
322
311
  // required options, with placeholderData
@@ -325,11 +314,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
325
314
  */
326
315
  <TData = A>(
327
316
  arg: Arg | WatchSource<Arg>,
328
- options: CustomDefinedPlaceholderQueryOptions<A, KnownFiberFailure<E>, TData>
317
+ options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
329
318
  ): readonly [
330
319
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
331
320
  ComputedRef<TData>,
332
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
321
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
333
322
  UseQueryReturnType<any, any>
334
323
  ]
335
324
  // optional options, optional A
@@ -338,11 +327,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
338
327
  */
339
328
  <TData = A>(
340
329
  arg: Arg | WatchSource<Arg>,
341
- options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
330
+ options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
342
331
  ): readonly [
343
332
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
344
333
  ComputedRef<TData | undefined>,
345
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
334
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
346
335
  UseQueryReturnType<any, any>
347
336
  ]
348
337
  }
package/src/runtime.ts CHANGED
@@ -1,24 +1,24 @@
1
- import { ManagedRuntime } from "effect"
1
+ import { Exit, flow, ManagedRuntime } from "effect"
2
2
  import { Effect, Layer, Logger } from "effect-app"
3
+ import { CauseException } from "effect-app/client/errors"
4
+ import { type Context } from "effect-app/Context"
3
5
 
4
- export function makeAppRuntime<A, E>(layer: Layer.Layer<A, E>) {
5
- return Effect.gen(function*() {
6
- const l = layer.pipe(
7
- Layer.provide(Logger.layer([Logger.consolePretty()]))
8
- ) as Layer.Layer<A, never>
9
- const mrt = ManagedRuntime.make(l)
10
- yield* mrt.servicesEffect
11
- return Object.assign(mrt, {
12
- [Symbol.dispose]() {
13
- return Effect.runSync(mrt.disposeEffect)
14
- },
6
+ export const makeAppRuntime = Effect.fnUntraced(function*<A, E>(layer: Layer.Layer<A, E>) {
7
+ const l = layer.pipe(
8
+ Layer.provide(Logger.layer([Logger.consolePretty()]))
9
+ ) as Layer.Layer<A, never>
10
+ const mrt = ManagedRuntime.make(l)
11
+ yield* mrt.contextEffect
12
+ return Object.assign(mrt, {
13
+ [Symbol.dispose]() {
14
+ return Effect.runSync(mrt.disposeEffect)
15
+ },
15
16
 
16
- [Symbol.asyncDispose]() {
17
- return mrt.dispose()
18
- }
19
- }) // as we initialise here, there is no more error left.
20
- })
21
- }
17
+ [Symbol.asyncDispose]() {
18
+ return mrt.dispose()
19
+ }
20
+ }) // as we initialise here, there is no more error left.
21
+ })
22
22
 
23
23
  export function initializeSync<A, E>(layer: Layer.Layer<A, E, never>) {
24
24
  const runtime = Effect.runSync(makeAppRuntime(layer))
@@ -29,3 +29,24 @@ export function initializeAsync<A, E>(layer: Layer.Layer<A, E, never>) {
29
29
  return Effect
30
30
  .runPromise(makeAppRuntime(layer))
31
31
  }
32
+
33
+ // we wrap into CauseException because we want to keep the full cause of the failure.
34
+ export const makeRunPromise = <T>(services: Context<T>) =>
35
+ flow(Effect.runPromiseExitWith(services), (_) =>
36
+ _.then(
37
+ Exit.match({
38
+ onFailure: (cause) => Promise.reject(new CauseException(cause, "runPromise")),
39
+ onSuccess: (value) => Promise.resolve(value)
40
+ })
41
+ ))
42
+
43
+ export const makeRunSync = <T>(services: Context<T>) =>
44
+ flow(
45
+ Effect.runSyncExitWith(services),
46
+ Exit.match({
47
+ onFailure: (cause) => {
48
+ throw new CauseException(cause, "runSync")
49
+ },
50
+ onSuccess: (value) => value
51
+ })
52
+ )
@@ -1,5 +1,5 @@
1
- import { Effect, Option, ServiceMap } from "effect-app"
2
- import { proxify } from "effect-app/ServiceMap"
1
+ import { Context, Effect, Option } from "effect-app"
2
+ import { accessEffectFn } from "effect-app/Context"
3
3
 
4
4
  export type ToastId = string | number
5
5
  export type ToastOpts = { id?: ToastId; timeout?: number }
@@ -13,7 +13,7 @@ export type UseToast = () => {
13
13
  dismiss: (this: void, id: ToastId) => void
14
14
  }
15
15
 
16
- export class CurrentToastId extends ServiceMap.Opaque<CurrentToastId, { toastId: ToastId }>()("CurrentToastId") {}
16
+ export class CurrentToastId extends Context.Opaque<CurrentToastId, { toastId: ToastId }>()("CurrentToastId") {}
17
17
 
18
18
  /** fallback to CurrentToastId when available unless id is explicitly set to a value or null */
19
19
  export const wrap = (toast: ReturnType<UseToast>) => {
@@ -41,26 +41,12 @@ export const wrap = (toast: ReturnType<UseToast>) => {
41
41
  }
42
42
  }
43
43
 
44
- export class Toast
45
- extends proxify(ServiceMap.Opaque<Toast, ReturnType<typeof wrap>>()("Toast"))<Toast, ReturnType<typeof wrap>>()
46
- {
47
- }
48
-
49
- // const a = Layer.effect(Toast, Effect.sync(() => Toast.of(null as any)))
50
-
51
- // const A = Toast.of({
52
- // error: () => Effect.succeed(null as any),
53
- // info: () => Effect.succeed(null as any),
54
- // success: () => Effect.succeed(null as any),
55
- // warning: () => Effect.succeed(null as any),
56
- // dismiss: () => Effect.succeed(null as any)
57
- // })
44
+ type ToastShape = ReturnType<typeof wrap>
58
45
 
59
- // const b = Toast.info("test")
60
-
61
- // const a2 = Toast.use((_) => _.error("test"))
62
-
63
- // const b2 = Effect.gen(function*() {
64
- // const toast = yield* Toast
65
- // toast.error("test")
66
- // })
46
+ export class Toast extends Context.Opaque<Toast, ToastShape>()("Toast") {
47
+ static readonly error = accessEffectFn(this, "error")
48
+ static readonly info = accessEffectFn(this, "info")
49
+ static readonly success = accessEffectFn(this, "success")
50
+ static readonly warning = accessEffectFn(this, "warning")
51
+ static readonly dismiss = accessEffectFn(this, "dismiss")
52
+ }
@@ -1,10 +1,11 @@
1
- import { Cause, Effect, Layer, type Option, ServiceMap } from "effect-app"
1
+ import { Cause, Context, Effect, Layer, type Option } from "effect-app"
2
2
  import { wrapEffect } from "effect-app/utils"
3
3
  import { CurrentToastId, Toast } from "./toast.js"
4
4
 
5
5
  export interface ToastOptions<A, E, Args extends ReadonlyArray<unknown>, WaiR, SucR, ErrR> {
6
6
  stableToastId?: undefined | string | ((...args: Args) => string | undefined)
7
7
  timeout?: number
8
+ showSpanInfo?: false
8
9
  onWaiting:
9
10
  | string
10
11
  | ((...args: Args) => string | null)
@@ -33,10 +34,10 @@ export interface ToastOptions<A, E, Args extends ReadonlyArray<unknown>, WaiR, S
33
34
  }
34
35
 
35
36
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
36
- export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
37
+ export class WithToast extends Context.Service<WithToast>()("WithToast", {
37
38
  make: Effect.gen(function*() {
38
39
  const toast = yield* Toast
39
- return <A, E, Args extends Array<unknown>, R, WaiR = never, SucR = never, ErrR = never>(
40
+ return <A, E, Args extends readonly unknown[], R, WaiR = never, SucR = never, ErrR = never>(
40
41
  options: ToastOptions<A, E, Args, WaiR, SucR, ErrR>
41
42
  ) =>
42
43
  Effect.fnUntraced(function*(self: Effect.Effect<A, E, R>, ...args: Args) {
@@ -74,15 +75,23 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
74
75
  return
75
76
  }
76
77
 
78
+ const spanInfo = options.showSpanInfo !== false
79
+ ? yield* Effect.currentSpan.pipe(
80
+ Effect.map((span) => `\nTrace: ${span.traceId}\nSpan: ${span.spanId}`),
81
+ Effect.orElseSucceed(() => "")
82
+ )
83
+ : ""
84
+
77
85
  const t = yield* wrapEffect(options.onFailure)(Cause.findErrorOption(cause), ...args)
78
86
  const opts = { timeout: baseTimeout * 2 }
79
87
 
80
88
  if (typeof t === "object") {
89
+ const message = t.message + spanInfo
81
90
  return t.level === "warn"
82
- ? yield* toast.warning(t.message, toastId !== undefined ? { ...opts, id: toastId } : opts)
83
- : yield* toast.error(t.message, toastId !== undefined ? { ...opts, id: toastId } : opts)
91
+ ? yield* toast.warning(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
92
+ : yield* toast.error(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
84
93
  }
85
- yield* toast.error(t, toastId !== undefined ? { ...opts, id: toastId } : opts)
94
+ yield* toast.error(t + spanInfo, toastId !== undefined ? { ...opts, id: toastId } : opts)
86
95
  }, Effect.uninterruptible)),
87
96
  toastId !== undefined ? Effect.provideService(CurrentToastId, CurrentToastId.of({ toastId })) : (_) => _
88
97
  )