@effect-app/vue 4.0.0-beta.181 → 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 +51 -0
- package/dist/commander.d.ts +205 -12
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +336 -24
- package/dist/makeClient.d.ts +66 -14
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +58 -25
- 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 +13 -8
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +32 -12
- package/dist/query.d.ts +14 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +105 -15
- package/examples/streamMutation.ts +8 -4
- package/package.json +2 -2
- package/src/commander.ts +578 -37
- package/src/makeClient.ts +143 -34
- package/src/makeUseCommand.ts +3 -1
- package/src/mutate.ts +55 -12
- package/src/query.ts +148 -24
- package/test/dist/stubs.d.ts +184 -38
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/makeClient.test.ts +2 -2
- package/test/streamFinal.test.ts +2 -2
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>
|
|
@@ -237,33 +240,36 @@ export type MutateStreamCallOptions<A, E> = {
|
|
|
237
240
|
|
|
238
241
|
/**
|
|
239
242
|
* The `mutateStream` factory for a stream-type request handler. Always invoke
|
|
240
|
-
* (optionally with `{ progress }`) to get a fresh `
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
243
|
+
* (optionally with `{ progress }`) to get a fresh callable `execute` — each call
|
|
244
|
+
* produces a new state + execute pair so independent invocations don't share
|
|
245
|
+
* state. The callable updates its underlying ref live with each emitted value
|
|
246
|
+
* and carries `id`, plus `running` and `progress` when the factory was called
|
|
247
|
+
* with a `progress` formatter. When the request declares a `final` schema,
|
|
248
|
+
* the callable resolves with the last emitted value typed as `Final`; otherwise
|
|
249
|
+
* it resolves with the success type. The factory itself carries the request
|
|
250
|
+
* `id` so it can be passed to `Command.fn` / `Command.wrapStream` directly.
|
|
247
251
|
*/
|
|
248
252
|
export type StreamMutationWithExtensions<Req> = Req extends
|
|
249
253
|
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
250
254
|
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
251
|
-
&
|
|
255
|
+
& ((input: I) => Effect.Effect<Final, E, R>)
|
|
252
256
|
& {
|
|
253
257
|
readonly id: Id
|
|
258
|
+
readonly _streamCallable: true
|
|
254
259
|
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
255
260
|
readonly progress?: ComputedRef<Progress | undefined>
|
|
256
261
|
})
|
|
257
|
-
& { readonly id: Id }
|
|
262
|
+
& { readonly id: Id; readonly _streamFactory: true }
|
|
258
263
|
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
259
264
|
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
260
|
-
&
|
|
265
|
+
& Effect.Effect<Final, E, R>
|
|
261
266
|
& {
|
|
262
267
|
readonly id: Id
|
|
268
|
+
readonly _streamCallable: true
|
|
263
269
|
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
264
270
|
readonly progress?: ComputedRef<Progress | undefined>
|
|
265
271
|
})
|
|
266
|
-
& { readonly id: Id }
|
|
272
|
+
& { readonly id: Id; readonly _streamFactory: true }
|
|
267
273
|
: never
|
|
268
274
|
|
|
269
275
|
/**
|
|
@@ -289,11 +295,42 @@ export type StreamFnExtension<RT, Req> = Req extends
|
|
|
289
295
|
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
290
296
|
: never
|
|
291
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
|
+
|
|
292
327
|
// we don't really care about the RT, as we are in charge of ensuring runtime safety anyway
|
|
293
328
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
294
329
|
declare const useQuery_: QueryImpl<any>["useQuery"]
|
|
295
330
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
296
331
|
declare const useSuspenseQuery_: QueryImpl<any>["useSuspenseQuery"]
|
|
332
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
333
|
+
declare const useStreamQuery_: QueryImpl<any>["useStreamQuery"]
|
|
297
334
|
|
|
298
335
|
export interface ProjectResult<RT, I, B, E, R, Request extends Req, Id extends string> {
|
|
299
336
|
request: (i: I) => Effect.Effect<B, E, R>
|
|
@@ -384,6 +421,32 @@ export type Queries<RT, Req> = Req extends
|
|
|
384
421
|
: never
|
|
385
422
|
: never
|
|
386
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
|
+
|
|
387
450
|
const _useMutation = makeMutation()
|
|
388
451
|
|
|
389
452
|
const wrapWithSpan = (self: { id: string; handler: any }, mut: any) => {
|
|
@@ -442,6 +505,7 @@ export type ClientFrom<M extends RequestsAny> = RequestHandlers<never, never, M,
|
|
|
442
505
|
export class QueryImpl<R> {
|
|
443
506
|
constructor(readonly getRuntime: () => Context.Context<R>) {
|
|
444
507
|
this.useQuery = makeQuery(this.getRuntime)
|
|
508
|
+
this.useStreamQuery = makeStreamQuery(this.getRuntime)
|
|
445
509
|
}
|
|
446
510
|
/**
|
|
447
511
|
* Effect results are passed to the caller, including errors.
|
|
@@ -449,6 +513,12 @@ export class QueryImpl<R> {
|
|
|
449
513
|
*/
|
|
450
514
|
readonly useQuery: ReturnType<typeof makeQuery<R>>
|
|
451
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
|
+
|
|
452
522
|
/**
|
|
453
523
|
* The difference with useQuery is that this function will return a Promise you can await in the Setup,
|
|
454
524
|
* which ensures that either there always is a latest value, or an error occurs on load.
|
|
@@ -646,9 +716,13 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
646
716
|
let sm: ReturnType<typeof makeStreamMutation>
|
|
647
717
|
const useStreamMutation = () => sm ??= makeStreamMutation()
|
|
648
718
|
|
|
719
|
+
let sm2: ReturnType<typeof makeStreamMutation2>
|
|
720
|
+
const useStreamMutation2 = () => sm2 ??= makeStreamMutation2()
|
|
721
|
+
|
|
649
722
|
const query = new QueryImpl(getBaseRt)
|
|
650
723
|
const useQuery = query.useQuery
|
|
651
724
|
const useSuspenseQuery = query.useSuspenseQuery
|
|
725
|
+
const useStreamQuery = query.useStreamQuery
|
|
652
726
|
|
|
653
727
|
const mergeInvalidation = (
|
|
654
728
|
a?: MutationOptionsBase["queryInvalidation"],
|
|
@@ -692,15 +766,19 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
692
766
|
) => {
|
|
693
767
|
const queries = Struct.keys(client).reduce(
|
|
694
768
|
(acc, key) => {
|
|
695
|
-
|
|
696
|
-
|
|
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
|
+
})
|
|
697
781
|
}
|
|
698
|
-
;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(client[key] as any), {
|
|
699
|
-
id: client[key].id
|
|
700
|
-
})
|
|
701
|
-
;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(client[key] as any), {
|
|
702
|
-
id: client[key].id
|
|
703
|
-
})
|
|
704
782
|
return acc
|
|
705
783
|
},
|
|
706
784
|
{} as
|
|
@@ -722,6 +800,12 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
722
800
|
QueryHandler<typeof client[Key]>
|
|
723
801
|
>["suspense"]
|
|
724
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
|
+
}
|
|
725
809
|
)
|
|
726
810
|
return queries
|
|
727
811
|
}
|
|
@@ -841,20 +925,21 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
841
925
|
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
842
926
|
const smFactory = Object.assign(
|
|
843
927
|
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
844
|
-
const
|
|
928
|
+
const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
|
|
845
929
|
const extras: {
|
|
846
930
|
id: string
|
|
931
|
+
_streamCallable: true
|
|
847
932
|
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
848
933
|
progress?: ComputedRef<Progress | undefined>
|
|
849
|
-
} = { id: client[key].id }
|
|
934
|
+
} = { id: client[key].id, _streamCallable: true }
|
|
850
935
|
if (opts?.progress) {
|
|
851
936
|
const fmt = opts.progress
|
|
852
|
-
extras.running =
|
|
853
|
-
extras.progress = computed(() => fmt(
|
|
937
|
+
extras.running = resultRef
|
|
938
|
+
extras.progress = computed(() => fmt(resultRef.value))
|
|
854
939
|
}
|
|
855
|
-
return Object.assign(
|
|
940
|
+
return Object.assign(execute, extras)
|
|
856
941
|
},
|
|
857
|
-
{ id: client[key].id }
|
|
942
|
+
{ id: client[key].id, _streamFactory: true as const }
|
|
858
943
|
)
|
|
859
944
|
;(acc as any)[camelCase(key) + "Stream"] = Object.assign(smFactory, {
|
|
860
945
|
fn: Command.fn(client[key].id)
|
|
@@ -938,27 +1023,44 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
938
1023
|
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
939
1024
|
const streamMutFactory = Object.assign(
|
|
940
1025
|
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
941
|
-
const
|
|
1026
|
+
const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
|
|
942
1027
|
const extras: {
|
|
943
1028
|
id: string
|
|
1029
|
+
_streamCallable: true
|
|
944
1030
|
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
945
1031
|
progress?: ComputedRef<Progress | undefined>
|
|
946
|
-
} = { id: client[key].id }
|
|
1032
|
+
} = { id: client[key].id, _streamCallable: true }
|
|
947
1033
|
if (opts?.progress) {
|
|
948
1034
|
const fmt = opts.progress
|
|
949
|
-
extras.running =
|
|
950
|
-
extras.progress = computed(() => fmt(
|
|
1035
|
+
extras.running = resultRef
|
|
1036
|
+
extras.progress = computed(() => fmt(resultRef.value))
|
|
951
1037
|
}
|
|
952
|
-
return Object.assign(
|
|
1038
|
+
return Object.assign(execute, extras)
|
|
953
1039
|
},
|
|
954
|
-
{ id: client[key].id }
|
|
1040
|
+
{ id: client[key].id, _streamFactory: true as const }
|
|
955
1041
|
)
|
|
956
1042
|
return {
|
|
957
1043
|
...client[key],
|
|
958
1044
|
request: h_,
|
|
1045
|
+
streamQuery: useStreamQuery(client[key] as any),
|
|
959
1046
|
mutateStream: streamMutFactory,
|
|
960
1047
|
wrapStream: Command.wrapStream(streamMutFactory),
|
|
961
|
-
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
|
+
})()
|
|
962
1064
|
}
|
|
963
1065
|
})()
|
|
964
1066
|
: {
|
|
@@ -1015,6 +1117,8 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1015
1117
|
& QueryRequestWithExtensions<QueryHandler<typeof client[Key]>>
|
|
1016
1118
|
& Queries<RT, QueryHandler<typeof client[Key]>>
|
|
1017
1119
|
& QueryProjection<RT, QueryHandler<typeof client[Key]>>)
|
|
1120
|
+
& (StreamHandler<typeof client[Key]> extends never ? {}
|
|
1121
|
+
: StreamQueries<RT, StreamHandler<typeof client[Key]>>)
|
|
1018
1122
|
& (CommandHandler<typeof client[Key]> extends never ? {}
|
|
1019
1123
|
: CommandRequestWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>>)
|
|
1020
1124
|
& (CommandHandler<typeof client[Key]> extends never ? {}
|
|
@@ -1024,6 +1128,8 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1024
1128
|
mutateStream: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
1025
1129
|
wrapStream: StreamCommandWithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1026
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]>>
|
|
1027
1133
|
})
|
|
1028
1134
|
& { Input: typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I : never }
|
|
1029
1135
|
}
|
|
@@ -1086,6 +1192,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1086
1192
|
fn: (...args: [any]) => useCommand().fn(...args),
|
|
1087
1193
|
wrap: (...args: [any]) => useCommand().wrap(...args),
|
|
1088
1194
|
wrapStream: (...args: [any]) => useCommand().wrapStream(...args),
|
|
1195
|
+
streamFn: (...args: [any]) => useCommand().streamFn(...args),
|
|
1089
1196
|
alt: (...args: [any]) => useCommand().alt(...args),
|
|
1090
1197
|
alt2: (...args: [any]) => useCommand().alt2(...args)
|
|
1091
1198
|
} as ReturnType<typeof useCommand>,
|
|
@@ -1125,6 +1232,8 @@ export interface CommandBase<I = void, A = void> {
|
|
|
1125
1232
|
label: string
|
|
1126
1233
|
/** formatted progress info for current `running` state, when `progress` was supplied */
|
|
1127
1234
|
progress?: Progress | undefined
|
|
1235
|
+
/** reactive result state, available on stream-backed commands */
|
|
1236
|
+
result?: AsyncResult.AsyncResult<any, any>
|
|
1128
1237
|
}
|
|
1129
1238
|
|
|
1130
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,16 +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
|
-
*
|
|
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.
|
|
424
424
|
*
|
|
425
425
|
* Must be called inside a Vue setup context (uses `useQueryClient` internally).
|
|
426
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
|
+
|
|
427
467
|
export const makeStreamMutation = () => {
|
|
428
468
|
const queryClient = useQueryClient()
|
|
429
469
|
|
|
@@ -437,7 +477,7 @@ export const makeStreamMutation = () => {
|
|
|
437
477
|
) => {
|
|
438
478
|
const state = shallowRef<AsyncResult.AsyncResult<any, any>>(AsyncResult.initial())
|
|
439
479
|
|
|
440
|
-
const runStream = (stream: Stream.Stream<any, any, any>, input?: unknown): Effect.Effect<any,
|
|
480
|
+
const runStream = (stream: Stream.Stream<any, any, any>, input?: unknown): Effect.Effect<any, any, any> => {
|
|
441
481
|
const invCache = buildInvalidateCache(queryClient, self, mergedInvalidation)
|
|
442
482
|
const keysRef = Ref.makeUnsafe<ReadonlyArray<InvalidationKey>>([])
|
|
443
483
|
// V3: pass onAdded so each mid-stream metadata chunk triggers query
|
|
@@ -476,11 +516,14 @@ export const makeStreamMutation = () => {
|
|
|
476
516
|
const lastValue = AsyncResult.isSuccess(current) ? current.value : undefined
|
|
477
517
|
const invExit = exit._tag === "Success" ? Exit.succeed(lastValue) : exit
|
|
478
518
|
const serverKeys = Ref.getUnsafe(keysRef)
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
-
|
|
519
|
+
// Stream failures bubble through the execute effect's typed error
|
|
520
|
+
// channel. The reactive `state` ref still mirrors the failure as
|
|
521
|
+
// `AsyncResult.failure` for live progress UI.
|
|
522
|
+
return invCache(input, invExit, serverKeys).pipe(
|
|
523
|
+
Effect.flatMap(() =>
|
|
524
|
+
exit._tag === "Success" ? Effect.succeed(lastValue) : Effect.failCause(exit.cause)
|
|
525
|
+
)
|
|
526
|
+
)
|
|
484
527
|
})
|
|
485
528
|
)
|
|
486
529
|
)
|
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 })
|