@effect-app/vue 4.0.0-beta.19 → 4.0.0-beta.191

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 (103) hide show
  1. package/CHANGELOG.md +1398 -0
  2. package/dist/commander.d.ts +620 -0
  3. package/dist/commander.d.ts.map +1 -0
  4. package/dist/commander.js +1056 -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 +14 -5
  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 +191 -292
  22. package/dist/makeClient.d.ts.map +1 -1
  23. package/dist/makeClient.js +217 -369
  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 +56 -25
  32. package/dist/mutate.d.ts.map +1 -1
  33. package/dist/mutate.js +132 -33
  34. package/dist/query.d.ts +24 -16
  35. package/dist/query.d.ts.map +1 -1
  36. package/dist/query.js +119 -37
  37. package/dist/routeParams.d.ts +1 -1
  38. package/dist/runtime.d.ts +7 -4
  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 +54 -0
  47. package/examples/streamMutation.ts +70 -0
  48. package/package.json +47 -49
  49. package/src/commander.ts +3378 -0
  50. package/src/{experimental/confirm.ts → confirm.ts} +10 -14
  51. package/src/errorReporter.ts +62 -74
  52. package/src/form.ts +56 -17
  53. package/src/intl.ts +12 -0
  54. package/src/lib.ts +46 -13
  55. package/src/makeClient.ts +623 -1043
  56. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +6 -4
  57. package/src/mutate.ts +273 -72
  58. package/src/query.ts +181 -68
  59. package/src/runtime.ts +41 -20
  60. package/src/{experimental/toast.ts → toast.ts} +11 -25
  61. package/src/{experimental/withToast.ts → withToast.ts} +28 -10
  62. package/test/Mutation.test.ts +176 -23
  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/streamFinal.test.d.ts.map +1 -0
  66. package/test/dist/streamFn.test.d.ts.map +1 -0
  67. package/test/dist/stubs.d.ts +3294 -115
  68. package/test/dist/stubs.d.ts.map +1 -1
  69. package/test/dist/stubs.js +177 -31
  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 +286 -38
  74. package/test/streamFinal.test.ts +63 -0
  75. package/test/streamFn.test.ts +455 -0
  76. package/test/stubs.ts +213 -42
  77. package/tsconfig.examples.json +20 -0
  78. package/tsconfig.json +0 -1
  79. package/tsconfig.json.bak +5 -2
  80. package/tsconfig.src.json +34 -34
  81. package/tsconfig.test.json +2 -2
  82. package/vitest.config.ts +5 -5
  83. package/dist/experimental/commander.d.ts +0 -359
  84. package/dist/experimental/commander.d.ts.map +0 -1
  85. package/dist/experimental/commander.js +0 -557
  86. package/dist/experimental/confirm.d.ts +0 -19
  87. package/dist/experimental/confirm.d.ts.map +0 -1
  88. package/dist/experimental/confirm.js +0 -28
  89. package/dist/experimental/intl.d.ts +0 -16
  90. package/dist/experimental/intl.d.ts.map +0 -1
  91. package/dist/experimental/intl.js +0 -5
  92. package/dist/experimental/makeUseCommand.d.ts +0 -8
  93. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  94. package/dist/experimental/makeUseCommand.js +0 -13
  95. package/dist/experimental/toast.d.ts +0 -47
  96. package/dist/experimental/toast.d.ts.map +0 -1
  97. package/dist/experimental/toast.js +0 -41
  98. package/dist/experimental/withToast.d.ts +0 -25
  99. package/dist/experimental/withToast.d.ts.map +0 -1
  100. package/dist/experimental/withToast.js +0 -45
  101. package/eslint.config.mjs +0 -24
  102. package/src/experimental/commander.ts +0 -1835
  103. package/src/experimental/intl.ts +0 -9
