@effect-app/vue 4.0.0-beta.182 → 4.0.0-beta.183
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.
- package/CHANGELOG.md +35 -0
- package/dist/commander.d.ts +189 -2
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +314 -2
- package/dist/makeClient.d.ts +52 -5
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +46 -13
- package/dist/makeUseCommand.d.ts +2 -2
- package/dist/makeUseCommand.d.ts.map +1 -1
- package/dist/makeUseCommand.js +1 -1
- package/dist/mutate.d.ts +12 -8
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +28 -8
- package/dist/query.d.ts +14 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +105 -15
- package/package.json +2 -2
- package/src/commander.ts +533 -2
- package/src/makeClient.ts +115 -11
- package/src/makeUseCommand.ts +3 -1
- package/src/mutate.ts +46 -7
- package/src/query.ts +148 -24
- package/test/dist/stubs.d.ts +184 -38
- package/test/dist/stubs.d.ts.map +1 -1
package/src/makeClient.ts
CHANGED
|
@@ -7,16 +7,19 @@ import type { ExtractModuleName, RequestHandler, RequestHandlers, RequestHandler
|
|
|
7
7
|
import type { InvalidationCallback } from "effect-app/client/makeClient"
|
|
8
8
|
import type * as ExitResult from "effect/Exit"
|
|
9
9
|
import { type Fiber } from "effect/Fiber"
|
|
10
|
+
import * as Stream from "effect/Stream"
|
|
10
11
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
11
12
|
import { computed, type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
|
|
12
13
|
import { type Commander, CommanderStatic, type Progress } from "./commander.js"
|
|
13
14
|
import { type I18n } from "./intl.js"
|
|
14
15
|
import { type CommanderResolved, makeUseCommand } from "./makeUseCommand.js"
|
|
15
|
-
import { makeMutation, makeStreamMutation, type MutationOptionsBase, useMakeMutation } from "./mutate.js"
|
|
16
|
-
import { type CustomUndefinedInitialQueryOptions, makeQuery } from "./query.js"
|
|
16
|
+
import { makeMutation, makeStreamMutation, makeStreamMutation2, type MutationOptionsBase, useMakeMutation } from "./mutate.js"
|
|
17
|
+
import { type CustomUndefinedInitialQueryOptions, makeQuery, makeStreamQuery } from "./query.js"
|
|
17
18
|
import { makeRunPromise } from "./runtime.js"
|
|
18
19
|
import { type Toast } from "./toast.js"
|
|
19
20
|
|
|
21
|
+
export type { Progress }
|
|
22
|
+
|
|
20
23
|
const mapHandler = <A, E, R, I = void, A2 = A, E2 = E, R2 = R>(
|
|
21
24
|
handler: Effect.Effect<A, E, R> | ((i: I) => Effect.Effect<A, E, R>),
|
|
22
25
|
map: (self: Effect.Effect<A, E, R>, i: I) => Effect.Effect<A2, E2, R2>
|
|
@@ -292,11 +295,42 @@ export type StreamFnExtension<RT, Req> = Req extends
|
|
|
292
295
|
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
293
296
|
: never
|
|
294
297
|
|
|
298
|
+
/**
|
|
299
|
+
* The `streamFn` builder for a stream-type request handler, using the stream-specific overloads.
|
|
300
|
+
*/
|
|
301
|
+
export type StreamFnStreamExtension<RT, Req> = Req extends
|
|
302
|
+
RequestStreamHandlerWithInput<infer _I, infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
303
|
+
? Commander.StreamGen<RT, Id, Id, undefined> & Commander.NonGenStream<RT, Id, Id, undefined>
|
|
304
|
+
: Req extends RequestStreamHandler<infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
305
|
+
? Commander.StreamGen<RT, Id, Id, undefined> & Commander.NonGenStream<RT, Id, Id, undefined>
|
|
306
|
+
: never
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* `mutateStream2` factory — like `mutateStream` but returns `Effect<Stream>` per invocation
|
|
310
|
+
* for use with `streamFn` combinators. Handles invalidation via `Stream.ensuring`.
|
|
311
|
+
*/
|
|
312
|
+
export type StreamMutation2WithExtensions<RT, Req> = Req extends
|
|
313
|
+
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer _Final> ?
|
|
314
|
+
& ((input: I) => Effect.Effect<Stream.Stream<A, E, R>>)
|
|
315
|
+
& {
|
|
316
|
+
readonly id: Id
|
|
317
|
+
readonly wrapStream: Commander.StreamGen<RT, Id, Id, undefined> & Commander.NonGenStream<RT, Id, Id, undefined>
|
|
318
|
+
}
|
|
319
|
+
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer _Final> ?
|
|
320
|
+
& Effect.Effect<Stream.Stream<A, E, R>>
|
|
321
|
+
& {
|
|
322
|
+
readonly id: Id
|
|
323
|
+
readonly wrapStream: Commander.StreamGen<RT, Id, Id, undefined> & Commander.NonGenStream<RT, Id, Id, undefined>
|
|
324
|
+
}
|
|
325
|
+
: never
|
|
326
|
+
|
|
295
327
|
// we don't really care about the RT, as we are in charge of ensuring runtime safety anyway
|
|
296
328
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
297
329
|
declare const useQuery_: QueryImpl<any>["useQuery"]
|
|
298
330
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
299
331
|
declare const useSuspenseQuery_: QueryImpl<any>["useSuspenseQuery"]
|
|
332
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
333
|
+
declare const useStreamQuery_: QueryImpl<any>["useStreamQuery"]
|
|
300
334
|
|
|
301
335
|
export interface ProjectResult<RT, I, B, E, R, Request extends Req, Id extends string> {
|
|
302
336
|
request: (i: I) => Effect.Effect<B, E, R>
|
|
@@ -387,6 +421,32 @@ export type Queries<RT, Req> = Req extends
|
|
|
387
421
|
: never
|
|
388
422
|
: never
|
|
389
423
|
|
|
424
|
+
export interface StreamQueriesWithInput<Request extends Req, Id extends string, I, A, E> {
|
|
425
|
+
/**
|
|
426
|
+
* Stream helper for stream requests.
|
|
427
|
+
* Runs as a tracked Vue Query and returns reactive state with accumulated chunks.
|
|
428
|
+
* Data is an array of all chunks received so far.
|
|
429
|
+
*/
|
|
430
|
+
streamQuery: ReturnType<typeof useStreamQuery_<I, E, A, Request, Id>>
|
|
431
|
+
}
|
|
432
|
+
export interface StreamQueriesWithoutInput<Request extends Req, Id extends string, A, E> {
|
|
433
|
+
/**
|
|
434
|
+
* Stream helper for stream requests.
|
|
435
|
+
* Runs as a tracked Vue Query and returns reactive state with accumulated chunks.
|
|
436
|
+
* Data is an array of all chunks received so far.
|
|
437
|
+
*/
|
|
438
|
+
streamQuery: ReturnType<typeof useStreamQuery_<E, A, Request, Id>>
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export type StreamQueries<RT, HandlerReq> = HandlerReq extends
|
|
442
|
+
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer _Final>
|
|
443
|
+
? Exclude<R, RT> extends never ? StreamQueriesWithInput<Request, Id, I, A, E>
|
|
444
|
+
: { streamQuery: MissingDependencies<RT, R> & {} }
|
|
445
|
+
: HandlerReq extends RequestStreamHandler<infer A, infer E, infer R, infer Request, infer Id, infer _Final>
|
|
446
|
+
? Exclude<R, RT> extends never ? StreamQueriesWithoutInput<Request, Id, A, E>
|
|
447
|
+
: { streamQuery: MissingDependencies<RT, R> & {} }
|
|
448
|
+
: never
|
|
449
|
+
|
|
390
450
|
const _useMutation = makeMutation()
|
|
391
451
|
|
|
392
452
|
const wrapWithSpan = (self: { id: string; handler: any }, mut: any) => {
|
|
@@ -445,6 +505,7 @@ export type ClientFrom<M extends RequestsAny> = RequestHandlers<never, never, M,
|
|
|
445
505
|
export class QueryImpl<R> {
|
|
446
506
|
constructor(readonly getRuntime: () => Context.Context<R>) {
|
|
447
507
|
this.useQuery = makeQuery(this.getRuntime)
|
|
508
|
+
this.useStreamQuery = makeStreamQuery(this.getRuntime)
|
|
448
509
|
}
|
|
449
510
|
/**
|
|
450
511
|
* Effect results are passed to the caller, including errors.
|
|
@@ -452,6 +513,12 @@ export class QueryImpl<R> {
|
|
|
452
513
|
*/
|
|
453
514
|
readonly useQuery: ReturnType<typeof makeQuery<R>>
|
|
454
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Stream results are accumulated as an array of chunks and returned as reactive state.
|
|
518
|
+
* @deprecated use client helpers instead (.streamQuery())
|
|
519
|
+
*/
|
|
520
|
+
readonly useStreamQuery: ReturnType<typeof makeStreamQuery<R>>
|
|
521
|
+
|
|
455
522
|
/**
|
|
456
523
|
* The difference with useQuery is that this function will return a Promise you can await in the Setup,
|
|
457
524
|
* which ensures that either there always is a latest value, or an error occurs on load.
|
|
@@ -649,9 +716,13 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
649
716
|
let sm: ReturnType<typeof makeStreamMutation>
|
|
650
717
|
const useStreamMutation = () => sm ??= makeStreamMutation()
|
|
651
718
|
|
|
719
|
+
let sm2: ReturnType<typeof makeStreamMutation2>
|
|
720
|
+
const useStreamMutation2 = () => sm2 ??= makeStreamMutation2()
|
|
721
|
+
|
|
652
722
|
const query = new QueryImpl(getBaseRt)
|
|
653
723
|
const useQuery = query.useQuery
|
|
654
724
|
const useSuspenseQuery = query.useSuspenseQuery
|
|
725
|
+
const useStreamQuery = query.useStreamQuery
|
|
655
726
|
|
|
656
727
|
const mergeInvalidation = (
|
|
657
728
|
a?: MutationOptionsBase["queryInvalidation"],
|
|
@@ -695,15 +766,19 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
695
766
|
) => {
|
|
696
767
|
const queries = Struct.keys(client).reduce(
|
|
697
768
|
(acc, key) => {
|
|
698
|
-
|
|
699
|
-
|
|
769
|
+
const requestType = client[key].Request.type
|
|
770
|
+
if (requestType === "query") {
|
|
771
|
+
;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(client[key] as any), {
|
|
772
|
+
id: client[key].id
|
|
773
|
+
})
|
|
774
|
+
;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(client[key] as any), {
|
|
775
|
+
id: client[key].id
|
|
776
|
+
})
|
|
777
|
+
} else if (requestType === "stream") {
|
|
778
|
+
;(acc as any)[camelCase(key) + "StreamQuery"] = Object.assign(useStreamQuery(client[key] as any), {
|
|
779
|
+
id: client[key].id
|
|
780
|
+
})
|
|
700
781
|
}
|
|
701
|
-
;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(client[key] as any), {
|
|
702
|
-
id: client[key].id
|
|
703
|
-
})
|
|
704
|
-
;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(client[key] as any), {
|
|
705
|
-
id: client[key].id
|
|
706
|
-
})
|
|
707
782
|
return acc
|
|
708
783
|
},
|
|
709
784
|
{} as
|
|
@@ -725,6 +800,12 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
725
800
|
QueryHandler<typeof client[Key]>
|
|
726
801
|
>["suspense"]
|
|
727
802
|
}
|
|
803
|
+
& {
|
|
804
|
+
[
|
|
805
|
+
Key in keyof typeof client as StreamHandler<typeof client[Key]> extends never ? never
|
|
806
|
+
: `${ToCamel<string & Key>}StreamQuery`
|
|
807
|
+
]: StreamQueries<RT, StreamHandler<typeof client[Key]>>["streamQuery"]
|
|
808
|
+
}
|
|
728
809
|
)
|
|
729
810
|
return queries
|
|
730
811
|
}
|
|
@@ -961,9 +1042,25 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
961
1042
|
return {
|
|
962
1043
|
...client[key],
|
|
963
1044
|
request: h_,
|
|
1045
|
+
streamQuery: useStreamQuery(client[key] as any),
|
|
964
1046
|
mutateStream: streamMutFactory,
|
|
965
1047
|
wrapStream: Command.wrapStream(streamMutFactory),
|
|
966
|
-
fn: Command.fn(client[key].id)
|
|
1048
|
+
fn: Command.fn(client[key].id),
|
|
1049
|
+
streamFn: useCommand().streamFn(client[key].id as any) as any,
|
|
1050
|
+
mutateStream2: (() => {
|
|
1051
|
+
const sm2Act = useStreamMutation2()(client[key] as any, mergedInvalidation)
|
|
1052
|
+
const originalHandler = (client[key] as any).handler
|
|
1053
|
+
const sm2Handler = Stream.isStream(originalHandler)
|
|
1054
|
+
? (_input: any, _ctx: any) => sm2Act
|
|
1055
|
+
: (input: any, _ctx: any) => (sm2Act as (i: any) => any)(input)
|
|
1056
|
+
return Object.assign(sm2Act, {
|
|
1057
|
+
id: client[key].id,
|
|
1058
|
+
wrapStream: (...combinators: any[]) => {
|
|
1059
|
+
const sfn = useCommand().streamFn(client[key].id as any) as any
|
|
1060
|
+
return sfn(sm2Handler, ...combinators)
|
|
1061
|
+
}
|
|
1062
|
+
})
|
|
1063
|
+
})()
|
|
967
1064
|
}
|
|
968
1065
|
})()
|
|
969
1066
|
: {
|
|
@@ -1020,6 +1117,8 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1020
1117
|
& QueryRequestWithExtensions<QueryHandler<typeof client[Key]>>
|
|
1021
1118
|
& Queries<RT, QueryHandler<typeof client[Key]>>
|
|
1022
1119
|
& QueryProjection<RT, QueryHandler<typeof client[Key]>>)
|
|
1120
|
+
& (StreamHandler<typeof client[Key]> extends never ? {}
|
|
1121
|
+
: StreamQueries<RT, StreamHandler<typeof client[Key]>>)
|
|
1023
1122
|
& (CommandHandler<typeof client[Key]> extends never ? {}
|
|
1024
1123
|
: CommandRequestWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>>)
|
|
1025
1124
|
& (CommandHandler<typeof client[Key]> extends never ? {}
|
|
@@ -1029,6 +1128,8 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1029
1128
|
mutateStream: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
1030
1129
|
wrapStream: StreamCommandWithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1031
1130
|
fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1131
|
+
streamFn: StreamFnStreamExtension<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1132
|
+
mutateStream2: StreamMutation2WithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1032
1133
|
})
|
|
1033
1134
|
& { Input: typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I : never }
|
|
1034
1135
|
}
|
|
@@ -1091,6 +1192,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1091
1192
|
fn: (...args: [any]) => useCommand().fn(...args),
|
|
1092
1193
|
wrap: (...args: [any]) => useCommand().wrap(...args),
|
|
1093
1194
|
wrapStream: (...args: [any]) => useCommand().wrapStream(...args),
|
|
1195
|
+
streamFn: (...args: [any]) => useCommand().streamFn(...args),
|
|
1094
1196
|
alt: (...args: [any]) => useCommand().alt(...args),
|
|
1095
1197
|
alt2: (...args: [any]) => useCommand().alt2(...args)
|
|
1096
1198
|
} as ReturnType<typeof useCommand>,
|
|
@@ -1130,6 +1232,8 @@ export interface CommandBase<I = void, A = void> {
|
|
|
1130
1232
|
label: string
|
|
1131
1233
|
/** formatted progress info for current `running` state, when `progress` was supplied */
|
|
1132
1234
|
progress?: Progress | undefined
|
|
1235
|
+
/** reactive result state, available on stream-backed commands */
|
|
1236
|
+
result?: AsyncResult.AsyncResult<any, any>
|
|
1133
1237
|
}
|
|
1134
1238
|
|
|
1135
1239
|
export interface EffectCommand<I = void, A = unknown, E = unknown> extends CommandBase<I, Fiber<A, E>> {}
|
package/src/makeUseCommand.ts
CHANGED
|
@@ -5,7 +5,9 @@ type X<X> = X
|
|
|
5
5
|
|
|
6
6
|
// helps retain JSDoc
|
|
7
7
|
export interface CommanderResolved<RT, RTHooks>
|
|
8
|
-
extends
|
|
8
|
+
extends
|
|
9
|
+
X<typeof CommanderStatic>,
|
|
10
|
+
Pick<CommanderImpl<RT, RTHooks>, "fn" | "wrap" | "wrapStream" | "streamFn" | "alt" | "alt2">
|
|
9
11
|
{
|
|
10
12
|
}
|
|
11
13
|
|
package/src/mutate.ts
CHANGED
|
@@ -414,17 +414,56 @@ export const useMakeMutation = () => {
|
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
/**
|
|
417
|
-
* Like `
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* success or failure.
|
|
417
|
+
* Like `makeStreamMutation`, but returns an `Effect<Stream>` per invocation instead of a
|
|
418
|
+
* `[ref, execute]` tuple. The outer Effect sets up per-invocation invalidation scaffolding
|
|
419
|
+
* and returns a stream that triggers query invalidation via `Stream.ensuring` when it completes.
|
|
421
420
|
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
421
|
+
* Use this with `streamFn` / `Command.streamFn(id)(mutateStream2Handler, ...combinators)` so that
|
|
422
|
+
* the command manages its own reactive state internally. Unlike `makeStreamMutation`, no external
|
|
423
|
+
* reactive result ref is created.
|
|
425
424
|
*
|
|
426
425
|
* Must be called inside a Vue setup context (uses `useQueryClient` internally).
|
|
427
426
|
*/
|
|
427
|
+
export const makeStreamMutation2 = () => {
|
|
428
|
+
const queryClient = useQueryClient()
|
|
429
|
+
|
|
430
|
+
return (
|
|
431
|
+
self: {
|
|
432
|
+
id: string
|
|
433
|
+
options?: ClientForOptions
|
|
434
|
+
handler: Stream.Stream<any, any, any> | ((i: any) => Stream.Stream<any, any, any>)
|
|
435
|
+
},
|
|
436
|
+
mergedInvalidation?: MutationOptionsBase["queryInvalidation"]
|
|
437
|
+
) => {
|
|
438
|
+
const invCache = buildInvalidateCache(queryClient, self, mergedInvalidation)
|
|
439
|
+
|
|
440
|
+
const makeInvocationEffect = (input: unknown, source: Stream.Stream<any, any, any>) =>
|
|
441
|
+
Effect.gen(function*() {
|
|
442
|
+
const keysRef = yield* Ref.make<ReadonlyArray<InvalidationKey>>([])
|
|
443
|
+
const invKeys = makeInvalidationKeysService(keysRef, (key) => invCache(input, Exit.succeed(undefined), [key]))
|
|
444
|
+
const lastRef = yield* Ref.make<any>(undefined)
|
|
445
|
+
return source.pipe(
|
|
446
|
+
Stream.provideService(InvalidationKeysFromServer, invKeys),
|
|
447
|
+
Stream.tap((v) => Ref.set(lastRef, v)),
|
|
448
|
+
Stream.ensuring(
|
|
449
|
+
Effect.gen(function*() {
|
|
450
|
+
const lastValue = yield* Ref.get(lastRef)
|
|
451
|
+
const serverKeys = yield* Ref.get(keysRef)
|
|
452
|
+
yield* invCache(input, Exit.succeed(lastValue), serverKeys)
|
|
453
|
+
})
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
const handler = self.handler
|
|
459
|
+
const act = Stream.isStream(handler)
|
|
460
|
+
? makeInvocationEffect(undefined, handler)
|
|
461
|
+
: (i: any) => makeInvocationEffect(i, (handler as (i: any) => Stream.Stream<any, any, any>)(i))
|
|
462
|
+
|
|
463
|
+
return act
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
428
467
|
export const makeStreamMutation = () => {
|
|
429
468
|
const queryClient = useQueryClient()
|
|
430
469
|
|
package/src/query.ts
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
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
|
-
|
|
6
|
-
import {
|
|
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"
|
|
7
8
|
import { makeQueryKey, type Req } from "effect-app/client"
|
|
8
|
-
import type { RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
|
|
9
|
+
import type { RequestHandler, RequestHandlerWithInput, RequestStreamHandler, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
|
|
9
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"
|
|
@@ -75,6 +80,64 @@ export interface CustomDefinedPlaceholderQueryOptions<
|
|
|
75
80
|
| PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
|
|
76
81
|
}
|
|
77
82
|
|
|
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
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
78
141
|
export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
|
|
79
142
|
const useQuery_: {
|
|
80
143
|
<I, A, E, Request extends Req, Name extends string>(
|
|
@@ -228,27 +291,6 @@ export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
|
|
|
228
291
|
] as any
|
|
229
292
|
}
|
|
230
293
|
|
|
231
|
-
function swrToQuery<E, A>(r: {
|
|
232
|
-
error: CauseException<E> | undefined
|
|
233
|
-
data: A | undefined
|
|
234
|
-
isValidating: boolean
|
|
235
|
-
}): AsyncResult.AsyncResult<A, E> {
|
|
236
|
-
if (r.error !== undefined) {
|
|
237
|
-
return AsyncResult.failureWithPrevious(
|
|
238
|
-
r.error.originalCause,
|
|
239
|
-
{
|
|
240
|
-
previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
|
|
241
|
-
waiting: r.isValidating
|
|
242
|
-
}
|
|
243
|
-
)
|
|
244
|
-
}
|
|
245
|
-
if (r.data !== undefined) {
|
|
246
|
-
return AsyncResult.success<A, E>(r.data, { waiting: r.isValidating })
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return AsyncResult.initial(r.isValidating)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
294
|
const useQuery: {
|
|
253
295
|
/**
|
|
254
296
|
* Effect results are passed to the caller, including errors.
|
|
@@ -351,6 +393,88 @@ export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
|
|
|
351
393
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
352
394
|
export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
|
|
353
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>>, never, never>,
|
|
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
|
+
|
|
354
478
|
function orPrevious<E, A>(result: AsyncResult.AsyncResult<A, E>) {
|
|
355
479
|
return AsyncResult.isFailure(result) && Option.isSome(result.previousSuccess)
|
|
356
480
|
? AsyncResult.success(result.previousSuccess.value, { waiting: result.waiting })
|