@effect-app/vue 4.0.0-beta.181 → 4.0.0-beta.182

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.
@@ -74,11 +74,15 @@ export const useExportMutation = () => {
74
74
  // When a request schema has `type: "stream"`, `clientFor` exposes:
75
75
  //
76
76
  // client.exportData.mutateStream
77
- // // -> () => [ComputedRef<AsyncResult<ExportEvent, E>>, (input: I) => Effect<void, never, R>] & { id }
78
- // // Always invoke `()` to get a fresh ref+execute pair (per-call state).
79
- // const [ref, execute] = client.exportData.mutateStream()
77
+ // // -> (options?) => ((input: I) => Effect<Final, never, R>) & { id, running?, progress? }
78
+ // // Always invoke `()` (optionally with `{ progress }`) to get a fresh callable.
79
+ // const execute = client.exportData.mutateStream()
80
+ // const executeWithProgress = client.exportData.mutateStream({
81
+ // progress: (r) => r._tag === "Success" ? `${r.value.completed}/${r.value.total}` : undefined
82
+ // })
83
+ // // The callable can also be passed directly to Command.fn / Command.wrap / Command.wrapStream.
80
84
  //
81
- // which is equivalent to calling `asStreamResult(client.exportData.handler)`.
85
+ // which wraps `asStreamResult(client.exportData.handler)` internally.
82
86
  //
83
87
  // The `.helpers` object also includes `exportDataStream` (the camelCase key
84
88
  // plus "Stream" suffix) with the same factory shape.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/vue",
3
- "version": "4.0.0-beta.181",
3
+ "version": "4.0.0-beta.182",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/effect-ts-app/libs/tree/main/packages/vue",
@@ -11,7 +11,7 @@
11
11
  "@vueuse/core": "^14.2.1",
12
12
  "change-case": "^5.4.4",
13
13
  "query-string": "^9.3.1",
14
- "effect-app": "4.0.0-beta.181"
14
+ "effect-app": "4.0.0-beta.182"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "@effect/atom-vue": "^4.0.0-beta.59",
package/src/commander.ts CHANGED
@@ -31,20 +31,31 @@ export type StreamMutationCallOptions<A, E> = {
31
31
  progress?: (result: AsyncResult.AsyncResult<A, E>) => Progress | undefined
32
32
  }
33
33
 
34
- type StreamMutationTuple<Id extends string, Arg, A, E, R> =
35
- & readonly [
36
- ComputedRef<AsyncResult.AsyncResult<A, E>>,
37
- ((arg: Arg) => Effect.Effect<any, never, R>) | Effect.Effect<any, never, R>
38
- ]
34
+ /**
35
+ * The result of invoking a `mutateStream` factory: the `execute` function (or
36
+ * `Effect`, when the request takes no input) carries `id`, plus `running` and
37
+ * `progress` when the factory was called with a `progress` formatter. Pass
38
+ * directly to `Command.fn` / `Command.wrap` / `Command.wrapStream`, or invoke
39
+ * to run the stream.
40
+ */
41
+ type StreamMutationCallable<Id extends string, Arg, A, E, R> =
42
+ & (((arg: Arg) => Effect.Effect<any, E, R>) | Effect.Effect<any, E, R>)
39
43
  & {
40
44
  readonly id: Id
45
+ readonly _streamCallable: true
41
46
  readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
42
47
  readonly progress?: ComputedRef<Progress | undefined>
43
48
  }
44
49
 
45
50
  type StreamMutationFactory<Id extends string, Arg, A, E, R> =
46
- & ((options?: StreamMutationCallOptions<A, E>) => StreamMutationTuple<Id, Arg, A, E, R>)
47
- & { readonly id: Id }
51
+ & ((options?: StreamMutationCallOptions<A, E>) => StreamMutationCallable<Id, Arg, A, E, R>)
52
+ & { readonly id: Id; readonly _streamFactory: true }
53
+
54
+ const isStreamFactory = (x: unknown): x is StreamMutationFactory<string, any, any, any, any> =>
55
+ typeof x === "function" && (x as any)._streamFactory === true
56
+
57
+ const isStreamCallable = (x: unknown): x is StreamMutationCallable<string, any, any, any, any> =>
58
+ x !== null && x !== undefined && (x as any)._streamCallable === true
48
59
  type FnOptions<
49
60
  Id extends string,
50
61
  I18nCustomKey extends string,
@@ -2395,7 +2406,7 @@ export class CommanderImpl<RT, RTHooks> {
2395
2406
  id:
2396
2407
  | Id
2397
2408
  | { id: Id }
2398
- | StreamMutationTuple<Id, any, RunningA, RunningE, any>
2409
+ | StreamMutationCallable<Id, any, RunningA, RunningE, any>
2399
2410
  | StreamMutationFactory<Id, any, RunningA, RunningE, any>,
2400
2411
  options?: FnOptions<Id, I18nKey, State>
2401
2412
  ): Commander.Gen<RT | RTHooks, Id, I18nKey, State> & Commander.NonGen<RT | RTHooks, Id, I18nKey, State> & {
@@ -2403,8 +2414,8 @@ export class CommanderImpl<RT, RTHooks> {
2403
2414
  } => {
2404
2415
  // Resolve id and (optionally) per-build stream metadata.
2405
2416
  const resolvedId: Id = typeof id === "string" ? id : (id as { id: Id }).id
2406
- const isStreamFactory = typeof id === "function" && "id" in id && (id as any).length <= 1
2407
- const isStreamTuple = Array.isArray(id) && "id" in id
2417
+ const factory = isStreamFactory(id)
2418
+ const callable = !factory && isStreamCallable(id)
2408
2419
  const resolveStreamMeta = ():
2409
2420
  | {
2410
2421
  running?: ComputedRef<AsyncResult.AsyncResult<RunningA, RunningE>> | undefined
@@ -2412,13 +2423,13 @@ export class CommanderImpl<RT, RTHooks> {
2412
2423
  }
2413
2424
  | undefined =>
2414
2425
  {
2415
- if (isStreamTuple) {
2416
- const t = id as StreamMutationTuple<Id, any, RunningA, RunningE, any>
2417
- return { running: t.running, progress: t.progress }
2426
+ if (factory) {
2427
+ const c = id()
2428
+ return { running: c.running, progress: c.progress }
2418
2429
  }
2419
- if (isStreamFactory) {
2420
- const t = id()
2421
- return { running: t.running, progress: t.progress }
2430
+ if (callable) {
2431
+ const c = id as StreamMutationCallable<Id, any, RunningA, RunningE, any>
2432
+ return { running: c.running, progress: c.progress }
2422
2433
  }
2423
2434
  return undefined
2424
2435
  }
@@ -2563,18 +2574,15 @@ export class CommanderImpl<RT, RTHooks> {
2563
2574
  id: Id
2564
2575
  mutateStream:
2565
2576
  | StreamMutationFactory<Id, Arg, A, E, R>
2566
- | StreamMutationTuple<Id, Arg, A, E, R>
2577
+ | StreamMutationCallable<Id, Arg, A, E, R>
2567
2578
  }
2568
- | StreamMutationTuple<Id, Arg, A, E, R>,
2579
+ | StreamMutationCallable<Id, Arg, A, E, R>,
2569
2580
  options?: FnOptions<Id, I18nKey, State>
2570
2581
  ): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
2571
2582
  if (mutation !== null && typeof mutation === "object" && "mutateStream" in mutation) {
2572
2583
  return this.wrapStream(mutation as any, options) as any
2573
2584
  }
2574
- if (Array.isArray(mutation) && "id" in mutation) {
2575
- return this.wrapStream(mutation as any, options) as any
2576
- }
2577
- if (typeof mutation === "function" && "id" in mutation && (mutation as any).length <= 1) {
2585
+ if (isStreamCallable(mutation) || isStreamFactory(mutation)) {
2578
2586
  return this.wrapStream(mutation as any, options) as any
2579
2587
  }
2580
2588
  // At this point mutation is either { mutate, id } or (fn & { id })
@@ -2655,19 +2663,22 @@ export class CommanderImpl<RT, RTHooks> {
2655
2663
  id: Id
2656
2664
  mutateStream:
2657
2665
  | StreamMutationFactory<Id, Arg, A, E, R>
2658
- | StreamMutationTuple<Id, Arg, A, E, R>
2666
+ | StreamMutationCallable<Id, Arg, A, E, R>
2659
2667
  }
2660
2668
  | StreamMutationFactory<Id, Arg, A, E, R>
2661
- | StreamMutationTuple<Id, Arg, A, E, R>,
2669
+ | StreamMutationCallable<Id, Arg, A, E, R>,
2662
2670
  options?: FnOptions<Id, I18nKey, State>
2663
2671
  ): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
2664
2672
  const id = mutation.id
2665
- // Resolve `source` to the factory or already-called tuple.
2666
- const source: StreamMutationFactory<Id, Arg, A, E, R> | StreamMutationTuple<Id, Arg, A, E, R> =
2673
+ // Resolve `source` to the factory or already-invoked callable.
2674
+ const source: StreamMutationFactory<Id, Arg, A, E, R> | StreamMutationCallable<Id, Arg, A, E, R> =
2667
2675
  mutation !== null && typeof mutation === "object" && "mutateStream" in mutation
2668
2676
  ? (mutation.mutateStream as any)
2669
2677
  : (mutation as any)
2670
- const resolveTuple = (): StreamMutationTuple<Id, Arg, A, E, R> => (typeof source === "function" ? source() : source)
2678
+ const resolveCallable = (): StreamMutationCallable<Id, Arg, A, E, R> =>
2679
+ (isStreamFactory(source)
2680
+ ? (source as StreamMutationFactory<Id, Arg, A, E, R>)()
2681
+ : source) as StreamMutationCallable<Id, Arg, A, E, R>
2671
2682
  return Object.assign(
2672
2683
  (...combinators: any[]): any => {
2673
2684
  // we capture the definition stack here, so we can append it to later stack traces
@@ -2676,15 +2687,14 @@ export class CommanderImpl<RT, RTHooks> {
2676
2687
  const errorDef = new Error()
2677
2688
  Error.stackTraceLimit = limit
2678
2689
 
2679
- // Fresh per build: call the factory once per command instance so each
2680
- // wrap call gets its own ComputedRef + execute pair. `running`/`progress`
2690
+ // Fresh per build: invoke the factory once per command instance so each
2691
+ // wrap call gets its own state + execute pair. `running`/`progress`
2681
2692
  // are only surfaced when the factory was called with a `progress` formatter.
2682
- const tuple = resolveTuple()
2683
- const [, executeRaw] = tuple
2684
- const mutate: (_arg: Arg) => Effect.Effect<any, never, R> = Effect.isEffect(executeRaw)
2685
- ? (_arg: Arg) => executeRaw
2686
- : executeRaw
2687
- const streamMeta = { running: tuple.running, progress: tuple.progress }
2693
+ const callable = resolveCallable()
2694
+ const mutate: (_arg: Arg) => Effect.Effect<any, E, R> = Effect.isEffect(callable)
2695
+ ? (_arg: Arg) => callable
2696
+ : callable as (arg: Arg) => Effect.Effect<any, E, R>
2697
+ const streamMeta = { running: callable.running, progress: callable.progress }
2688
2698
 
2689
2699
  return this.makeCommand(id, options, errorDef, streamMeta)(
2690
2700
  Effect.fnUntraced(
package/src/makeClient.ts CHANGED
@@ -237,33 +237,36 @@ export type MutateStreamCallOptions<A, E> = {
237
237
 
238
238
  /**
239
239
  * The `mutateStream` factory for a stream-type request handler. Always invoke
240
- * (optionally with `{ progress }`) to get a fresh `[resultRef, execute]` tuple
241
- * each call produces a new `ComputedRef` + execute pair so independent invocations
242
- * don't share state. `execute` updates the ref live with each emitted value.
243
- * When the request declares a `final` schema, `execute` resolves with the last
244
- * emitted value typed as `Final`; otherwise it resolves with the success type.
245
- * The factory itself carries the request `id` so it can be passed to
246
- * `Command.fn` / `Command.wrapStream` directly.
240
+ * (optionally with `{ progress }`) to get a fresh callable `execute` each call
241
+ * produces a new state + execute pair so independent invocations don't share
242
+ * state. The callable updates its underlying ref live with each emitted value
243
+ * and carries `id`, plus `running` and `progress` when the factory was called
244
+ * with a `progress` formatter. When the request declares a `final` schema,
245
+ * the callable resolves with the last emitted value typed as `Final`; otherwise
246
+ * it resolves with the success type. The factory itself carries the request
247
+ * `id` so it can be passed to `Command.fn` / `Command.wrapStream` directly.
247
248
  */
248
249
  export type StreamMutationWithExtensions<Req> = Req extends
249
250
  RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
250
251
  & ((options?: MutateStreamCallOptions<A, E>) =>
251
- & readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, (input: I) => Effect.Effect<Final, never, R>]
252
+ & ((input: I) => Effect.Effect<Final, E, R>)
252
253
  & {
253
254
  readonly id: Id
255
+ readonly _streamCallable: true
254
256
  readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
255
257
  readonly progress?: ComputedRef<Progress | undefined>
256
258
  })
257
- & { readonly id: Id }
259
+ & { readonly id: Id; readonly _streamFactory: true }
258
260
  : Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
259
261
  & ((options?: MutateStreamCallOptions<A, E>) =>
260
- & readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, Effect.Effect<Final, never, R>]
262
+ & Effect.Effect<Final, E, R>
261
263
  & {
262
264
  readonly id: Id
265
+ readonly _streamCallable: true
263
266
  readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
264
267
  readonly progress?: ComputedRef<Progress | undefined>
265
268
  })
266
- & { readonly id: Id }
269
+ & { readonly id: Id; readonly _streamFactory: true }
267
270
  : never
268
271
 
269
272
  /**
@@ -841,20 +844,21 @@ export const makeClient = <RT_, RTHooks>(
841
844
  const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
842
845
  const smFactory = Object.assign(
843
846
  (opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
844
- const tuple = streamMutation(client[key] as any, mergedInvalidation)
847
+ const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
845
848
  const extras: {
846
849
  id: string
850
+ _streamCallable: true
847
851
  running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
848
852
  progress?: ComputedRef<Progress | undefined>
849
- } = { id: client[key].id }
853
+ } = { id: client[key].id, _streamCallable: true }
850
854
  if (opts?.progress) {
851
855
  const fmt = opts.progress
852
- extras.running = tuple[0]
853
- extras.progress = computed(() => fmt(tuple[0].value))
856
+ extras.running = resultRef
857
+ extras.progress = computed(() => fmt(resultRef.value))
854
858
  }
855
- return Object.assign(tuple, extras)
859
+ return Object.assign(execute, extras)
856
860
  },
857
- { id: client[key].id }
861
+ { id: client[key].id, _streamFactory: true as const }
858
862
  )
859
863
  ;(acc as any)[camelCase(key) + "Stream"] = Object.assign(smFactory, {
860
864
  fn: Command.fn(client[key].id)
@@ -938,20 +942,21 @@ export const makeClient = <RT_, RTHooks>(
938
942
  const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
939
943
  const streamMutFactory = Object.assign(
940
944
  (opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
941
- const tuple = streamMutation(client[key] as any, mergedInvalidation)
945
+ const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
942
946
  const extras: {
943
947
  id: string
948
+ _streamCallable: true
944
949
  running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
945
950
  progress?: ComputedRef<Progress | undefined>
946
- } = { id: client[key].id }
951
+ } = { id: client[key].id, _streamCallable: true }
947
952
  if (opts?.progress) {
948
953
  const fmt = opts.progress
949
- extras.running = tuple[0]
950
- extras.progress = computed(() => fmt(tuple[0].value))
954
+ extras.running = resultRef
955
+ extras.progress = computed(() => fmt(resultRef.value))
951
956
  }
952
- return Object.assign(tuple, extras)
957
+ return Object.assign(execute, extras)
953
958
  },
954
- { id: client[key].id }
959
+ { id: client[key].id, _streamFactory: true as const }
955
960
  )
956
961
  return {
957
962
  ...client[key],
package/src/mutate.ts CHANGED
@@ -421,6 +421,7 @@ export const useMakeMutation = () => {
421
421
  *
422
422
  * When the request declares a `final` schema, `execute` resolves with the last emitted value
423
423
  * typed as `Final`; otherwise it resolves with the last emitted value typed as the success type.
424
+ * Stream failures bubble through the execute effect's typed error channel `E`.
424
425
  *
425
426
  * Must be called inside a Vue setup context (uses `useQueryClient` internally).
426
427
  */
@@ -437,7 +438,7 @@ export const makeStreamMutation = () => {
437
438
  ) => {
438
439
  const state = shallowRef<AsyncResult.AsyncResult<any, any>>(AsyncResult.initial())
439
440
 
440
- const runStream = (stream: Stream.Stream<any, any, any>, input?: unknown): Effect.Effect<any, never, any> => {
441
+ const runStream = (stream: Stream.Stream<any, any, any>, input?: unknown): Effect.Effect<any, any, any> => {
441
442
  const invCache = buildInvalidateCache(queryClient, self, mergedInvalidation)
442
443
  const keysRef = Ref.makeUnsafe<ReadonlyArray<InvalidationKey>>([])
443
444
  // V3: pass onAdded so each mid-stream metadata chunk triggers query
@@ -476,11 +477,14 @@ export const makeStreamMutation = () => {
476
477
  const lastValue = AsyncResult.isSuccess(current) ? current.value : undefined
477
478
  const invExit = exit._tag === "Success" ? Exit.succeed(lastValue) : exit
478
479
  const serverKeys = Ref.getUnsafe(keysRef)
479
- // Note: when the stream fails, `lastValue` is undefined. The failure is
480
- // communicated via the reactive `state` ref (AsyncResult.failure). The
481
- // execute effect always resolves successfully; callers should inspect the
482
- // ref to distinguish success from failure.
483
- return invCache(input, invExit, serverKeys).pipe(Effect.as(lastValue))
480
+ // Stream failures bubble through the execute effect's typed error
481
+ // channel. The reactive `state` ref still mirrors the failure as
482
+ // `AsyncResult.failure` for live progress UI.
483
+ return invCache(input, invExit, serverKeys).pipe(
484
+ Effect.flatMap(() =>
485
+ exit._tag === "Success" ? Effect.succeed(lastValue) : Effect.failCause(exit.cause)
486
+ )
487
+ )
484
488
  })
485
489
  )
486
490
  )
@@ -278,8 +278,8 @@ it.skip("stream final type tests", () => {
278
278
  const { clientFor } = useClient()
279
279
  const client = clientFor(Something, undefined, somethingInvalidationResources)
280
280
 
281
- const [_refNoFinal, execNoFinal] = client.StreamWithoutFinal.mutateStream()
282
- const [_refWithFinal, execWithFinal] = client.StreamWithFinal.mutateStream()
281
+ const execNoFinal = client.StreamWithoutFinal.mutateStream()
282
+ const execWithFinal = client.StreamWithFinal.mutateStream()
283
283
 
284
284
  // Without `final`: execute input is {id: string} and resolves with void
285
285
  const _execNoFinalResult: ReturnType<typeof execNoFinal> = execNoFinal({ id: "test" })
@@ -47,7 +47,7 @@ it.skip("mutateStream without final: execute resolves void (type-level)", () =>
47
47
  const { clientFor } = useClient()
48
48
  const client = clientFor(Something, undefined, somethingInvalidationResources)
49
49
 
50
- const [_ref, execute] = client.StreamWithoutFinal.mutateStream()
50
+ const execute = client.StreamWithoutFinal.mutateStream()
51
51
 
52
52
  // execute returns void — assigning to ExportComplete Effect should fail
53
53
  const result = execute({ id: "test" })
@@ -64,7 +64,7 @@ it.skip("mutateStream with final: execute resolves with ExportComplete (type-lev
64
64
  const { clientFor } = useClient()
65
65
  const client = clientFor(Something, undefined, somethingInvalidationResources)
66
66
 
67
- const [_ref, execute] = client.StreamWithFinal.mutateStream()
67
+ const execute = client.StreamWithFinal.mutateStream()
68
68
 
69
69
  // execute returns ExportComplete — assignment should compile cleanly
70
70
  const result = execute({ id: "test" })