@effect-app/vue 4.0.0-beta.22 → 4.0.0-beta.220

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 +1605 -0
  2. package/dist/commander.d.ts +628 -0
  3. package/dist/commander.d.ts.map +1 -0
  4. package/dist/commander.js +1060 -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 -9
  19. package/dist/lib.d.ts.map +1 -1
  20. package/dist/lib.js +35 -10
  21. package/dist/makeClient.d.ts +152 -339
  22. package/dist/makeClient.d.ts.map +1 -1
  23. package/dist/makeClient.js +221 -376
  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 +52 -34
  32. package/dist/mutate.d.ts.map +1 -1
  33. package/dist/mutate.js +137 -46
  34. package/dist/query.d.ts +19 -39
  35. package/dist/query.d.ts.map +1 -1
  36. package/dist/query.js +128 -72
  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 +50 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/toast.js +32 -0
  44. package/dist/withToast.d.ts +27 -0
  45. package/dist/withToast.d.ts.map +1 -0
  46. package/dist/withToast.js +59 -0
  47. package/examples/streamMutation.ts +70 -0
  48. package/package.json +48 -50
  49. package/src/commander.ts +3393 -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 +47 -20
  55. package/src/makeClient.ts +568 -1134
  56. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +6 -4
  57. package/src/mutate.ts +265 -127
  58. package/src/query.ts +197 -183
  59. package/src/runtime.ts +41 -20
  60. package/src/{experimental/toast.ts → toast.ts} +13 -27
  61. package/src/{experimental/withToast.ts → withToast.ts} +40 -12
  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 +3529 -122
  68. package/test/dist/stubs.d.ts.map +1 -1
  69. package/test/dist/stubs.js +182 -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 +292 -38
  74. package/test/streamFinal.test.ts +63 -0
  75. package/test/streamFn.test.ts +455 -0
  76. package/test/stubs.ts +218 -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 { RequestHandlerWithInput, 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,20 +80,68 @@ 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 })
82
99
  }
100
+
101
+ return AsyncResult.initial(r.isValidating)
83
102
  }
84
103
 
85
- export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
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
+ }
138
+ }
139
+ }
140
+
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
- q:
89
- | RequestHandlerWithInput<I, A, E, R, Request, Name>
90
- | RequestHandler<A, E, R, Request, Name>
144
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
91
145
  ): {
92
146
  <TData = A>(
93
147
  arg: I | WatchSource<I> | undefined,
@@ -95,8 +149,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
95
149
  ): readonly [
96
150
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
97
151
  ComputedRef<TData | undefined>,
98
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
99
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
152
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
153
+ UseQueryDefinedReturnType<TData, CauseException<E>>
100
154
  ]
101
155
 
102
156
  <TData = A>(
@@ -105,8 +159,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
105
159
  ): readonly [
106
160
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
107
161
  ComputedRef<TData>,
108
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
109
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
162
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
163
+ UseQueryDefinedReturnType<TData, CauseException<E>>
110
164
  ]
111
165
 
112
166
  <TData = A>(
@@ -115,14 +169,12 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
115
169
  ): readonly [
116
170
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
117
171
  ComputedRef<TData>,
118
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
119
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
172
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
173
+ UseQueryDefinedReturnType<TData, CauseException<E>>
120
174
  ]
121
175
  }
122
176
  } = <I, A, E, Request extends Req, Name extends string>(
123
- q:
124
- | RequestHandlerWithInput<I, A, E, R, Request, Name>
125
- | RequestHandler<A, E, R, Request, Name>
177
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
126
178
  ) =>
127
179
  <TData = A>(
128
180
  arg: I | WatchSource<I> | undefined,
@@ -130,26 +182,20 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
130
182
  options?: any
131
183
  // TODO
132
184
  ) => {
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
- ))
185
+ // we wrap into CauseException because we want to keep the full cause of the failure.
186
+ const runPromise = makeRunPromise(getRuntime())
141
187
  const arr = arg