package/src/query.ts CHANGED
@@ -2,16 +2,22 @@
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-call */
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-return */
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
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"
8
- import type { RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
9
- import { ServiceUnavailableError } from "effect-app/client/errors"
5
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
6
+ import { type DefaultError, type Enabled, experimental_streamedQuery as streamedQuery, 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"
7
+ import { Array, Cause, type Context, Effect, Exit, Option, S } from "effect-app"
8
+ import { makeQueryKey, type Req } from "effect-app/client"
9
+ import type { RequestHandler, RequestHandlerWithInput, RequestStreamHandler, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
10
+ import { CauseException, ServiceUnavailableError } from "effect-app/client/errors"
11
+ import * as Channel from "effect/Channel"
12
+ import * as Pull from "effect/Pull"
13
+ import * as Scope from "effect/Scope"
14
+ import type * as Stream from "effect/Stream"
10
15
  import { type Span } from "effect/Tracer"
11
16
  import { isHttpClientError } from "effect/unstable/http/HttpClientError"
12
17
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
13
18
  import { computed, type ComputedRef, type MaybeRefOrGetter, ref, shallowRef, watch, type WatchSource } from "vue"
14
- import { makeQueryKey, reportRuntimeError } from "./lib.js"
19
+ import { reportRuntimeError } from "./lib.js"
20
+ import { makeRunPromise } from "./runtime.js"
15
21
 
16
22
  // 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
23
  // but because how they are dealing with some extends clause, we loose all properties except initialData
@@ -74,15 +80,65 @@ export interface CustomDefinedPlaceholderQueryOptions<
74
80
  | PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
75
81
  }
76
82
 
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)
83
+ function swrToQuery<E, A>(r: {
84
+ error: CauseException<E> | undefined
85
+ data: A | undefined
86
+ isValidating: boolean
87
+ }): AsyncResult.AsyncResult<A, E> {
88
+ if (r.error !== undefined) {
89
+ return AsyncResult.failureWithPrevious(
90
+ r.error.originalCause,
91
+ {
92
+ previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
93
+ waiting: r.isValidating
94
+ }
95
+ )
96
+ }
97
+ if (r.data !== undefined) {
98
+ return AsyncResult.success<A, E>(r.data, { waiting: r.isValidating })
99
+ }
100
+
101
+ return AsyncResult.initial(r.isValidating)
102
+ }
103
+
104
+ function streamToAsyncIterableWithCauseException<A, E, R>(
105
+ self: Stream.Stream<A, E, R>,
106
+ context: Context.Context<R>,
107
+ id: string
108
+ ): AsyncIterable<A> {
109
+ return {
110
+ [Symbol.asyncIterator]() {
111
+ const runPromise = Effect.runPromiseWith(context)
112
+ const runPromiseExit = Effect.runPromiseExitWith(context)
113
+ const scope = Scope.makeUnsafe()
114
+ let pull: any
115
+ let currentIter: Iterator<A> | undefined
116
+ return {
117
+ async next(): Promise<IteratorResult<A>> {
118
+ if (currentIter) {
119
+ const next = currentIter.next()
120
+ if (!next.done) return next
121
+ currentIter = undefined
122
+ }
123
+ pull ??= await runPromise(Channel.toPullScoped((self as any).channel, scope))
124
+ const exit = await runPromiseExit(pull)
125
+ if (Exit.isSuccess(exit)) {
126
+ currentIter = (exit.value as any)[Symbol.iterator]()
127
+ return currentIter!.next()
128
+ } else if (Pull.isDoneCause((exit as any).cause)) {
129
+ return { done: true, value: undefined }
130
+ }
131
+ throw new CauseException((exit as any).cause, id)
132
+ },
133
+ return(_) {
134
+ return runPromise(Effect.as(Scope.close(scope, Exit.void), { done: true, value: undefined }) as any)
135
+ }
136
+ }
137
+ }
82
138
  }
83
139
  }
84
140
 
85
- export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
141
+ export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
86
142
  const useQuery_: {
87
143
  <I, A, E, Request extends Req, Name extends string>(
88
144
  q:
@@ -95,8 +151,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
95
151
  ): readonly [
96
152
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
97
153
  ComputedRef<TData | undefined>,
98
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
99
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
154
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
155
+ UseQueryDefinedReturnType<TData, CauseException<E>>
100
156
  ]
101
157
 
102
158
  <TData = A>(
@@ -105,8 +161,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
105
161
  ): readonly [
106
162
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
107
163
  ComputedRef<TData>,
108
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
109
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
164
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
165
+ UseQueryDefinedReturnType<TData, CauseException<E>>
110
166
  ]
111
167
 
112
168
  <TData = A>(
@@ -115,8 +171,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
115
171
  ): readonly [
116
172
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
117
173
  ComputedRef<TData>,
118
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
119
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
174
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
175
+ UseQueryDefinedReturnType<TData, CauseException<E>>
120
176
  ]
121
177
  }
122
178
  } = <I, A, E, Request extends Req, Name extends string>(
@@ -130,25 +186,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
130
186
  options?: any
131
187
  // TODO
132
188
  ) => {
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
- ))
189
+ // we wrap into CauseException because we want to keep the full cause of the failure.
190
+ const runPromise = makeRunPromise(getRuntime())
141
191
  const arr = arg
