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

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 (106) hide show
  1. package/CHANGELOG.md +1613 -0
  2. package/dist/commander.d.ts +634 -0
  3. package/dist/commander.d.ts.map +1 -0
  4. package/dist/commander.js +1070 -0
  5. package/dist/confirm.d.ts +21 -0
  6. package/dist/confirm.d.ts.map +1 -0
  7. package/dist/confirm.js +26 -0
  8. package/dist/errorReporter.d.ts +7 -5
  9. package/dist/errorReporter.d.ts.map +1 -1
  10. package/dist/errorReporter.js +14 -19
  11. package/dist/form.d.ts +15 -6
  12. package/dist/form.d.ts.map +1 -1
  13. package/dist/form.js +46 -13
  14. package/dist/index.d.ts +1 -1
  15. package/dist/intl.d.ts +15 -0
  16. package/dist/intl.d.ts.map +1 -0
  17. package/dist/intl.js +9 -0
  18. package/dist/lib.d.ts +8 -10
  19. package/dist/lib.d.ts.map +1 -1
  20. package/dist/lib.js +35 -10
  21. package/dist/makeClient.d.ts +156 -339
  22. package/dist/makeClient.d.ts.map +1 -1
  23. package/dist/makeClient.js +225 -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 +9 -0
  29. package/dist/makeUseCommand.d.ts.map +1 -0
  30. package/dist/makeUseCommand.js +13 -0
  31. package/dist/mutate.d.ts +54 -34
  32. package/dist/mutate.d.ts.map +1 -1
  33. package/dist/mutate.js +139 -46
  34. package/dist/query.d.ts +20 -39
  35. package/dist/query.d.ts.map +1 -1
  36. package/dist/query.js +133 -72
  37. package/dist/routeParams.d.ts +3 -2
  38. package/dist/routeParams.d.ts.map +1 -1
  39. package/dist/routeParams.js +4 -3
  40. package/dist/runtime.d.ts +10 -6
  41. package/dist/runtime.d.ts.map +1 -1
  42. package/dist/runtime.js +32 -18
  43. package/dist/toast.d.ts +51 -0
  44. package/dist/toast.d.ts.map +1 -0
  45. package/dist/toast.js +34 -0
  46. package/dist/withToast.d.ts +30 -0
  47. package/dist/withToast.d.ts.map +1 -0
  48. package/dist/withToast.js +64 -0
  49. package/examples/streamMutation.ts +72 -0
  50. package/package.json +48 -50
  51. package/src/commander.ts +3406 -0
  52. package/src/{experimental/confirm.ts → confirm.ts} +12 -14
  53. package/src/errorReporter.ts +65 -75
  54. package/src/form.ts +61 -18
  55. package/src/intl.ts +12 -0
  56. package/src/lib.ts +48 -20
  57. package/src/makeClient.ts +574 -1134
  58. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +8 -5
  59. package/src/mutate.ts +268 -127
  60. package/src/query.ts +203 -183
  61. package/src/routeParams.ts +3 -2
  62. package/src/runtime.ts +46 -21
  63. package/src/{experimental/toast.ts → toast.ts} +15 -27
  64. package/src/{experimental/withToast.ts → withToast.ts} +46 -12
  65. package/test/Mutation.test.ts +181 -24
  66. package/test/dist/form.test.d.ts.map +1 -1
  67. package/test/dist/lib.test.d.ts.map +1 -0
  68. package/test/dist/streamFinal.test.d.ts.map +1 -0
  69. package/test/dist/streamFn.test.d.ts.map +1 -0
  70. package/test/dist/stubs.d.ts +3531 -122
  71. package/test/dist/stubs.d.ts.map +1 -1
  72. package/test/dist/stubs.js +187 -32
  73. package/test/form-validation-errors.test.ts +25 -20
  74. package/test/form.test.ts +22 -3
  75. package/test/lib.test.ts +240 -0
  76. package/test/makeClient.test.ts +292 -38
  77. package/test/streamFinal.test.ts +64 -0
  78. package/test/streamFn.test.ts +457 -0
  79. package/test/stubs.ts +223 -43
  80. package/tsconfig.examples.json +20 -0
  81. package/tsconfig.json +0 -1
  82. package/tsconfig.json.bak +5 -2
  83. package/tsconfig.src.json +34 -34
  84. package/tsconfig.test.json +2 -2
  85. package/vitest.config.ts +5 -5
  86. package/dist/experimental/commander.d.ts +0 -359
  87. package/dist/experimental/commander.d.ts.map +0 -1
  88. package/dist/experimental/commander.js +0 -557
  89. package/dist/experimental/confirm.d.ts +0 -19
  90. package/dist/experimental/confirm.d.ts.map +0 -1
  91. package/dist/experimental/confirm.js +0 -28
  92. package/dist/experimental/intl.d.ts +0 -16
  93. package/dist/experimental/intl.d.ts.map +0 -1
  94. package/dist/experimental/intl.js +0 -5
  95. package/dist/experimental/makeUseCommand.d.ts +0 -8
  96. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  97. package/dist/experimental/makeUseCommand.js +0 -13
  98. package/dist/experimental/toast.d.ts +0 -47
  99. package/dist/experimental/toast.d.ts.map +0 -1
  100. package/dist/experimental/toast.js +0 -41
  101. package/dist/experimental/withToast.d.ts +0 -25
  102. package/dist/experimental/withToast.d.ts.map +0 -1
  103. package/dist/experimental/withToast.js +0 -45
  104. package/eslint.config.mjs +0 -24
  105. package/src/experimental/commander.ts +0 -1835
  106. package/src/experimental/intl.ts +0 -9
