@effect-app/vue 4.0.0-beta.18 → 4.0.0-beta.180

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 (100) hide show
  1. package/CHANGELOG.md +1224 -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 +6 -8
  19. package/dist/lib.d.ts.map +1 -1
  20. package/dist/lib.js +34 -7
  21. package/dist/makeClient.d.ts +148 -290
  22. package/dist/makeClient.d.ts.map +1 -1
  23. package/dist/makeClient.js +205 -361
  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 +57 -25
  32. package/dist/mutate.d.ts.map +1 -1
  33. package/dist/mutate.js +160 -33
  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/examples/streamMutation.ts +83 -0
  49. package/package.json +48 -48
  50. package/src/{experimental/commander.ts → commander.ts} +930 -255
  51. package/src/{experimental/confirm.ts → confirm.ts} +10 -14
  52. package/src/errorReporter.ts +62 -74
  53. package/src/form.ts +55 -16
  54. package/src/intl.ts +12 -0
  55. package/src/lib.ts +46 -13
  56. package/src/makeClient.ts +570 -1038
  57. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +3 -3
  58. package/src/mutate.ts +306 -72
  59. package/src/query.ts +39 -50
  60. package/src/runtime.ts +39 -18
  61. package/src/{experimental/toast.ts → toast.ts} +11 -25
  62. package/src/{experimental/withToast.ts → withToast.ts} +15 -6
  63. package/test/Mutation.test.ts +130 -10
  64. package/test/dist/form.test.d.ts.map +1 -1
  65. package/test/dist/lib.test.d.ts.map +1 -0
  66. package/test/dist/streamFinal.test.d.ts.map +1 -0
  67. package/test/dist/stubs.d.ts +3144 -117
  68. package/test/dist/stubs.d.ts.map +1 -1
  69. package/test/dist/stubs.js +132 -25
  70. package/test/form-validation-errors.test.ts +23 -19
  71. package/test/form.test.ts +20 -2
  72. package/test/lib.test.ts +240 -0
  73. package/test/makeClient.test.ts +241 -38
  74. package/test/streamFinal.test.ts +110 -0
  75. package/test/stubs.ts +172 -42
  76. package/tsconfig.examples.json +20 -0
  77. package/tsconfig.json +0 -1
  78. package/tsconfig.json.bak +5 -2
  79. package/tsconfig.src.json +34 -34
  80. package/tsconfig.test.json +2 -2
  81. package/vitest.config.ts +5 -5
  82. package/dist/experimental/commander.d.ts +0 -359
  83. package/dist/experimental/commander.d.ts.map +0 -1
  84. package/dist/experimental/commander.js +0 -557
  85. package/dist/experimental/confirm.d.ts +0 -19
  86. package/dist/experimental/confirm.d.ts.map +0 -1
  87. package/dist/experimental/confirm.js +0 -28
  88. package/dist/experimental/intl.d.ts +0 -16
  89. package/dist/experimental/intl.d.ts.map +0 -1
  90. package/dist/experimental/intl.js +0 -5
  91. package/dist/experimental/makeUseCommand.d.ts +0 -8
  92. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  93. package/dist/experimental/makeUseCommand.js +0 -13
  94. package/dist/experimental/toast.d.ts +0 -47
  95. package/dist/experimental/toast.d.ts.map +0 -1
  96. package/dist/experimental/toast.js +0 -41
  97. package/dist/experimental/withToast.d.ts +0 -25
  98. package/dist/experimental/withToast.d.ts.map +0 -1
  99. package/dist/experimental/withToast.js +0 -45
  100. package/src/experimental/intl.ts +0 -9
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
  )
@@ -1,7 +1,8 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { it } from "@effect/vitest"
3
3
  import { Cause, Effect, Exit, Fiber, Option } from "effect-app"
4
- import { CommandContext, DefaultIntl } from "../src/experimental/commander.js"
4
+ import { OperationFailure } from "effect-app/Operations"
5
+ import { CommandContext, DefaultIntl } from "../src/commander.js"
5
6
  import { AsyncResult } from "../src/lib.js"
6
7
  import { useExperimental } from "./stubs.js"
7
8
 
@@ -71,7 +72,10 @@ describe("alt2", () => {
71
72
  expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
72
73
  })),
73
74
  Effect.tap(() =>
74
- Effect.currentSpan.pipe(Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))))
75
+ Effect.currentSpan.pipe(
76
+ Effect.map((_) => _.name),
77
+ Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
78
+ )
75
79
  ),
76
80
  Effect.tap(() => Effect.sync(() => executed = true))
77
81
  )
@@ -125,7 +129,10 @@ it.live("works", () =>
125
129
  expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
126
130
  })),
127
131
  Effect.tap(() =>
128
- Effect.currentSpan.pipe(Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))))
132
+ Effect.currentSpan.pipe(
133
+ Effect.map((_) => _.name),
134
+ Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
135
+ )
129
136
  ),