142
192
  const req: { value: I } = !arg
143
- ? undefined
193
+ ? undefined as any
144
194
  : typeof arr === "function"
145
195
  ? ({
146
196
  get value() {
147
197
  return (arr as any)()
148
198
  }
149
- } as any)
199
+ })
150
200
  : ref(arg)
151
201
  const queryKey = makeQueryKey(q)
202
+ const projectionHash = (q as { queryKeyProjectionHash?: string }).queryKeyProjectionHash
203
+ const baseQueryKey = projectionHash === undefined ? queryKey : [...queryKey, projectionHash]
152
204
  const handler = q.handler
153
205
 
154
206
  const defaultOptions = {
@@ -161,21 +213,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
161
213
  throwOnError: false
162
214
  }
163
215
 
164
- const r = useTanstackQuery<A, KnownFiberFailure<E>, TData>(
216
+ const r = useTanstackQuery<A, CauseException<E>, TData>(
165
217
  Effect.isEffect(handler)
166
218
  ? {
167
219
  ...defaultOptions,
168
220
  ...options,
169
221
  retry: (retryCount, error) => {
170
- if (error instanceof KnownFiberFailure) {
171
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
222
+ if (error instanceof CauseException) {
223
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
172
224
  return false
173
225
  }
174
226
  }
175
227
 
176
228
  return retryCount < 5
177
229
  },
178
- queryKey,
230
+ queryKey: baseQueryKey,
179
231
  queryFn: ({ meta, signal }) =>
180
232
  runPromise(
181
233
  handler
@@ -191,15 +243,15 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
191
243
  ...defaultOptions,
192
244
  ...options,
193
245
  retry: (retryCount, error) => {
194
- if (error instanceof KnownFiberFailure) {
195
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
246
+ if (error instanceof CauseException) {
247
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
196
248
  return false
197
249
  }
198
250
  }
199
251
 
200
252
  return retryCount < 5
201
253
  },
202
- queryKey: [...queryKey, req],
254
+ queryKey: projectionHash === undefined ? [...queryKey, req] : [...queryKey, req, projectionHash],
203
255
  queryFn: ({ meta, signal }) =>
204
256
  runPromise(
205
257
  handler(req.value)
@@ -239,27 +291,6 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
239
291
  ] as any
240
292
  }
241
293
 
242
- function swrToQuery<E, A>(r: {
243
- error: KnownFiberFailure<E> | undefined
244
- data: A | undefined
245
- isValidating: boolean
246
- }): AsyncResult.AsyncResult<A, E> {
247
- if (r.error !== undefined) {
248
- return AsyncResult.failureWithPrevious(
249
- r.error.effectCause,
250
- {
251
- previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
252
- waiting: r.isValidating
253
- }
254
- )
255
- }
256
- if (r.data !== undefined) {
257
- return AsyncResult.success<A, E>(r.data, { waiting: r.isValidating })
258
- }
259
-
260
- return AsyncResult.initial(r.isValidating)
261
- }
262
-
263
294
  const useQuery: {
264
295
  /**
265
296
  * Effect results are passed to the caller, including errors.
@@ -273,11 +304,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
273
304
  * Effect results are passed to the caller, including errors.
274
305
  */
275
306
  <TData = A>(
276
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
307
+ options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
277
308
  ): readonly [
278
309
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
279
310
  ComputedRef<TData>,
280
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
311
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
281
312
  UseQueryReturnType<any, any>
282
313
  ]
283
314
  <TData = A>(
@@ -285,17 +316,17 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
285
316
  ): readonly [
286
317
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
287
318
  ComputedRef<TData>,
288
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
289
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
319
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
320
+ UseQueryDefinedReturnType<TData, CauseException<E>>
290
321
  ]
291
322
  // optional options, optional A
292
323
  /**
293
324
  * Effect results are passed to the caller, including errors.
294
325
  */
295
- <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>): readonly [
326
+ <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>): readonly [
296
327
  ComputedRef<AsyncResult.AsyncResult<A, E>>,
297
328
  ComputedRef<A | undefined>,
298
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
329
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
299
330
  UseQueryReturnType<any, any>
300
331
  ]
301
332
  }
@@ -312,11 +343,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
312
343
  */
313
344
  <TData = A>(
314
345
  arg: Arg | WatchSource<Arg>,
315
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
346
+ options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
316
347
  ): readonly [
317
348
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
318
349
  ComputedRef<TData>,
319
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
350
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
320
351
  UseQueryReturnType<any, any>
321
352
  ]
322
353
  // required options, with placeholderData
@@ -325,11 +356,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
325
356
  */
326
357
  <TData = A>(
327
358
  arg: Arg | WatchSource<Arg>,
328
- options: CustomDefinedPlaceholderQueryOptions<A, KnownFiberFailure<E>, TData>
359
+ options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
329
360
  ): readonly [
330
361
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
331
362
  ComputedRef<TData>,
332
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
363
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
333
364
  UseQueryReturnType<any, any>
334
365
  ]
335
366
  // optional options, optional A
@@ -338,11 +369,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
338
369
  */
339
370
  <TData = A>(
340
371
  arg: Arg | WatchSource<Arg>,
341
- options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
372
+ options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
342
373
  ): readonly [
343
374
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
344
375
  ComputedRef<TData | undefined>,
345
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
376
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
346
377
  UseQueryReturnType<any, any>
347
378
  ]
348
379
  }
@@ -362,6 +393,88 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
362
393
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
363
394
  export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
364
395
 
396
+ type StreamQueryResult<A, E> = readonly [
397
+ ComputedRef<AsyncResult.AsyncResult<A[], E>>,
398
+ ComputedRef<A[] | undefined>,
399
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<A[], CauseException<E>>>,
400
+ UseQueryReturnType<any, any>
401
+ ]
402
+
403
+ export const makeStreamQuery = <R>(getRuntime: () => Context.Context<R>) => {
404
+ const streamQuery_: {
405
+ <E, A, Request extends Req, Name extends string>(
406
+ q: RequestStreamHandler<A, E, R, Request, Name>
407
+ ): () => StreamQueryResult<A, E>
408
+ <Arg, E, A, Request extends Req, Name extends string>(
409
+ q: RequestStreamHandlerWithInput<Arg, A, E, R, Request, Name>
410
+ ): (arg: Arg | WatchSource<Arg>) => StreamQueryResult<A, E>
411
+ } = (q: any) => (arg?: any) => {
412
+ const context = getRuntime()
413
+ const arr = arg
414
+ const req: { value: any } = !arg
415
+ ? undefined as any
416
+ : typeof arr === "function"
417
+ ? ({
418
+ get value() {
419
+ return arr()
420
+ }
421
+ })
422
+ : ref(arg)
423
+ const queryKey = makeQueryKey(q)
424
+ const handler = q.handler
425
+ const isWithInput = typeof handler === "function"
426
+
427
+ const r = useTanstackQuery<any[], CauseException<any>, any[]>(
428
+ {
429
+ throwOnError: false,
430
+ retry: (retryCount: number, error: unknown) => {
431
+ if (error instanceof CauseException) {
432
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
433
+ return false
434
+ }
435
+ }
436
+ return retryCount < 5
437
+ },
438
+ queryKey: isWithInput ? [...queryKey, req] : queryKey,
439
+ queryFn: streamedQuery({
440
+ streamFn: () => {
441
+ const stream = isWithInput
442
+ ? handler(req.value)
443
+ : handler
444
+ return streamToAsyncIterableWithCauseException(stream, context, q.id)
445
+ }
446
+ })
447
+ }
448
+ )
449
+
450
+ const latestSuccess = shallowRef<any[]>()
451
+ const result = computed((): AsyncResult.AsyncResult<any[], any> =>
452
+ swrToQuery({
453
+ error: r.error.value ?? undefined,
454
+ data: r.data.value === undefined ? latestSuccess.value : r.data.value,
455
+ isValidating: r.isFetching.value
456
+ })
457
+ )
458
+ watch(result, (value) => latestSuccess.value = Option.getOrUndefined(AsyncResult.value(value)), { immediate: true })
459
+
460
+ return [
461
+ result,
462
+ computed(() => latestSuccess.value),
463
+ (options?: RefetchOptions) =>
464
+ Effect.currentSpan.pipe(
465
+ Effect.orElseSucceed(() => null),
466
+ Effect.flatMap((span) => Effect.promise(() => r.refetch({ ...options, updateMeta: { span } })))
467
+ ),
468
+ r
469
+ ] as any
470
+ }
471
+
472
+ return streamQuery_
473
+ }
474
+
475
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
476
+ export interface MakeStreamQuery2<R> extends ReturnType<typeof makeStreamQuery<R>> {}
477
+
365
478
  function orPrevious<E, A>(result: AsyncResult.AsyncResult<A, E>) {
366
479
  return AsyncResult.isFailure(result) && Option.isSome(result.previousSuccess)
367
480
  ? AsyncResult.success(result.previousSuccess.value, { waiting: result.waiting })
package/src/runtime.ts CHANGED
@@ -1,31 +1,52 @@
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>
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
- export function initializeSync<A, E>(layer: Layer.Layer<A, E, never>) {
23
+ export function initializeSync<A, E>(layer: Layer.Layer<A, E>) {
24
24
  const runtime = Effect.runSync(makeAppRuntime(layer))
25
25
  return runtime
26
26
  }
27
27
 
28
- export function initializeAsync<A, E>(layer: Layer.Layer<A, E, never>) {
28
+ export function initializeAsync<A, E>(layer: Layer.Layer<A, E>) {
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, Fiber, Layer, type Option } from "effect-app"
2
2
  import { wrapEffect } from "effect-app/utils"
3
- import { CurrentToastId, Toast } from "./toast.js"
3
+ import { CurrentToastId, Toast, type ToastId } 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) {
@@ -47,12 +48,20 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
47
48
  : options.stableToastId
48
49
 
49
50
  const t = yield* wrapEffect(options.onWaiting)(...args)
50
- const toastId = t === null ? stableToastId : yield* toast.info(
51
- t,
52
- { id: stableToastId ?? null } // TODO: timeout forever?
51
+ const toastId: ToastId | undefined = t === null
52
+ ? stableToastId
53
+ : stableToastId ?? `wait-${Math.random().toString(36).slice(2)}`
54
+
55
+ const waitingFiber = t === null ? undefined : yield* Effect.forkChild(
56
+ Effect.sleep("1 seconds").pipe(
57
+ Effect.andThen(toast.info(t, { id: toastId!, timeout: Infinity }))
58
+ )
53
59
  )
60
+ const interruptWaiting = waitingFiber ? Fiber.interrupt(waitingFiber) : Effect.void
61
+
54
62
  return yield* self.pipe(
55
63
  Effect.tap(Effect.fnUntraced(function*(a) {
64
+ yield* interruptWaiting
56
65
  const t = yield* wrapEffect(options.onSuccess)(a, ...args)
57
66
  if (t === null) {
58
67
  return
@@ -63,6 +72,7 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
63
72
  )
64
73
  })),
65
74
  Effect.tapCause(Effect.fnUntraced(function*(cause) {
75
+ yield* interruptWaiting
66
76
  yield* Effect.logDebug(
67
77
  "WithToast - caught error cause: " + Cause.squash(cause),
68
78
  Cause.hasInterruptsOnly(cause),
@@ -74,15 +84,23 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
74
84
  return
75
85
  }
76
86
 
87
+ const spanInfo = options.showSpanInfo !== false
88
+ ? yield* Effect.currentSpan.pipe(
89
+ Effect.map((span) => `\nTrace: ${span.traceId}\nSpan: ${span.spanId}`),
90
+ Effect.orElseSucceed(() => "")
91
+ )
92
+ : ""
93
+
77
94
  const t = yield* wrapEffect(options.onFailure)(Cause.findErrorOption(cause), ...args)
78
95
  const opts = { timeout: baseTimeout * 2 }
79
96
 
80
97
  if (typeof t === "object") {
98
+ const message = t.message + spanInfo
81
99
  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)
100
+ ? yield* toast.warning(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
101
+ : yield* toast.error(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
84
102
  }
85
- yield* toast.error(t, toastId !== undefined ? { ...opts, id: toastId } : opts)
103
+ yield* toast.error(t + spanInfo, toastId !== undefined ? { ...opts, id: toastId } : opts)
86
104
  }, Effect.uninterruptible)),
87
105
  toastId !== undefined ? Effect.provideService(CurrentToastId, CurrentToastId.of({ toastId })) : (_) => _
88
106
  )