package/src/query.ts CHANGED
@@ -2,16 +2,28 @@
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 * as Array from "effect-app/Array"
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 type * as Context from "effect-app/Context"
12
+ import * as Effect from "effect-app/Effect"
13
+ import * as Option from "effect-app/Option"
14
+ import * as S from "effect-app/Schema"
15
+ import * as Cause from "effect/Cause"
16
+ import * as Channel from "effect/Channel"
17
+ import * as Exit from "effect/Exit"
18
+ import * as Pull from "effect/Pull"
19
+ import * as Scope from "effect/Scope"
20
+ import type * as Stream from "effect/Stream"
10
21
  import { type Span } from "effect/Tracer"
11
22
  import { isHttpClientError } from "effect/unstable/http/HttpClientError"
12
23
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
13
24
  import { computed, type ComputedRef, type MaybeRefOrGetter, ref, shallowRef, watch, type WatchSource } from "vue"
14
- import { makeQueryKey, reportRuntimeError } from "./lib.js"
25
+ import { reportRuntimeError } from "./lib.js"
26
+ import { makeRunPromise } from "./runtime.js"
15
27
 
16
28
  // 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
29
  // but because how they are dealing with some extends clause, we loose all properties except initialData
@@ -74,20 +86,68 @@ export interface CustomDefinedPlaceholderQueryOptions<
74
86
  | PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
75
87
  }
76
88
 
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)
89
+ function swrToQuery<E, A>(r: {
90
+ error: CauseException<E> | undefined
91
+ data: A | undefined
92
+ isValidating: boolean
93
+ }): AsyncResult.AsyncResult<A, E> {
94
+ if (r.error !== undefined) {
95
+ return AsyncResult.failureWithPrevious(
96
+ r.error.originalCause,
97
+ {
98
+ previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
99
+ waiting: r.isValidating
100
+ }
101
+ )
102
+ }
103
+ if (r.data !== undefined) {
104
+ return AsyncResult.success<A, E>(r.data, { waiting: r.isValidating })
82
105
  }
106
+
107
+ return AsyncResult.initial(r.isValidating)
83
108
  }
84
109
 