130
137
  Effect.tap(() => Effect.sync(() => executed = true))
131
138
  )
@@ -175,7 +182,10 @@ it.live("works non-gen", () =>
175
182
  expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
176
183
  })),
177
184
  Effect.tap(() =>
178
- Effect.currentSpan.pipe(Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))))
185
+ Effect.currentSpan.pipe(
186
+ Effect.map((_) => _.name),
187
+ Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
188
+ )
179
189
  ),
180
190
  Effect.tap(() => Effect.sync(() => executed = true))
181
191
  )
@@ -317,7 +327,10 @@ it.live("with toasts", () =>
317
327
  expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
318
328
  })),
319
329
  Effect.tap(() =>
320
- Effect.currentSpan.pipe(Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))))
330
+ Effect.currentSpan.pipe(
331
+ Effect.map((_) => _.name),
332
+ Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
333
+ )
321
334
  ),
322
335
  // WithToast.handle({
323
336
  // onFailure: "failed",
@@ -388,9 +401,55 @@ it.live("fail", () =>
388
401
  expect(command.waiting).toBe(false)
389
402
  expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
390
403
  expect(toasts.length).toBe(1) // toast should show error
404
+ expect(toasts[0].type).toBe("warning")
405
+ expect(toasts[0].message).toContain("Test Action Failed:\nBoom!")
406
+ expect(toasts[0].message).toMatch(/Trace: [a-f0-9]{32}/)
407
+ expect(toasts[0].message).toMatch(/Span: [a-f0-9]{16}/)
408
+ }))
409
+
410
+ it.live("fail with showSpanInfo disabled", () =>
411
+ Effect
412
+ .gen(function*() {
413
+ const toasts: any[] = []
414
+ const Command = useExperimental({ toasts, messages: DefaultIntl.en })
415
+
416
+ const command = Command.fn("Test Action")(
417
+ function*() {
418
+ return yield* Effect.fail({ message: "Boom!" })
419
+ },
420
+ Command.withDefaultToast({ showSpanInfo: false })
421
+ )
422
+
423
+ yield* Fiber.join(command.handle())
424
+
425
+ expect(toasts.length).toBe(1)
391
426
  expect(toasts[0].message).toBe("Test Action Failed:\nBoom!")
392
427
  }))
393
428
 
429
+ it.live("fail with custom errorRenderer uses warning toast", () =>
430
+ Effect
431
+ .gen(function*() {
432
+ const toasts: any[] = []
433
+ const Command = useExperimental({ toasts, messages: DefaultIntl.en })
434
+
435
+ const command = Command.fn("Test Action")(
436
+ function*() {
437
+ return yield* Effect.fail(OperationFailure.make({ message: null }))
438
+ },
439
+ Command.withDefaultToast({
440
+ errorRenderer: () => "Rendered Boom!"
441
+ })
442
+ )
443
+
444
+ yield* Fiber.join(command.handle())
445
+
446
+ expect(toasts.length).toBe(1)
447
+ expect(toasts[0].type).toBe("warning")
448
+ expect(toasts[0].message).toContain("Test Action, with warnings\nRendered Boom!")
449
+ expect(toasts[0].message).toMatch(/Trace: [a-f0-9]{32}/)
450
+ expect(toasts[0].message).toMatch(/Span: [a-f0-9]{16}/)
451
+ }))
452
+
394
453
  it.live("fail and recover", () =>
395
454
  Effect
396
455
  .gen(function*() {
@@ -444,7 +503,8 @@ it.live("defect", () =>
444
503
  expect(command.waiting).toBe(false)
445
504
  expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
446
505
  expect(toasts.length).toBe(1) // toast should show error
447
- expect(toasts[0].message).toBe("Test Action unexpected error, please try again shortly.")
506
+ expect(toasts[0].type).toBe("error")
507
+ expect(toasts[0].message).toContain("Test Action unexpected error, please try again shortly.")
448
508
  }))
449
509
 
450
510
  it.live("works with alt", () =>
@@ -471,7 +531,10 @@ it.live("works with alt", () =>
471
531
  expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
472
532
  })),
473
533
  Effect.tap(() =>
474
- Effect.currentSpan.pipe(Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))))
534
+ Effect.currentSpan.pipe(
535
+ Effect.map((_) => _.name),
536
+ Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
537
+ )
475
538
  ),
476
539
  Effect.tap(() => Effect.sync(() => executed = true))
477
540
  )
@@ -624,7 +687,10 @@ it.live("with toasts with alt", () =>
624
687
  expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
625
688
  })),
626
689
  Effect.tap(() =>
627
- Effect.currentSpan.pipe(Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))))
690
+ Effect.currentSpan.pipe(
691
+ Effect.map((_) => _.name),
692
+ Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
693
+ )
628
694
  ),