142
- const req: { value: I } = !arg
188
+ const req: { value: I } | undefined = !arg
143
189
  ? undefined
144
190
  : typeof arr === "function"
145
191
  ? ({
146
192
  get value() {
147
193
  return (arr as any)()
148
194
  }
149
- } as any)
150
- : ref(arg)
195
+ })
196
+ : ref(arg) as any
151
197
  const queryKey = makeQueryKey(q)
152
- const handler = q.handler
198
+ const projectionHash = (q as { queryKeyProjectionHash?: string }).queryKeyProjectionHash
153
199
 
154
200
  const defaultOptions = {
155
201
  // we do not want to throw errors, because we turn the success and error responses into a Result type
@@ -161,57 +207,31 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
161
207
  throwOnError: false
162
208
  }
163
209
 
164
- const r = useTanstackQuery<A, KnownFiberFailure<E>, TData>(
165
- Effect.isEffect(handler)
166
- ? {
167
- ...defaultOptions,
168
- ...options,
169
- retry: (retryCount, error) => {
170
- if (error instanceof KnownFiberFailure) {
171
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
172
- return false
173
- }
174
- }
175
-
176
- return retryCount < 5
177
- },
178
- queryKey,
179
- queryFn: ({ meta, signal }) =>
180
- runPromise(
181
- handler
182
- .pipe(
183
- Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause)),
184
- Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
185
- meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
186
- ),
187
- { signal }
188
- )
210
+ const r = useTanstackQuery<A, CauseException<E>, TData>({
211
+ ...defaultOptions,
212
+ ...options,
213
+ retry: (retryCount, error) => {
214
+ if (error instanceof CauseException) {
215
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
216
+ return false
217
+ }
189
218
  }
190
- : {
191
- ...defaultOptions,
192
- ...options,
193
- retry: (retryCount, error) => {
194
- if (error instanceof KnownFiberFailure) {
195
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
196
- return false
197
- }
198
- }
199
219
 
200
- return retryCount < 5
201
- },
202
- queryKey: [...queryKey, req],
203
- queryFn: ({ meta, signal }) =>
204
- runPromise(
205
- handler(req.value)
206
- .pipe(
207
- Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause)),
208
- Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
209
- meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
210
- ),
211
- { signal }
212
- )
213
- }
214
- )
220
+ return retryCount < 5
221
+ },
222
+ queryKey: projectionHash === undefined ? [...queryKey, req] : [...queryKey, req, projectionHash],
223
+ queryFn: ({ meta, signal }) =>
224
+ runPromise(
225
+ q
226
+ .handler(req?.value as I)
227
+ .pipe(
228
+ Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause)),
229
+ Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
230
+ meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
231
+ ),
232
+ { signal }
233
+ )
234
+ })
215
235
 
216
236
  const latestSuccess = shallowRef<TData>()
217
237
  const result = computed((): AsyncResult.AsyncResult<TData, E> =>
@@ -239,129 +259,130 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
239
259
  ] as any
240
260
  }