85
- export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
110
+ function streamToAsyncIterableWithCauseException<A, E, R>(
111
+ self: Stream.Stream<A, E, R>,
112
+ context: Context.Context<R>,
113
+ id: string
114
+ ): AsyncIterable<A> {
115
+ return {
116
+ [Symbol.asyncIterator]() {
117
+ const runPromise = Effect.runPromiseWith(context)
118
+ const runPromiseExit = Effect.runPromiseExitWith(context)
119
+ const scope = Scope.makeUnsafe()
120
+ let pull: any
121
+ let currentIter: Iterator<A> | undefined
122
+ return {
123
+ async next(): Promise<IteratorResult<A>> {
124
+ if (currentIter) {
125
+ const next = currentIter.next()
126
+ if (!next.done) return next
127
+ currentIter = undefined
128
+ }
129
+ pull ??= await runPromise(Channel.toPullScoped((self as any).channel, scope))
130
+ const exit = await runPromiseExit(pull)
131
+ if (Exit.isSuccess(exit)) {
132
+ currentIter = (exit.value as any)[Symbol.iterator]()
133
+ return currentIter!.next()
134
+ } else if (Pull.isDoneCause((exit as any).cause)) {
135
+ return { done: true, value: undefined }
136
+ }
137
+ throw new CauseException((exit as any).cause, id)
138
+ },
139
+ return(_) {
140
+ return runPromise(Effect.as(Scope.close(scope, Exit.void), { done: true, value: undefined }) as any)
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
86
148
  const useQuery_: {
87
149
  <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>
150
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
91
151
  ): {
92
152
  <TData = A>(
93
153
  arg: I | WatchSource<I> | undefined,
@@ -95,8 +155,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
95
155
  ): readonly [
96
156
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
97
157
  ComputedRef<TData | undefined>,
98
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
99
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
158
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
159
+ UseQueryDefinedReturnType<TData, CauseException<E>>
100
160
  ]
101
161
 
102
162
  <TData = A>(
@@ -105,8 +165,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
105
165
  ): readonly [
106
166
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
107
167
  ComputedRef<TData>,
108
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
109
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
168
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
169
+ UseQueryDefinedReturnType<TData, CauseException<E>>
110
170
  ]
111
171
 
112
172
  <TData = A>(
@@ -115,14 +175,12 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
115
175
  ): readonly [
116
176
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
117
177
  ComputedRef<TData>,
118
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
119
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
178
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
179
+ UseQueryDefinedReturnType<TData, CauseException<E>>
120
180
  ]
121
181
  }
122
182
  } = <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>
183
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
126
184
  ) =>
127
185
  <TData = A>(
128
186
  arg: I | WatchSource<I> | undefined,
@@ -130,26 +188,20 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
130
188
  options?: any
131
189
  // TODO
132
190
  ) => {
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
- ))
191
+ // we wrap into CauseException because we want to keep the full cause of the failure.
192
+ const runPromise = makeRunPromise(getRuntime())
141
193
  const arr = arg
142
- const req: { value: I } = !arg
194
+ const req: { value: I } | undefined = !arg
143
195
  ? undefined
144
196
  : typeof arr === "function"
145
197
  ? ({
146
198
  get value() {
147
199
  return (arr as any)()
148
200
  }
149
- } as any)
150
- : ref(arg)
201
+ })
202
+ : ref(arg) as any
151
203
  const queryKey = makeQueryKey(q)
152
- const handler = q.handler
204
+ const projectionHash = (q as { queryKeyProjectionHash?: string }).queryKeyProjectionHash
153
205
 
154
206
  const defaultOptions = {
155
207
  // we do not want to throw errors, because we turn the success and error responses into a Result type
@@ -161,57 +213,31 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
161
213
  throwOnError: false
162
214
  }
163
215
 
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
- )
216
+ const r = useTanstackQuery<A, CauseException<E>, TData>({
217
+ ...defaultOptions,
218
+ ...options,
219
+ retry: (retryCount, error) => {
220
+ if (error instanceof CauseException) {
221
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
222
+ return false
223
+ }
189
224
  }
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
225
 
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
- )
226
+ return retryCount < 5
227
+ },
228
+ queryKey: projectionHash === undefined ? [...queryKey, req] : [...queryKey, req, projectionHash],
229
+ queryFn: ({ meta, signal }) =>
230
+ runPromise(
231
+ q
232
+ .handler(req?.value as I)
233
+ .pipe(
234
+ Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause)),
235
+ Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
236
+ meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
237
+ ),
238
+ { signal }
239
+ )
240
+ })
215
241
 