629
695
  Command.withDefaultToast(),
630
696
  Effect.tap(() => Effect.sync(() => executed = true))
@@ -696,7 +762,8 @@ it.live("fail with alt", () =>
696
762
  expect(command.waiting).toBe(false)
697
763
  expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
698
764
  expect(toasts.length).toBe(1) // toast should show error
699
- expect(toasts[0].message).toBe("Test Action Failed:\nBoom!")
765
+ expect(toasts[0].type).toBe("warning")
766
+ expect(toasts[0].message).toContain("Test Action Failed:\nBoom!")
700
767
  }))
701
768
 
702
769
  it.live("fail and recover with alt", () =>
@@ -756,5 +823,58 @@ it.live("defect with alt", () =>
756
823
  expect(command.waiting).toBe(false)
757
824
  expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
758
825
  expect(toasts.length).toBe(1) // toast should show error
759
- expect(toasts[0].message).toBe("Test Action unexpected error, please try again shortly.")
826
+ expect(toasts[0].type).toBe("error")
827
+ expect(toasts[0].message).toContain("Test Action unexpected error, please try again shortly.")
760
828
  }))
829
+
830
+ describe("state-in-toast", () => {
831
+ it("works", () => {
832
+ const toasts: any[] = []
833
+ const removeMutation = Object.assign(
834
+ Effect.fn(function*(_item: string) {
835
+ yield* Effect.sleep(1000)
836
+ }),
837
+ { id: "remove_thing" }
838
+ )
839
+
840
+ const item = "x"
841
+
842
+ const Command = useExperimental({ toasts, messages: DefaultIntl.en })
843
+
844
+ Command.fn(removeMutation, {
845
+ state: () => ({ item }),
846
+ waitKey: (id) => `${id}.${item}`,
847
+ blockKey: () => `modify_thing.${item}`
848
+ // allowed: () => role.value === "admin"
849
+ })(
850
+ function*() {
851
+ // yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item }))
852
+ yield* removeMutation(item)
853
+ },
854
+ Command.withDefaultToast({
855
+ onSuccess: (a, b, c, d) => {
856
+ console.log("Success", { a, b, c, d })
857
+ expectTypeOf(d.state).toEqualTypeOf<{ readonly item: "x" }>()
858
+ }
859
+ })
860
+ )
861
+
862
+ Command.fn(removeMutation, {
863
+ state: () => ({ item }),
864
+ waitKey: (id) => `${id}.${item}`,
865
+ blockKey: () => `modify_thing.${item}`
866
+ // allowed: () => role.value === "admin"
867
+ })(
868
+ function*() {
869
+ // yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item }))
870
+ yield* removeMutation(item)
871
+ },
872
+ Command.withDefaultToast({
873
+ onSuccess: (a, b, c) => {
874
+ console.log("Success", { a, b, c })
875
+ expectTypeOf(c).toEqualTypeOf<undefined>()
876
+ }
877
+ })
878
+ )
879
+ })
880
+ })
@@ -1 +1 @@
1
- {"version":3,"file":"form.test.d.ts","sourceRoot":"","sources":["../form.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,CAAC,EAAE,MAAM,YAAY,CAAA;;;;;;;;;;;;;;;;;AAGtC,qBAAa,YAAa,SAAQ,iBAShC;CAAG;;;;AAEL,qBAAa,mBAAoB,SAAQ,wBAEvC;CAAG;;;;;;;;;;;AAEL,qBAAa,WAAY,SAAQ,gBAK/B;CAAG;;;;;;;;AAEL,cAAM,MAAO,SAAQ,WAEnB;CAAG;;;;;;;;AAEL,cAAM,MAAO,SAAQ,WAEnB;CAAG;;;;;;;;;;;AAEL,cAAM,QAAS,SAAQ,aAGrB;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBL,qBAAa,cAAe,SAAQ,mBAGlC;CAAG"}
1
+ {"version":3,"file":"form.test.d.ts","sourceRoot":"","sources":["../form.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,CAAC,EAAE,MAAM,YAAY,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGtC,qBAAa,YAAa,SAAQ,iBAahC;CAAG;;;;AAEL,qBAAa,mBAAoB,SAAQ,wBAEvC;CAAG;;;;;;;;;;;;AAEL,qBAAa,WAAY,SAAQ,gBAK/B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;AAEL,cAAM,MAAO,SAAQ,WAEnB;CAAG;;;;;;;;;;;;;;;;;;;;;;;;AAEL,cAAM,MAAO,SAAQ,WAEnB;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEL,cAAM,QAAS,SAAQ,aAGrB;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBL,qBAAa,cAAe,SAAQ,mBAGlC;CAAG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.test.d.ts","sourceRoot":"","sources":["../lib.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamFinal.test.d.ts","sourceRoot":"","sources":["../streamFinal.test.ts"],"names":[],"mappings":""}