241
261
 
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
262
  const useQuery: {
264
263
  /**
265
264
  * Effect results are passed to the caller, including errors.
265
+ * When `I = void` the input argument may be omitted.
266
266
  * @deprecated use client helpers instead (.query())
267
267
  */
268
- <E, A, Request extends Req, Name extends string>(
269
- self: RequestHandler<A, E, R, Request, Name>
270
- ): {
271
- // required options, with initialData
272
- /**
273
- * Effect results are passed to the caller, including errors.
274
- */
275
- <TData = A>(
276
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
277
- ): readonly [
278
- ComputedRef<AsyncResult.AsyncResult<TData, E>>,
279
- ComputedRef<TData>,
280
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
281
- UseQueryReturnType<any, any>
282
- ]
283
- <TData = A>(
284
- options: CustomDefinedPlaceholderQueryOptions<A, E, TData>
285
- ): readonly [
286
- ComputedRef<AsyncResult.AsyncResult<TData, E>>,
287
- ComputedRef<TData>,
288
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
289
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
290
- ]
291
- // optional options, optional A
292
- /**
293
- * Effect results are passed to the caller, including errors.
294
- */
295
- <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>): readonly [
296
- ComputedRef<AsyncResult.AsyncResult<A, E>>,
297
- ComputedRef<A | undefined>,
298
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
299
- UseQueryReturnType<any, any>
300
- ]
301
- }
302
- /**
303
- * Effect results are passed to the caller, including errors.
304
- * @deprecated use client helpers instead (.query())
305
- */
306
- <Arg, E, A, Request extends Req, Name extends string>(
307
- self: RequestHandlerWithInput<Arg, A, E, R, Request, Name>
268
+ <I, E, A, Request extends Req, Name extends string>(
269
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
308
270
  ): {
309
- // required options, with initialData
310
- /**
311
- * Effect results are passed to the caller, including errors.
312
- */
313
271
  <TData = A>(
314
- arg: Arg | WatchSource<Arg>,
315
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
272
+ arg: I | WatchSource<I>,
273
+ options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
316
274
  ): readonly [
317
275
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
318
276
  ComputedRef<TData>,
319
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
277
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
320
278
  UseQueryReturnType<any, any>
321
279
  ]
322
- // required options, with placeholderData
323
- /**
324
- * Effect results are passed to the caller, including errors.
325
- */
326
280
  <TData = A>(
327
- arg: Arg | WatchSource<Arg>,
328
- options: CustomDefinedPlaceholderQueryOptions<A, KnownFiberFailure<E>, TData>
281
+ arg: I | WatchSource<I>,
282
+ options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
329
283
  ): readonly [
330
284
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
331
285
  ComputedRef<TData>,
332
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
286
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
333
287
  UseQueryReturnType<any, any>
334
288
  ]
335
- // optional options, optional A
336
- /**
337
- * Effect results are passed to the caller, including errors.
338
- */
339
289
  <TData = A>(
340
- arg: Arg | WatchSource<Arg>,
341
- options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
290
+ arg: I | WatchSource<I>,
291
+ options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
342
292
  ): readonly [
343
293
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
344
294
  ComputedRef<TData | undefined>,
345
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
295
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
346
296
  UseQueryReturnType<any, any>
347
297
  ]
348
298
  }
349
- } = (
299
+ } = ((
350
300
  self: any
351
301
  ) => {
352
302
  const q = useQuery_(self)
353
-
354
- return (argOrOptions?: any, options?: any) =>
355
- Effect.isEffect(self.handler)
356
- ? q(undefined, argOrOptions)
357
- : q(argOrOptions, options)
358
- }
303
+ return (arg?: any, options?: any) => q(arg, options)
304
+ }) as any
359
305
  return useQuery
360
306
  }
361
307
 
362
308
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
363
309
  export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
364
310
 