216
242
  const latestSuccess = shallowRef<TData>()
217
243
  const result = computed((): AsyncResult.AsyncResult<TData, E> =>
@@ -239,129 +265,130 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
239
265
  ] as any
240
266
  }
241
267
 
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
268
  const useQuery: {
264
269
  /**
265
270
  * Effect results are passed to the caller, including errors.
271
+ * When `I = void` the input argument may be omitted.
266
272
  * @deprecated use client helpers instead (.query())
267
273
  */
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>
274
+ <I, E, A, Request extends Req, Name extends string>(
275
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
308
276
  ): {
309
- // required options, with initialData
310
- /**
311
- * Effect results are passed to the caller, including errors.
312
- */
313
277
  <TData = A>(
314
- arg: Arg | WatchSource<Arg>,
315
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
278
+ arg: I | WatchSource<I>,
279
+ options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
316
280
  ): readonly [
317
281
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
318
282
  ComputedRef<TData>,
319
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
283
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
320
284
  UseQueryReturnType<any, any>
321
285
  ]
322
- // required options, with placeholderData
323
- /**
324
- * Effect results are passed to the caller, including errors.
325
- */
326
286
  <TData = A>(
327
- arg: Arg | WatchSource<Arg>,
328
- options: CustomDefinedPlaceholderQueryOptions<A, KnownFiberFailure<E>, TData>
287
+ arg: I | WatchSource<I>,
288
+ options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
329
289
  ): readonly [
330
290
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
331
291
  ComputedRef<TData>,
332
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
292
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
333
293
  UseQueryReturnType<any, any>
334
294
  ]
335
- // optional options, optional A
336
- /**
337
- * Effect results are passed to the caller, including errors.
338
- */
339
295
  <TData = A>(
340
- arg: Arg | WatchSource<Arg>,
341
- options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
296
+ arg: I | WatchSource<I>,
297
+ options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
342
298
  ): readonly [
343
299
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
344
300
  ComputedRef<TData | undefined>,
345
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
301
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
346
302
  UseQueryReturnType<any, any>
347
303
  ]
348
304
  }
349
- } = (
305
+ } = ((
350
306
  self: any
351
307
  ) => {
352
308
  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
- }
309
+ return (arg?: any, options?: any) => q(arg, options)
310
+ }) as any
359
311
  return useQuery
360
312
  }
361
313
 
362
314
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
363
315
  export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
364
316
 
317
+ type StreamQueryResult<A, E> = readonly [
318
+ ComputedRef<AsyncResult.AsyncResult<A[], E>>,
319
+ ComputedRef<A[] | undefined>,
320
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<A[], CauseException<E>>>,
321
+ UseQueryReturnType<any, any>
322
+ ]
323
+
324
+ export const makeStreamQuery = <R>(getRuntime: () => Context.Context<R>) => {
325
+ const streamQuery_: {
326
+ <I, E, A, Request extends Req, Name extends string>(
327
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
328
+ ): (arg: I | WatchSource<I>) => StreamQueryResult<A, E>
329
+ } = (q: any) => (arg?: any) => {
330
+ const context = getRuntime()
331
+ const arr = arg
332
+ const req: { value: any } | undefined = !arg
333
+ ? undefined
334
+ : typeof arr === "function"
335
+ ? ({
336
+ get value() {
337
+ return arr()
338
+ }
339
+ })
340
+ : ref(arg)
341
+ const queryKey = makeQueryKey(q)
342
+
343
+ const r = useTanstackQuery<any[], CauseException<any>, any[]>(
344
+ {
345
+ throwOnError: false,
346
+ retry: (retryCount: number, error: unknown) => {
347
+ if (error instanceof CauseException) {
348
+ if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
349
+ return false
350
+ }
351
+ }
352
+ return retryCount < 5
353
+ },
354
+ queryKey: [...queryKey, req],
355
+ queryFn: streamedQuery({
356
+ streamFn: () => {
357
+ const stream = q.handler(req?.value)
358
+ return streamToAsyncIterableWithCauseException(stream, context, q.id)
359
+ }
360
+ })
361
+ }
362
+ )
363
+
364
+ const latestSuccess = shallowRef<any[]>()
365
+ const result = computed((): AsyncResult.AsyncResult<any[], any> =>
366
+ swrToQuery({
367
+ error: r.error.value ?? undefined,
368
+ data: r.data.value === undefined ? latestSuccess.value : r.data.value,
369
+ isValidating: r.isFetching.value
370
+ })
371
+ )
372
+ watch(result, (value) => latestSuccess.value = Option.getOrUndefined(AsyncResult.value(value)), { immediate: true })
373
+
374
+ return [
375
+ result,
376
+ computed(() => latestSuccess.value),
377
+ (options?: RefetchOptions) =>
378
+ Effect.currentSpan.pipe(
379
+ Effect.orElseSucceed(() => null),
380
+ Effect.flatMap((span) => Effect.promise(() => r.refetch({ ...options, updateMeta: { span } })))
381
+ ),
382
+ r
383
+ ] as any
384
+ }
385
+
386
+ return streamQuery_
387
+ }
388
+
389
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
390
+ export interface MakeStreamQuery2<R> extends ReturnType<typeof makeStreamQuery<R>> {}
391
+
365
392
  function orPrevious<E, A>(result: AsyncResult.AsyncResult<A, E>) {
366
393
  return AsyncResult.isFailure(result) && Option.isSome(result.previousSuccess)
367
394
  ? AsyncResult.success(result.previousSuccess.value, { waiting: result.waiting })
@@ -412,20 +439,13 @@ export const useUpdateQuery = () => {
412
439
  const queryClient = useQueryClient()
413
440
 
414
441
  const f: {
415
- <A>(
416
- query: RequestHandler<A, any, any, any, any>,
417
- updater: (data: NoInfer<A>) => NoInfer<A>
418
- ): void
419
442
  <I, A>(
420
443
  query: RequestHandlerWithInput<I, A, any, any, any, any>,
421
444
  input: I,
422
445
  updater: (data: NoInfer<A>) => NoInfer<A>
423
446
  ): 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)
447
+ } = (query: any, input: any, updater: any) => {
448
+ const key = [...makeQueryKey(query), input]
429
449
  const data = queryClient.getQueryData(key)
430
450
  if (data) {
431
451
  queryClient.setQueryData(key, updater)
@@ -1,7 +1,8 @@
1
- import { flow } from "effect"
2
- import { Option, S } from "effect-app"
1
+ import * as Option from "effect-app/Option"
2
+ import * as S from "effect-app/Schema"
3
3
  import type { Schema } from "effect-app/Schema"
4
4
  import { typedKeysOf } from "effect-app/utils"
5
+ import { flow } from "effect/Function"
5
6
  import type { ParsedQuery } from "query-string"
6
7
 
7
8
  export function getQueryParam(search: ParsedQuery, param: string) {
package/src/runtime.ts CHANGED
@@ -1,31 +1,56 @@
1
- import { ManagedRuntime } from "effect"
2
- import { Effect, Layer, Logger } from "effect-app"
1
+ import { CauseException } from "effect-app/client/errors"
2
+ import { type Context } from "effect-app/Context"
3
+ import * as Effect from "effect-app/Effect"
4
+ import * as Layer from "effect-app/Layer"
5
+ import * as Exit from "effect/Exit"
6
+ import { flow } from "effect/Function"
7
+ import * as Logger from "effect/Logger"
8
+ import * as ManagedRuntime from "effect/ManagedRuntime"
3
9
 
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
- },
10
+ export const makeAppRuntime = Effect.fnUntraced(function*<A, E>(layer: Layer.Layer<A, E>) {
11
+ const l = layer.pipe(
12
+ Layer.provide(Logger.layer([Logger.consolePretty()]))
13
+ ) as Layer.Layer<A>
14
+ const mrt = ManagedRuntime.make(l)
15
+ yield* mrt.contextEffect
16
+ return Object.assign(mrt, {
17
+ [Symbol.dispose]() {
18
+ return Effect.runSync(mrt.disposeEffect)
19
+ },
15
20
 
16
- [Symbol.asyncDispose]() {
17
- return mrt.dispose()
18
- }
19
- }) // as we initialise here, there is no more error left.
20
- })
21
- }
21
+ [Symbol.asyncDispose]() {
22
+ return mrt.dispose()
23
+ }
24
+ }) // as we initialise here, there is no more error left.
25
+ })
22
26
 