311
+ type StreamQueryResult<A, E> = readonly [
312
+ ComputedRef<AsyncResult.AsyncResult<A[], E>>,
313
+ ComputedRef<A[] | undefined>,
314
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<A[], CauseException<E>>>,
315
+ UseQueryReturnType<any, any>
316
+ ]
317
+
318
+ export const makeStreamQuery = <R>(getRuntime: () => Context.Context<R>) => {
319
+ const streamQuery_: {
320
+ <I, E, A, Request extends Req, Name extends string>(
321
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
322
+ ): (arg: I | WatchSource<I>) => StreamQueryResult<A, E>
323
+ } = (q: any) => (arg?: any) => {
324
+ const context = getRuntime()
325
+ const arr = arg
326
+ const req: { value: any } | undefined = !arg
327
+ ? undefined
328
+ : typeof arr === "function"
329
+ ? ({
330
+ get value() {
331
+ return arr()
332
+ }
333
+ })
334
+ : ref(arg)
335
+ const queryKey = makeQueryKey(q)
336
+
337
+ const r = useTanstackQuery<any[], CauseException<any>, any[]>(
338
+ {
339
+ throwOnError: false,
340
+ retry: (retryCount: number, error: unknown) => {
341
+ if (error instanceof CauseException) {
342
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
343
+ return false
344
+ }
345
+ }
346
+ return retryCount < 5
347
+ },
348
+ queryKey: [...queryKey, req],
349
+ queryFn: streamedQuery({
350
+ streamFn: () => {
351
+ const stream = q.handler(req?.value)
352
+ return streamToAsyncIterableWithCauseException(stream, context, q.id)
353
+ }
354
+ })
355
+ }
356
+ )
357
+
358
+ const latestSuccess = shallowRef<any[]>()
359
+ const result = computed((): AsyncResult.AsyncResult<any[], any> =>
360
+ swrToQuery({
361
+ error: r.error.value ?? undefined,
362
+ data: r.data.value === undefined ? latestSuccess.value : r.data.value,
363
+ isValidating: r.isFetching.value
364
+ })
365
+ )
366
+ watch(result, (value) => latestSuccess.value = Option.getOrUndefined(AsyncResult.value(value)), { immediate: true })
367
+
368
+ return [
369
+ result,
370
+ computed(() => latestSuccess.value),
371
+ (options?: RefetchOptions) =>
372
+ Effect.currentSpan.pipe(
373
+ Effect.orElseSucceed(() => null),
374
+ Effect.flatMap((span) => Effect.promise(() => r.refetch({ ...options, updateMeta: { span } })))
375
+ ),
376
+ r
377
+ ] as any
378
+ }
379
+
380
+ return streamQuery_
381
+ }
382
+
383
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
384
+ export interface MakeStreamQuery2<R> extends ReturnType<typeof makeStreamQuery<R>> {}
385
+
365
386
  function orPrevious<E, A>(result: AsyncResult.AsyncResult<A, E>) {
366
387
  return AsyncResult.isFailure(result) && Option.isSome(result.previousSuccess)
367
388
  ? AsyncResult.success(result.previousSuccess.value, { waiting: result.waiting })
@@ -412,20 +433,13 @@ export const useUpdateQuery = () => {
412
433
  const queryClient = useQueryClient()
413
434
 
414
435
  const f: {
415
- <A>(
416
- query: RequestHandler<A, any, any, any, any>,
417
- updater: (data: NoInfer<A>) => NoInfer<A>
418
- ): void
419
436
  <I, A>(
420
437
  query: RequestHandlerWithInput<I, A, any, any, any, any>,
421
438
  input: I,
422
439
  updater: (data: NoInfer<A>) => NoInfer<A>
423
440
  ): void
424
- } = (query: any, updateOrInput: any, updaterMaybe?: any) => {
425
- const updater = updaterMaybe !== undefined ? updaterMaybe : updateOrInput
426
- const key = updaterMaybe !== undefined
427
- ? [...makeQueryKey(query), updateOrInput]
428
- : makeQueryKey(query)
441
+ } = (query: any, input: any, updater: any) => {
442
+ const key = [...makeQueryKey(query), input]
429
443
  const data = queryClient.getQueryData(key)
430
444
  if (data) {
431
445
  queryClient.setQueryData(key, updater)
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,9 +1,9 @@
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
- export type ToastOpts = { id?: ToastId; timeout?: number }
6
- export type ToastOptsInternal = { id?: ToastId | null; timeout?: number }
5
+ export type ToastOpts = { id?: ToastId; timeout?: number; groupId?: string; requestId?: string }
6
+ export type ToastOptsInternal = { id?: ToastId | null; timeout?: number; groupId?: string; requestId?: string }
7
7
 
8
8
  export type UseToast = () => {
9
9
  error: (this: void, message: string, options?: ToastOpts) => ToastId
@@ -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
+ }