23
- export function initializeSync<A, E>(layer: Layer.Layer<A, E, never>) {
27
+ export function initializeSync<A, E>(layer: Layer.Layer<A, E>) {
24
28
  const runtime = Effect.runSync(makeAppRuntime(layer))
25
29
  return runtime
26
30
  }
27
31
 
28
- export function initializeAsync<A, E>(layer: Layer.Layer<A, E, never>) {
32
+ export function initializeAsync<A, E>(layer: Layer.Layer<A, E>) {
29
33
  return Effect
30
34
  .runPromise(makeAppRuntime(layer))
31
35
  }
36
+
37
+ // we wrap into CauseException because we want to keep the full cause of the failure.
38
+ export const makeRunPromise = <T>(services: Context<T>) =>
39
+ flow(Effect.runPromiseExitWith(services), (_) =>
40
+ _.then(
41
+ Exit.match({
42
+ onFailure: (cause) => Promise.reject(new CauseException(cause, "runPromise")),
43
+ onSuccess: (value) => Promise.resolve(value)
44
+ })
45
+ ))
46
+
47
+ export const makeRunSync = <T>(services: Context<T>) =>
48
+ flow(
49
+ Effect.runSyncExitWith(services),
50
+ Exit.match({
51
+ onFailure: (cause) => {
52
+ throw new CauseException(cause, "runSync")
53
+ },
54
+ onSuccess: (value) => value
55
+ })
56
+ )
@@ -1,9 +1,11 @@
1
- import { Effect, Option, ServiceMap } from "effect-app"
2
- import { proxify } from "effect-app/ServiceMap"
1
+ import * as Context from "effect-app/Context"
2
+ import { accessEffectFn } from "effect-app/Context"
3
+ import * as Effect from "effect-app/Effect"
4
+ import * as Option from "effect-app/Option"
3
5
 
4
6
  export type ToastId = string | number
5
- export type ToastOpts = { id?: ToastId; timeout?: number }
6
- export type ToastOptsInternal = { id?: ToastId | null; timeout?: number }
7
+ export type ToastOpts = { id?: ToastId; timeout?: number; groupId?: string; requestId?: string }
8
+ export type ToastOptsInternal = { id?: ToastId | null; timeout?: number; groupId?: string; requestId?: string }
7
9
 
8
10
  export type UseToast = () => {
9
11
  error: (this: void, message: string, options?: ToastOpts) => ToastId
@@ -13,7 +15,7 @@ export type UseToast = () => {
13
15
  dismiss: (this: void, id: ToastId) => void
14
16
  }
15
17
 
16
- export class CurrentToastId extends ServiceMap.Opaque<CurrentToastId, { toastId: ToastId }>()("CurrentToastId") {}
18
+ export class CurrentToastId extends Context.Opaque<CurrentToastId, { toastId: ToastId }>()("CurrentToastId") {}
17
19
 
18
20
  /** fallback to CurrentToastId when available unless id is explicitly set to a value or null */
19
21
  export const wrap = (toast: ReturnType<UseToast>) => {
@@ -41,26 +43,12 @@ export const wrap = (toast: ReturnType<UseToast>) => {
41
43
  }
42
44
  }
43
45
 
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
- // })
46
+ type ToastShape = ReturnType<typeof wrap>
58
47
 
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
- // })
48
+ export class Toast extends Context.Opaque<Toast, ToastShape>()("Toast") {
49
+ static readonly error = accessEffectFn(this, "error")
50
+ static readonly info = accessEffectFn(this, "info")
51
+ static readonly success = accessEffectFn(this, "success")
52
+ static readonly warning = accessEffectFn(this, "warning")
53
+ static readonly dismiss = accessEffectFn(this, "dismiss")
54
+ }