@effect-app/vue 4.0.0-beta.27 → 4.0.0-beta.272

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 (111) hide show
  1. package/CHANGELOG.md +2008 -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/{experimental/confirm.d.ts → confirm.d.ts} +6 -4
  6. package/dist/confirm.d.ts.map +1 -0
  7. package/dist/confirm.js +26 -0
  8. package/dist/errorReporter.d.ts +6 -4
  9. package/dist/errorReporter.d.ts.map +1 -1
  10. package/dist/errorReporter.js +14 -19
  11. package/dist/form.d.ts +14 -5
  12. package/dist/form.d.ts.map +1 -1
  13. package/dist/form.js +46 -13
  14. package/dist/intl.d.ts +15 -0
  15. package/dist/intl.d.ts.map +1 -0
  16. package/dist/intl.js +9 -0
  17. package/dist/lib.d.ts +7 -9
  18. package/dist/lib.d.ts.map +1 -1
  19. package/dist/lib.js +35 -10
  20. package/dist/makeClient.d.ts +157 -343
  21. package/dist/makeClient.d.ts.map +1 -1
  22. package/dist/makeClient.js +218 -377
  23. package/dist/makeContext.d.ts.map +1 -1
  24. package/dist/makeIntl.d.ts.map +1 -1
  25. package/dist/{experimental/makeUseCommand.d.ts → makeUseCommand.d.ts} +4 -3
  26. package/dist/makeUseCommand.d.ts.map +1 -0
  27. package/dist/makeUseCommand.js +13 -0
  28. package/dist/mutate.d.ts +96 -38
  29. package/dist/mutate.d.ts.map +1 -1
  30. package/dist/mutate.js +177 -49
  31. package/dist/query.d.ts +23 -38
  32. package/dist/query.d.ts.map +1 -1
  33. package/dist/query.js +156 -78
  34. package/dist/routeParams.d.ts +4 -4
  35. package/dist/routeParams.d.ts.map +1 -1
  36. package/dist/routeParams.js +4 -3
  37. package/dist/runtime.d.ts +1 -14
  38. package/dist/runtime.d.ts.map +1 -1
  39. package/dist/runtime.js +2 -26
  40. package/dist/suspense.d.ts +5 -0
  41. package/dist/suspense.d.ts.map +1 -0
  42. package/dist/suspense.js +12 -0
  43. package/dist/toast.d.ts +2 -0
  44. package/dist/toast.d.ts.map +1 -0
  45. package/dist/toast.js +2 -0
  46. package/dist/withToast.d.ts +2 -0
  47. package/dist/withToast.d.ts.map +1 -0
  48. package/dist/withToast.js +2 -0
  49. package/examples/streamMutation.ts +72 -0
  50. package/package.json +30 -91
  51. package/src/commander.ts +3413 -0
  52. package/src/{experimental/confirm.ts → confirm.ts} +13 -15
  53. package/src/errorReporter.ts +65 -75
  54. package/src/form.ts +61 -18
  55. package/src/index.ts +7 -7
  56. package/src/intl.ts +12 -0
  57. package/src/lib.ts +49 -21
  58. package/src/makeClient.ts +586 -1139
  59. package/src/makeIntl.ts +2 -2
  60. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +9 -6
  61. package/src/mutate.ts +335 -134
  62. package/src/query.ts +241 -183
  63. package/src/routeParams.ts +7 -7
  64. package/src/runtime.ts +1 -31
  65. package/src/suspense.ts +17 -0
  66. package/src/toast.ts +1 -0
  67. package/src/withToast.ts +1 -0
  68. package/test/Mutation.test.ts +181 -24
  69. package/test/dist/form.test.d.ts.map +1 -1
  70. package/test/dist/lib.test.d.ts.map +1 -0
  71. package/test/dist/streamFinal.test.d.ts.map +1 -0
  72. package/test/dist/streamFn.test.d.ts.map +1 -0
  73. package/test/dist/stubs.d.ts +3527 -122
  74. package/test/dist/stubs.d.ts.map +1 -1
  75. package/test/dist/stubs.js +187 -32
  76. package/test/dist/suspense.test.d.ts.map +1 -0
  77. package/test/form-validation-errors.test.ts +25 -20
  78. package/test/form.test.ts +22 -3
  79. package/test/lib.test.ts +240 -0
  80. package/test/makeClient.test.ts +327 -38
  81. package/test/streamFinal.test.ts +64 -0
  82. package/test/streamFn.test.ts +457 -0
  83. package/test/stubs.ts +223 -43
  84. package/test/suspense.test.ts +20 -0
  85. package/tsconfig.examples.json +20 -0
  86. package/tsconfig.json +3 -1
  87. package/tsconfig.json.bak +5 -2
  88. package/tsconfig.src.json +34 -34
  89. package/tsconfig.test.json +2 -2
  90. package/vitest.config.ts +5 -5
  91. package/dist/experimental/commander.d.ts +0 -359
  92. package/dist/experimental/commander.d.ts.map +0 -1
  93. package/dist/experimental/commander.js +0 -557
  94. package/dist/experimental/confirm.d.ts.map +0 -1
  95. package/dist/experimental/confirm.js +0 -28
  96. package/dist/experimental/intl.d.ts +0 -16
  97. package/dist/experimental/intl.d.ts.map +0 -1
  98. package/dist/experimental/intl.js +0 -5
  99. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  100. package/dist/experimental/makeUseCommand.js +0 -13
  101. package/dist/experimental/toast.d.ts +0 -47
  102. package/dist/experimental/toast.d.ts.map +0 -1
  103. package/dist/experimental/toast.js +0 -41
  104. package/dist/experimental/withToast.d.ts +0 -25
  105. package/dist/experimental/withToast.d.ts.map +0 -1
  106. package/dist/experimental/withToast.js +0 -45
  107. package/eslint.config.mjs +0 -24
  108. package/src/experimental/commander.ts +0 -1835
  109. package/src/experimental/intl.ts +0 -9
  110. package/src/experimental/toast.ts +0 -66
  111. package/src/experimental/withToast.ts +0 -99
package/src/makeClient.ts CHANGED
@@ -1,129 +1,201 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { type InvalidateOptions, type InvalidateQueryFilters, isCancelledError, type QueryObserverResult, type RefetchOptions, type UseQueryReturnType } from "@tanstack/vue-query"
2
+ import { isCancelledError, type QueryObserverResult, type RefetchOptions, type UseQueryReturnType } from "@tanstack/vue-query"
3
3
  import { camelCase } from "change-case"
4
- import { Cause, Data, Effect, Exit, Layer, type ManagedRuntime, Match, Option, S, ServiceMap, Struct } from "effect-app"
5
4
  import { type ApiClientFactory, type Req } from "effect-app/client"
6
- import type { RequestHandler, RequestHandlers, RequestHandlerWithInput, Requests } from "effect-app/client/clientFor"
7
- import { ErrorSilenced, type SupportedErrors } from "effect-app/client/errors"
8
- import { constant, identity, pipe, tuple } from "effect-app/Function"
9
- import { type OperationFailure, OperationSuccess } from "effect-app/Operations"
10
- import { dropUndefinedT, extendM } from "effect-app/utils"
5
+ import type { ExtractModuleName, HandlerInput, RequestHandlers, RequestHandlerWithInput, RequestsAny, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
6
+ import type { InvalidationCallback } from "effect-app/client/makeClient"
7
+ import type * as Context from "effect-app/Context"
8
+ import * as Effect from "effect-app/Effect"
9
+ import type * as Layer from "effect-app/Layer"
10
+ import * as S from "effect-app/Schema"
11
+ import * as Exit from "effect/Exit"
11
12
  import { type Fiber } from "effect/Fiber"
13
+ import * as Hash from "effect/Hash"
14
+ import type * as ManagedRuntime from "effect/ManagedRuntime"
15
+ import type * as Stream from "effect/Stream"
16
+ import * as Struct from "effect/Struct"
12
17
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
13
- import { computed, type ComputedRef, onBeforeUnmount, type Ref, ref, watch, type WatchSource } from "vue"
14
- import { reportMessage } from "./errorReporter.js"
15
- import { type Commander, CommanderStatic } from "./experimental/commander.js"
16
- import { I18n } from "./experimental/intl.js"
17
- import { type CommanderResolved, makeUseCommand } from "./experimental/makeUseCommand.js"
18
- import { Toast } from "./experimental/toast.js"
19
- import { buildFieldInfoFromFieldsRoot } from "./form.js"
20
- import { reportRuntimeError } from "./lib.js"
21
- import { asResult, makeMutation, type MutationOptions, type MutationOptionsBase, mutationResultToVue, type Res, useMakeMutation } from "./mutate.js"
22
- import { type CustomUndefinedInitialQueryOptions, makeQuery } from "./query.js"
23
-
24
- const mapHandler = <A, E, R, I = void, A2 = A, E2 = E, R2 = R>(
25
- handler: Effect.Effect<A, E, R> | ((i: I) => Effect.Effect<A, E, R>),
26
- map: (self: Effect.Effect<A, E, R>, i: I) => Effect.Effect<A2, E2, R2>
27
- ) => Effect.isEffect(handler) ? map(handler, undefined as any) : (i: I) => map(handler(i), i)
28
-
29
- export interface RequestExtensions<RT, Id extends string, I, A, E, R> {
18
+ import { type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
19
+ import { type Commander, CommanderStatic, type Progress } from "./commander.ts"
20
+ import { type I18n } from "./intl.ts"
21
+ import { type CommanderResolved, makeUseCommand } from "./makeUseCommand.ts"
22
+ import { type InvalidationEntry, makeMutation, makeStreamMutation2, type MutationOptionsBase, useMakeMutation } from "./mutate.ts"
23
+ import { type CustomUndefinedInitialQueryOptions, makeQuery, makeStreamQuery } from "./query.ts"
24
+ import { makeRunPromise } from "./runtime.ts"
25
+ import { awaitResolvedSuspenseResult } from "./suspense.ts"
26
+ import { type Toast } from "./toast.ts"
27
+
28
+ export type { Progress }
29
+
30
+ // TODO: optimize - work from encoded shape directly
31
+ const projectHandler = (
32
+ handler: (i: any) => Effect.Effect<any, any, any>,
33
+ successSchema: S.Top,
34
+ projectionSchema: S.Top
35
+ ) => {
36
+ const encode = S.encodeEffect(successSchema)
37
+ const decode = S.decodeEffectConcurrently(projectionSchema)
38
+ return (i: any) => handler(i).pipe(Effect.flatMap(encode), Effect.flatMap(decode))
39
+ }
40
+
41
+ const projectionSchemaHash = (schema: S.Top) => String(Hash.hash(schema.ast))
42
+
43
+ export interface CommandRequestExtensions<RT, Id extends string, I, A, E, R> {
30
44
  /** Defines a Command based on this call, taking the `id` of the call as the `id` of the Command.
31
45
  * The Request function will be taken as the first member of the Command, the Command required input will be the Request input.
32
46
  * see Command.wrap for details */
33
- wrap: Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
47
+ wrap: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
48
+ options?: Commander.FnOptions<Id, I18nKey, State>
49
+ ) => Commander.CommanderWrap<RT, Id, I18nKey, State, I, A, E, R>
34
50
  /** Defines a Command based on this call, taking the `id` of the call as the `id` of the Command.
35
51
  * see Command.fn for details */
36
- fn: Commander.CommanderFn<RT, Id, Id, undefined>
52
+ fn: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
53
+ options?: Commander.FnOptions<Id, I18nKey, State>
54
+ ) => Commander.CommanderFn<RT, Id, I18nKey, State>
37
55
  }
38
56
 
39
57
  /** my other doc */
40
- export interface RequestExtWithInput<
58
+ export interface RequestExt<
41
59
  RT,
42
60
  Id extends string,
43
61
  I,
44
62
  A,
45
63
  E,
46
64
  R
47
- > extends Commander.CommandContextLocal<Id, Id>, RequestExtensions<RT, Id, I, A, E, R> {
65
+ > extends
66
+ Commander.CommandContextLocal<Id, Id>,
67
+ Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>,
68
+ CommandRequestExtensions<RT, Id, I, A, E, R>
69
+ {
48
70
  /**
49
- * Request the endpoint with input
71
+ * Send the request to the endpoint and return the raw Effect response.
72
+ * This does not perform query cache invalidation.
50
73
  */
51
- (i: I): Effect.Effect<A, E, R>
74
+ request: (i: I) => Effect.Effect<A, E, R>
52
75
  }
53
76
 
54
- /**
55
- * Request the endpoint
56
- */
57
- export interface RequestExt<
58
- RT,
59
- Id extends string,
60
- A,
61
- E,
62
- R
63
- > extends
64
- Commander.CommandContextLocal<Id, Id>,
65
- Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>,
66
- RequestExtensions<RT, Id, void, A, E, R>,
67
- Effect.Effect<A, E, R>
68
- {
77
+ export type CommandRequestWithExtensions<RT, Req> = Req extends
78
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id> ? RequestExt<RT, Id, I, A, E, R>
79
+ : never
80
+
81
+ export interface QueryExtensions<I, A, E, R> {
82
+ /**
83
+ * Send the request to the endpoint and return the raw Effect response.
84
+ * This does not set up query state tracking.
85
+ */
86
+ request: (i: I) => Effect.Effect<A, E, R>
69
87
  }
70
88
 
71
- export type RequestWithExtensions<RT, Req> = Req extends
72
- RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id>
73
- ? RequestExtWithInput<RT, Id, I, A, E, R>
74
- : Req extends RequestHandler<infer A, infer E, infer R, infer _Request, infer Id> ? RequestExt<RT, Id, A, E, R>
89
+ export type QueryRequestWithExtensions<Req> = Req extends
90
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer _Id> ? QueryExtensions<I, A, E, R>
91
+ : never
92
+
93
+ type QueryHandler<Req> = Req extends
94
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
95
+ ? Request["type"] extends "query" ? RequestHandlerWithInput<I, A, E, R, Request, Id> : never
96
+ : never
97
+
98
+ type CommandHandler<Req> = Req extends
99
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
100
+ ? Request["type"] extends "command" ? RequestHandlerWithInput<I, A, E, R, Request, Id> : never
101
+ : never
102
+
103
+ type QueryStreamHandler<Req> = Req extends
104
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer Final>
105
+ ? [Request["stream"], Request["type"]] extends [true, "query"]
106
+ ? RequestStreamHandlerWithInput<I, A, E, R, Request, Id, Final>
107
+ : never
108
+ : never
109
+
110
+ type CommandStreamHandler<Req> = Req extends
111
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer Final>
112
+ ? [Request["stream"], Request["type"]] extends [true, "command"]
113
+ ? RequestStreamHandlerWithInput<I, A, E, R, Request, Id, Final>
114
+ : never
75
115
  : never
76
116
 
77
117
  export interface MutationExtensions<RT, Id extends string, I, A, E, R> {
78
118
  /** Defines a Command based on this mutation, taking the `id` of the mutation as the `id` of the Command.
79
119
  * The Mutation function will be taken as the first member of the Command, the Command required input will be the Mutation input.
80
120
  * see Command.wrap for details */
81
- wrap: Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
82
- /** Defines a Command based on this mutation, taking the `id` of the mutation as the `id` of the Command.
121
+ wrap: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
122
+ options?: Commander.FnOptions<Id, I18nKey, State>
123
+ ) => Commander.CommanderWrap<RT, Id, I18nKey, State, I, A, E, R>
124
+ /** Defines a Command based on this call, taking the `id` of the mutation as the `id` of the Command.
83
125
  * see Command.fn for details */
84
- fn: Commander.CommanderFn<RT, Id, Id, undefined>
85
- }
86
-
87
- /** my other doc */
88
- export interface MutationExtWithInput<
89
- RT,
90
- Id extends string,
91
- I,
92
- A,
93
- E,
94
- R
95
- > extends Commander.CommandContextLocal<Id, Id>, MutationExtensions<RT, Id, I, A, E, R> {
96
- /**
97
- * Call the endpoint with input
98
- * Invalidate queries based on namespace of this mutation.
99
- * Do not use for queries.
100
- */
101
- (i: I): Effect.Effect<A, E, R>
126
+ fn: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
127
+ options?: Commander.FnOptions<Id, I18nKey, State>
128
+ ) => Commander.CommanderFn<RT, Id, I18nKey, State>
102
129
  }
103
130
 
104
131
  /**
105
- * Call the endpoint
106
- * Invalidate queries based on namespace of this mutation.
107
- * Do not use for queries.
132
+ * Send the request to the endpoint and return the raw Effect response.
133
+ * Also invalidates query caches using the request namespace by default.
134
+ * Namespace invalidation targets parent namespace keys
135
+ * (for example `$project/$configuration.get` invalidates `$project`).
136
+ * Override invalidation in client options via `queryInvalidation`.
137
+ *
138
+ * Pass `options` to attach a `select` Effect that runs after the mutation
139
+ * succeeds (its output is returned to the caller) and/or override the default
140
+ * `queryInvalidation`.
141
+ *
142
+ * When `I = void` the input argument may be omitted.
108
143
  */
109
144
  export interface MutationExt<
110
145
  RT,
111
146
  Id extends string,
147
+ I,
112
148
  A,
113
149
  E,
114
- R
115
- > extends
116
- Commander.CommandContextLocal<Id, Id>,
117
- Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>,
118
- MutationExtensions<RT, Id, void, A, E, R>,
119
- Effect.Effect<A, E, R>
120
- {
150
+ R,
151
+ EA = unknown
152
+ > extends MutationExtensions<RT, Id, I, A, E, R> {
153
+ <B = A, E2 = never, R2 = never>(
154
+ input: I,
155
+ options?: MutationOptionsBase<A, B, E2, R2>
156
+ ): Effect.Effect<B, E | E2, R | R2>
157
+
158
+ project: <ProjSchema extends S.Top>(
159
+ schema: EA extends ProjSchema["Encoded"] ? ProjSchema : never
160
+ ) => MutationExt<
161
+ RT,
162
+ Id,
163
+ I,
164
+ ProjSchema["Type"],
165
+ E | S.SchemaError,
166
+ R | ProjSchema["DecodingServices"],
167
+ ProjSchema["Encoded"]
168
+ >
121
169
  }
122
170
 
123
171
  export type MutationWithExtensions<RT, Req> = Req extends
124
- RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id>
125
- ? MutationExtWithInput<RT, Id, I, A, E, R>
126
- : Req extends RequestHandler<infer A, infer E, infer R, infer _Request, infer Id> ? MutationExt<RT, Id, A, E, R>
172
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
173
+ ? MutationExt<RT, Id, I, A, E, R, Request["success"]["Encoded"]>
174
+ : never
175
+
176
+ /**
177
+ * The `streamFn` builder for a stream-type request handler, using the stream-specific overloads.
178
+ */
179
+ export type StreamFnStreamExtension<RT, Req> = Req extends
180
+ RequestStreamHandlerWithInput<infer _I, infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
181
+ ? <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
182
+ options?: Commander.FnOptions<Id, I18nKey, State>
183
+ ) => Commander.StreamGen<RT, Id, I18nKey, State> & Commander.NonGenStream<RT, Id, I18nKey, State>
184
+ : never
185
+
186
+ /**
187
+ * `mutate` factory — wraps per-invocation invalidation scaffolding
188
+ * into the stream itself (via `Stream.unwrap`) for use with `streamFn` combinators.
189
+ */
190
+ export type StreamMutation2WithExtensions<RT, Req> = Req extends
191
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer _Final> ?
192
+ & ((input: I) => Stream.Stream<A, E, R>)
193
+ & {
194
+ readonly id: Id
195
+ readonly wrap: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
196
+ options?: Commander.FnOptions<Id, I18nKey, State>
197
+ ) => Commander.StreamerWrap<RT, Id, I18nKey, State, I, A, E, R>
198
+ }
127
199
  : never
128
200
 
129
201
  // we don't really care about the RT, as we are in charge of ensuring runtime safety anyway
@@ -131,33 +203,50 @@ export type MutationWithExtensions<RT, Req> = Req extends
131
203
  declare const useQuery_: QueryImpl<any>["useQuery"]
132
204
  // eslint-disable-next-line unused-imports/no-unused-vars
133
205
  declare const useSuspenseQuery_: QueryImpl<any>["useSuspenseQuery"]
206
+ // eslint-disable-next-line unused-imports/no-unused-vars
207
+ declare const useStreamQuery_: QueryImpl<any>["useStreamQuery"]
208
+
209
+ export interface ProjectResult<RT, I, B, E, R, Request extends Req, Id extends string> {
210
+ request: (i: I) => Effect.Effect<B, E, R>
211
+ query: Exclude<R, RT> extends never ? ReturnType<typeof useQuery_<I, E, B, Request, Id>>
212
+ : MissingDependencies<RT, R> & {}
213
+ suspense: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQuery_<I, E, B, Request, Id>>
214
+ : MissingDependencies<RT, R> & {}
215
+ }
216
+
217
+ export type QueryProjection<RT, HandlerReq> = HandlerReq extends
218
+ RequestHandlerWithInput<infer I, infer _A, infer E, infer R, infer Request, infer Id>
219
+ ? Request["type"] extends "query" ? {
220
+ project: <ProjSchema extends S.Top>(
221
+ schema: Request["success"]["Encoded"] extends ProjSchema["Encoded"] ? ProjSchema : never
222
+ ) => ProjectResult<
223
+ RT,
224
+ I,
225
+ ProjSchema["Type"],
226
+ E | S.SchemaError,
227
+ R | ProjSchema["DecodingServices"],
228
+ Request,
229
+ Id
230
+ >
231
+ }
232
+ : {}
233
+ : {}
134
234
 
135
- export interface QueriesWithInput<Request extends Req, Id extends string, I, A, E> {
235
+ export interface QueryResultExtensions<Request extends Req, Id extends string, I, A, E> {
136
236
  /**
137
- * Effect results are passed to the caller, including errors.
237
+ * Read helper for query requests.
238
+ * Runs as a tracked Vue Query and returns reactive state.
239
+ * Queries read state and should not be used to mutate it.
240
+ * When `I = void` the input argument may be omitted.
138
241
  */
139
242
  query: ReturnType<typeof useQuery_<I, E, A, Request, Id>>
140
243
  // TODO or suspense as Option?
141
244
  /**
142
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
143
- * which ensures that either there always is a latest value, or an error occurs on load.
144
- * So that Suspense and error boundaries can be used.
245
+ * Like `.query`, but returns a Promise for setup-time awaiting.
246
+ * Use this when integrating with Vue Suspense / error boundaries.
145
247
  */
146
248
  suspense: ReturnType<typeof useSuspenseQuery_<I, E, A, Request, Id>>
147
249
  }
148
- export interface QueriesWithoutInput<Request extends Req, Id extends string, A, E> {
149
- /**
150
- * Effect results are passed to the caller, including errors.
151
- */
152
- query: ReturnType<typeof useQuery_<E, A, Request, Id>>
153
- // TODO or suspense as Option?
154
- /**
155
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
156
- * which ensures that either there always is a latest value, or an error occurs on load.
157
- * So that Suspense and error boundaries can be used.
158
- */
159
- suspense: ReturnType<typeof useSuspenseQuery_<E, A, Request, Id>>
160
- }
161
250
 
162
251
  export type MissingDependencies<RT, R> = {
163
252
  message: "Dependencies required that are not provided by the runtime"
@@ -166,184 +255,40 @@ export type MissingDependencies<RT, R> = {
166
255
 
167
256
  export type Queries<RT, Req> = Req extends
168
257
  RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
169
- ? Exclude<R, RT> extends never ? QueriesWithInput<Request, Id, I, A, E>
170
- : {
171
- query: MissingDependencies<RT, R> & {}
172
- suspense: MissingDependencies<RT, R> & {}
173
- }
174
- : Req extends RequestHandler<infer A, infer E, infer R, infer Request, infer Id>
175
- ? Exclude<R, RT> extends never ? QueriesWithoutInput<Request, Id, A, E>
258
+ ? Request["type"] extends "query" ? Exclude<R, RT> extends never ? QueryResultExtensions<Request, Id, I, A, E>
176
259
  : { query: MissingDependencies<RT, R> & {}; suspense: MissingDependencies<RT, R> & {} }
177
260
  : never
261
+ : never
178
262
 
179
- /**
180
- * Use this after handling an error yourself, still continueing on the Error track, but the error will not be reported.
181
- */
182
- export class SuppressErrors extends Data.TaggedError("SuppressErrors")<{}> {
183
- readonly [ErrorSilenced] = true as const
184
- }
185
-
186
- export type ResponseErrors = S.SchemaError | SupportedErrors | SuppressErrors | OperationFailure
187
-
188
- export interface Opts<
189
- A,
190
- E,
191
- R,
192
- I = void,
193
- A2 = A,
194
- E2 = E,
195
- R2 = R,
196
- ESuccess = never,
197
- RSuccess = never,
198
- EError = never,
199
- RError = never,
200
- EDefect = never,
201
- RDefect = never
202
- > extends MutationOptions<A, E, R, A2, E2, R2, I> {
203
- /** set to `undefined` to use default message */
204
- successMessage?: ((a: A2, i: I) => Effect.Effect<string | undefined, ESuccess, RSuccess>) | undefined
205
- /** set to `undefined` to use default message */
206
- failMessage?: ((e: E2, i: I) => Effect.Effect<string | undefined, EError, RError>) | undefined
207
- /** set to `undefined` to use default message */
208
- defectMessage?: ((e: Cause.Cause<E2>, i: I) => Effect.Effect<string | undefined, EDefect, RDefect>) | undefined
209
- }
210
-
211
- export interface LowOpts<
212
- A,
213
- E,
214
- I = void,
215
- ESuccess = never,
216
- RSuccess = never,
217
- EError = never,
218
- RError = never,
219
- EDefect = never,
220
- RDefect = never
221
- > {
222
- onSuccess: (a: A, i: I) => Effect.Effect<void, ESuccess, RSuccess>
223
- onFail: (e: E, i: I) => Effect.Effect<void, EError, RError>
224
- onDefect: (e: Cause.Cause<E>, i: I) => Effect.Effect<void, EDefect, RDefect>
225
- }
226
-
227
- export interface LowOptsOptional<
228
- A,
229
- E,
230
- R,
231
- I = void,
232
- A2 = A,
233
- E2 = E,
234
- R2 = R,
235
- ESuccess = never,
236
- RSuccess = never,
237
- EError = never,
238
- RError = never,
239
- EDefect = never,
240
- RDefect = never
241
- > extends MutationOptions<A, E, R, A2, E2, R2, I> {
242
- onSuccess?: (a: A, i: I) => Effect.Effect<void, ESuccess, RSuccess>
243
- onFail?: (e: E, i: I) => Effect.Effect<void, EError, RError>
244
- onDefect?: (e: Cause.Cause<E>, i: I) => Effect.Effect<void, EDefect, RDefect>
245
- }
246
-
247
- type WithAction<A> = A & {
248
- action: string
263
+ export interface StreamQueryExtensions<Request extends Req, Id extends string, I, A, E> {
264
+ /**
265
+ * Stream helper for query-stream requests.
266
+ * Runs as a tracked Vue Query and returns reactive state with accumulated chunks.
267
+ * Data is an array of all chunks received so far.
268
+ * When `I = void` the input argument may be omitted.
269
+ */
270
+ query: ReturnType<typeof useStreamQuery_<I, E, A, Request, Id>>
249
271
  }
272
+ export type StreamQueries<RT, HandlerReq> = HandlerReq extends
273
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer _Final>
274
+ ? Exclude<R, RT> extends never ? StreamQueryExtensions<Request, Id, I, A, E>
275
+ : { query: MissingDependencies<RT, R> & {} }
276
+ : never
250
277
 
251
- // computed() takes a getter function and returns a readonly reactive ref
252
- // object for the returned value from the getter.
253
-
254
- type Resp<I, A, E, R, V = ComputedRef<Res<A, E>>> = readonly [
255
- V,
256
- WithAction<(I: I) => Effect.Effect<Exit.Exit<A, E>, never, R>>
257
- ]
258
-
259
- type ActResp<A, E, R, V = ComputedRef<Res<A, E>>> = readonly [
260
- V,
261
- WithAction<Effect.Effect<Exit.Exit<A, E>, never, R>>
262
- ]
263
-
264
- export const suppressToast = constant(Effect.succeed(undefined))
265
-
266
- /** handles errors as specified and reports defects */
267
- function handleRequest<
268
- E extends ResponseErrors,
269
- A,
270
- R,
271
- I = void,
272
- ESuccess = never,
273
- RSuccess = never,
274
- EError = never,
275
- RError = never,
276
- EDefect = never,
277
- RDefect = never
278
- >(
279
- f: Effect.Effect<Exit.Exit<A, E>, never, R> | ((i: I) => Effect.Effect<Exit.Exit<A, E>, never, R>),
280
- id: string,
281
- action: string,
282
- options: {
283
- onSuccess: (a: A, i: I) => Effect.Effect<void, ESuccess, RSuccess>
284
- onFail: (e: E, i: I) => Effect.Effect<void, EError, RError>
285
- onDefect: (e: Cause.Cause<E>, i: I) => Effect.Effect<void, EDefect, RDefect>
286
- }
287
- ) {
288
- const handleEffect = (i: any) => (self: Effect.Effect<Exit.Exit<A, E>, never, R>) =>
289
- self.pipe(
290
- Effect.tap(
291
- Effect.matchCauseEffect({
292
- onSuccess: (r) => options.onSuccess(r, i),
293
- onFailure: (cause) =>
294
- Effect.gen(function*() {
295
- if (Cause.hasInterruptsOnly(cause)) {
296
- console.info(`Interrupted while trying to ${action}`)
297
- return
298
- }
299
-
300
- const fail = Cause.findErrorOption(cause)
301
- if (Option.isSome(fail)) {
302
- if (fail.value._tag === "SuppressErrors") {
303
- console.info(`Suppressed error trying to ${action}`, fail.value)
304
- return
305
- }
306
- const message = `Failure trying to ${action}`
307
- yield* reportMessage(message, { action, error: fail.value })
308
- yield* options.onFail(fail.value, i)
309
- return
310
- }
311
-
312
- const extra = {
313
- action,
314
- message: `Unexpected Error trying to ${action}`
315
- }
316
- yield* reportRuntimeError(cause, extra)
278
+ const _useMutation = makeMutation()
317
279
 
318
- yield* options.onDefect(cause, i)
319
- })
320
- })
321
- ),
322
- Effect.withSpan(`mutation ${id}`, {}, { captureStackTrace: false })
323
- )
324
- return Object.assign(
325
- Effect.isEffect(f)
326
- ? pipe(
327
- f,
328
- handleEffect(void 0)
329
- )
330
- : (i: I) =>
331
- pipe(
332
- f(i),
333
- handleEffect(i)
334
- ),
335
- { action }
336
- )
280
+ const wrapWithSpan = (self: { id: string }, mut: any) => {
281
+ const span = (eff: Effect.Effect<any, any, any>) =>
282
+ Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })(eff)
283
+ return (input: any, options?: MutationOptionsBase) => span(mut(input, options))
337
284
  }
338
285
 
339
- const _useMutation = makeMutation()
340
-
341
286
  /**
342
287
  * Pass an Effect or a function that returns an Effect, e.g from a client action
343
288
  * Executes query cache invalidation based on default rules or provided option.
344
289
  * adds a span with the mutation id
345
290
  */
346
- export const useMutation: typeof _useMutation = <
291
+ export const useMutation: typeof _useMutation = (<
347
292
  I,
348
293
  E,
349
294
  A,
@@ -351,16 +296,12 @@ export const useMutation: typeof _useMutation = <
351
296
  Request extends Req,
352
297
  Name extends string
353
298
  >(
354
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
355
- options?: MutationOptionsBase
299
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
356
300
  ) =>
357
301
  Object.assign(
358
- mapHandler(
359
- _useMutation(self as any, options),
360
- Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })
361
- ) as any,
302
+ wrapWithSpan(self, _useMutation(self as any)),
362
303
  { id: self.id }
363
- )
304
+ )) as any
364
305
 
365
306
  /**
366
307
  * Pass an Effect or a function that returns an Effect, e.g from a client action
@@ -369,7 +310,7 @@ export const useMutation: typeof _useMutation = <
369
310
  */
370
311
  export const useMutationInt = (): typeof _useMutation => {
371
312
  const _useMutation = useMakeMutation()
372
- return <
313
+ return (<
373
314
  I,
374
315
  E,
375
316
  A,
@@ -377,715 +318,23 @@ export const useMutationInt = (): typeof _useMutation => {
377
318
  Request extends Req,
378
319
  Name extends string
379
320
  >(
380
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
381
- options?: MutationOptionsBase
321
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
382
322
  ) =>
383
323
  Object.assign(
384
- mapHandler(
385
- _useMutation(self as any, options),
386
- Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })
387
- ) as any,
324
+ wrapWithSpan(self, _useMutation(self as any)),
388
325
  { id: self.id }
389
- )
390
- }
391
-
392
- export class LegacyMutationImpl<RT> {
393
- constructor(
394
- private readonly getRuntime: () => ServiceMap.ServiceMap<RT>,
395
- private readonly toast: Toast,
396
- private readonly intl: I18n
397
- ) {}
398
-
399
- /**
400
- * Effect results are converted to Exit, so errors are ignored by default.
401
- * you should use the result ref to render errors!
402
- * @deprecated use `Command.fn` and friends instead
403
- */
404
- readonly useSafeMutation: {
405
- /**
406
- * Effect results are converted to Exit, so errors are ignored by default.
407
- * you should use the result ref to render errors!
408
- * @deprecated use `Command.fn` and friends instead
409
- */
410
- <I, E, A, R, Request extends Req, Name extends string, A2 = A, E2 = E, R2 = R>(
411
- self: RequestHandlerWithInput<I, A, E, R, Request, Name>,
412
- options?: MutationOptions<A, E, R, A2, E2, R2, I>
413
- ): readonly [
414
- ComputedRef<AsyncResult.AsyncResult<A2, E2>>,
415
- (i: I) => Effect.Effect<Exit.Exit<A2, E2>, never, R2>
416
- ]
417
- /**
418
- * Effect results are converted to Exit, so errors are ignored by default.
419
- * you should use the result ref to render errors!
420
- * @deprecated use `Command.fn` and friends instead
421
- */
422
- <E, A, R, Request extends Req, Name extends string, A2 = A, E2 = E, R2 = R>(
423
- self: RequestHandler<A, E, R, Request, Name>,
424
- options?: MutationOptions<A, E, R, A2, E2, R2>
425
- ): readonly [
426
- ComputedRef<AsyncResult.AsyncResult<A2, E2>>,
427
- Effect.Effect<Exit.Exit<A2, E2>, never, R2>
428
- ]
429
- } = <I, E, A, R, Request extends Req, Name extends string, A2 = A, E2 = E, R2 = R>(
430
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
431
- options?: MutationOptions<A, E, R, A2, E2, R2, I>
432
- ) => {
433
- const unsafe = _useMutation(self as any, options)
434
-
435
- type MH = NonNullable<NonNullable<typeof options>["mapHandler"]>
436
- const mh = options?.mapHandler ?? identity as MH
437
-
438
- const [a, b] = asResult(
439
- mapHandler(
440
- mapHandler(unsafe as any, mh),
441
- Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause))
442
- ) as any
443
- )
444
- return [
445
- a,
446
- mapHandler(
447
- b,
448
- Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })
449
- )
450
- ] as const as any
451
- }
452
-
453
- /** handles errors as toasts and reports defects
454
- * @deprecated use `Command.fn` and friends instead
455
- */
456
- readonly useHandleRequestWithToast = () => {
457
- // eslint-disable-next-line @typescript-eslint/no-this-alias
458
- const self = this
459
- return handleRequestWithToast
460
- /**
461
- * Pass a function that returns a Promise.
462
- * Returns an execution function which reports errors as Toast.
463
- */
464
- function handleRequestWithToast<
465
- A,
466
- E extends ResponseErrors,
467
- R,
468
- I = void,
469
- A2 = A,
470
- E2 extends ResponseErrors = E,
471
- R2 = R,
472
- ESuccess = never,
473
- RSuccess = never,
474
- EError = never,
475
- RError = never,
476
- EDefect = never,
477
- RDefect = never
478
- >(
479
- f: Effect.Effect<Exit.Exit<A2, E2>, never, R2> | ((i: I) => Effect.Effect<Exit.Exit<A2, E2>, never, R2>),
480
- id: string,
481
- action: string,
482
- options: Opts<A, E, R, I, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect> = {}
483
- ) {
484
- const actionMessage = self.intl.formatMessage({ id: `action.${action}`, defaultMessage: action })
485
- const defaultWarnMessage = self.intl.formatMessage(
486
- { id: "handle.with_warnings" },
487
- { action: actionMessage }
488
- )
489
- const defaultSuccessMessage = self.intl.formatMessage(
490
- { id: "handle.success" },
491
- { action: actionMessage }
492
- )
493
- const defaultErrorMessage = self.intl.formatMessage(
494
- { id: "handle.with_errors" },
495
- { action: actionMessage }
496
- )
497
-
498
- return handleRequest<E2, A2, R2, any, ESuccess, RSuccess, EError, RError, EDefect, RDefect>(f, id, action, {
499
- onSuccess: Effect.fnUntraced(function*(a, i) {
500
- const message = options.successMessage ? yield* options.successMessage(a, i) : defaultSuccessMessage
501
- + (S.is(OperationSuccess)(a) && a.message
502
- ? "\n" + a.message
503
- : "")
504
- if (message) {
505
- yield* self.toast.success(message)
506
- }
507
- }),
508
- onFail: Effect.fnUntraced(function*(e, i) {
509
- if (!options.failMessage && e._tag === "OperationFailure") {
510
- yield* self.toast.warning(
511
- defaultWarnMessage + e.message
512
- ? "\n" + e.message
513
- : ""
514
- )
515
- return
516
- }
517
-
518
- const message = options.failMessage
519
- ? yield* options.failMessage(e, i)
520
- : `${defaultErrorMessage}:\n` + renderError(e)
521
- if (message) {
522
- yield* self.toast.error(message)
523
- }
524
- }),
525
- onDefect: Effect.fnUntraced(function*(cause, i) {
526
- const message = options.defectMessage
527
- ? yield* options.defectMessage(cause, i)
528
- : self.intl.formatMessage(
529
- { id: "handle.unexpected_error" },
530
- {
531
- action: actionMessage,
532
- error: Cause.pretty(cause)
533
- }
534
- )
535
- if (message) {
536
- yield* self.toast.error(message)
537
- }
538
- })
539
- })
540
- }
541
-
542
- function renderError(e: ResponseErrors): string {
543
- return Match.value(e as any).pipe(
544
- Match.tags({
545
- // HttpErrorRequest: e =>
546
- // this.intl.value.formatMessage(
547
- // { id: "handle.request_error" },
548
- // { error: `${e.error}` },
549
- // ),
550
- // HttpErrorResponse: e =>
551
- // e.response.status >= 500 ||
552
- // e.response.body._tag !== "Some" ||
553
- // !e.response.body.value
554
- // ? this.intl.value.formatMessage(
555
- // { id: "handle.error_response" },
556
- // {
557
- // error: `${
558
- // e.response.body._tag === "Some" && e.response.body.value
559
- // ? parseError(e.response.body.value)
560
- // : "Unknown"
561
- // } (${e.response.status})`,
562
- // },
563
- // )
564
- // : this.intl.value.formatMessage(
565
- // { id: "handle.unexpected_error" },
566
- // {
567
- // error:
568
- // JSON.stringify(e.response.body, undefined, 2) +
569
- // "( " +
570
- // e.response.status +
571
- // ")",
572
- // },
573
- // ),
574
- // ResponseError: e =>
575
- // this.intl.value.formatMessage(
576
- // { id: "handle.response_error" },
577
- // { error: `${e.error}` },
578
- // ),
579
- SchemaError: (e: any) => {
580
- console.warn(e.toString())
581
- return self.intl.formatMessage({ id: "validation.failed" })
582
- }
583
- }),
584
- Match.orElse((e: any) => `${e.message ?? e._tag ?? e}`)
585
- )
586
- }
587
- }
588
-
589
- /**
590
- * Pass a function that returns an Effect, e.g from a client action, give it a name.
591
- * Returns a tuple with raw Result and execution function which reports success and errors as Toast.
592
- * @deprecated use `Command.fn` and friends instead
593
- */
594
- readonly useAndHandleMutationResult: {
595
- /**
596
- * Pass a function that returns an Effect, e.g from a client action, give it a name.
597
- * Returns a tuple with raw Result and execution function which reports success and errors as Toast.
598
- * @deprecated use `Command.fn` and friends instead
599
- */
600
- <
601
- I,
602
- E extends ResponseErrors,
603
- A,
604
- R,
605
- Request extends Req,
606
- Name extends string,
607
- A2 = A,
608
- E2 extends ResponseErrors = E,
609
- R2 = R,
610
- ESuccess = never,
611
- RSuccess = never,
612
- EError = never,
613
- RError = never,
614
- EDefect = never,
615
- RDefect = never
616
- >(
617
- self: RequestHandlerWithInput<I, A, E, R, Request, Name>,
618
- action: string,
619
- options?: Opts<A, E, R, I, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
620
- ): Resp<I, A2, E2, R2, ComputedRef<AsyncResult.AsyncResult<A2, E2>>>
621
- /**
622
- * Pass a function that returns an Effect, e.g from a client action, give it a name.
623
- * Returns a tuple with raw Result and execution function which reports success and errors as Toast.
624
- * @deprecated use `Command.fn` and friends instead
625
- */
626
- <
627
- E extends ResponseErrors,
628
- A,
629
- R,
630
- Request extends Req,
631
- Name extends string,
632
- A2 = A,
633
- E2 extends ResponseErrors = E,
634
- R2 = R,
635
- ESuccess = never,
636
- RSuccess = never,
637
- EError = never,
638
- RError = never,
639
- EDefect = never,
640
- RDefect = never
641
- >(
642
- self: RequestHandler<A, E, R, Request, Name>,
643
- action: string,
644
- options?: Opts<A, E, R, void, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
645
- ): ActResp<A2, E2, R2, ComputedRef<AsyncResult.AsyncResult<A2, E2>>>
646
- } = <E extends ResponseErrors, A, R, Request extends Req, Name extends string, I>(
647
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
648
- action: any,
649
- options?: Opts<any, any, any, any, any, any, any, any, any, any, any, any, any>
650
- ): any => {
651
- const handleRequestWithToast = this.useHandleRequestWithToast()
652
- const handler = self.handler
653
- const unsafe = _useMutation({
654
- ...self,
655
- handler: Effect.isEffect(handler)
656
- ? (pipe(
657
- Effect.annotateCurrentSpan({ action }),
658
- Effect.andThen(handler)
659
- ) as any)
660
- : (...args: [any]) =>
661
- pipe(
662
- Effect.annotateCurrentSpan({ action }),
663
- Effect.andThen(handler(...args))
664
- )
665
- }, options ? dropUndefinedT(options) : undefined)
666
-
667
- type MH = NonNullable<NonNullable<typeof options>["mapHandler"]>
668
- const mh = options?.mapHandler ?? identity as MH
669
-
670
- // Effect.tapDefect(reportRuntimeError) handled in toast handler,
671
- const [a, b] = asResult(mapHandler(unsafe, mh) as any)
672
-
673
- return tuple(
674
- a,
675
- handleRequestWithToast(b as any, self.id, action, options)
676
- )
677
- }
678
- //
679
-
680
- /**
681
- * Pass a function that returns an Effect, e.g from a client action, give it a name.
682
- * Returns a tuple with state ref and execution function which reports success and errors as Toast.
683
- *
684
- * @deprecated use `Command.fn` and friends instead
685
- */
686
- readonly useAndHandleMutation: {
687
- /**
688
- * Pass a function that returns an Effect, e.g from a client action, give it a name.
689
- * Returns a tuple with state ref and execution function which reports success and errors as Toast.
690
- *
691
- * @deprecated use `Command.fn` and friends instead
692
- */
693
- <
694
- I,
695
- E extends ResponseErrors,
696
- A,
697
- R,
698
- Request extends Req,
699
- Name extends string,
700
- A2 = A,
701
- E2 extends ResponseErrors = E,
702
- R2 = R,
703
- ESuccess = never,
704
- RSuccess = never,
705
- EError = never,
706
- RError = never,
707
- EDefect = never,
708
- RDefect = never
709
- >(
710
- self: RequestHandlerWithInput<I, A, E, R, Request, Name>,
711
- action: string,
712
- options?: Opts<A, E, R, I, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
713
- ): Resp<I, A2, E2, R2>
714
- /**
715
- * Pass a function that returns an Effect, e.g from a client action, give it a name.
716
- * Returns a tuple with state ref and execution function which reports success and errors as Toast.
717
- *
718
- * @deprecated use `Command.fn` and friends instead
719
- */
720
- <
721
- E extends ResponseErrors,
722
- A,
723
- R,
724
- Request extends Req,
725
- Name extends string,
726
- A2 = A,
727
- E2 extends ResponseErrors = E,
728
- R2 = R,
729
- ESuccess = never,
730
- RSuccess = never,
731
- EError = never,
732
- RError = never,
733
- EDefect = never,
734
- RDefect = never
735
- >(
736
- self: RequestHandler<A, E, R, Request, Name>,
737
- action: string,
738
- options?: Opts<A, E, R, void, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
739
- ): ActResp<A2, E2, R2>
740
- } = (
741
- self: any,
742
- action: any,
743
- options?: Opts<any, any, any, any, any, any, any, any, any, any, any, any, any>
744
- ): any => {
745
- const [a, b] = this.useAndHandleMutationResult(self, action, options)
746
-
747
- return tuple(
748
- computed(() => mutationResultToVue(a.value)),
749
- b
750
- )
751
- }
752
-
753
- /** @deprecated use `Command.fn` and friends instead */
754
- readonly makeUseAndHandleMutation = (
755
- defaultOptions?: Opts<any, any, any, any, any, any, any, any, any>
756
- ) =>
757
- ((self: any, action: any, options: any) => {
758
- return this.useAndHandleMutation(
759
- self,
760
- action,
761
- { ...defaultOptions, ...options }
762
- )
763
- }) as unknown as {
764
- <
765
- I,
766
- E extends ResponseErrors,
767
- A,
768
- R,
769
- Request extends Req,
770
- Name extends string,
771
- A2 = A,
772
- E2 extends ResponseErrors = E,
773
- R2 = R,
774
- ESuccess = never,
775
- RSuccess = never,
776
- EError = never,
777
- RError = never,
778
- EDefect = never,
779
- RDefect = never
780
- >(
781
- self: RequestHandlerWithInput<I, A, E, R, Request, Name>,
782
- action: string,
783
- options?: Opts<A, E, R, I, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
784
- ): Resp<I, A2, E2, R2>
785
- <
786
- E extends ResponseErrors,
787
- A,
788
- R,
789
- Request extends Req,
790
- Name extends string,
791
- A2 = A,
792
- E2 extends ResponseErrors = E,
793
- R2 = R,
794
- ESuccess = never,
795
- RSuccess = never,
796
- EError = never,
797
- RError = never,
798
- EDefect = never,
799
- RDefect = never
800
- >(
801
- self: RequestHandler<A, E, R, Request, Name>,
802
- action: string,
803
- options?: Opts<A, E, R, void, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
804
- ): ActResp<A2, E2, R2>
805
- }
806
-
807
- /**
808
- * The same as @see useAndHandleMutation, but does not display any toasts by default.
809
- * Messages for success, error and defect toasts can be provided in the Options.
810
- * @deprecated use `Command.fn` and friends instead
811
- */
812
- readonly useAndHandleMutationSilently: {
813
- /**
814
- * The same as @see useAndHandleMutation, but does not display any toasts by default.
815
- * Messages for success, error and defect toasts can be provided in the Options.
816
- * @deprecated use `Command.fn` and friends instead
817
- */
818
- <
819
- I,
820
- E extends ResponseErrors,
821
- A,
822
- R,
823
- Request extends Req,
824
- Name extends string,
825
- A2 = A,
826
- E2 extends ResponseErrors = E,
827
- R2 = R,
828
- ESuccess = never,
829
- RSuccess = never,
830
- EError = never,
831
- RError = never,
832
- EDefect = never,
833
- RDefect = never
834
- >(
835
- self: RequestHandlerWithInput<I, A, E, R, Request, Name>,
836
- action: string,
837
- options?: Opts<A, E, R, I, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
838
- ): Resp<I, A2, E2, R>
839
- /**
840
- * The same as @see useAndHandleMutation, but does not display any toasts by default.
841
- * Messages for success, error and defect toasts can be provided in the Options.
842
- * @deprecated use `Command.fn` and friends instead
843
- */
844
- <
845
- E extends ResponseErrors,
846
- A,
847
- R,
848
- Request extends Req,
849
- Name extends string,
850
- A2 = A,
851
- E2 extends ResponseErrors = E,
852
- R2 = R,
853
- ESuccess = never,
854
- RSuccess = never,
855
- EError = never,
856
- RError = never,
857
- EDefect = never,
858
- RDefect = never
859
- >(
860
- self: RequestHandler<A, E, R, Request, Name>,
861
- action: string,
862
- options?: Opts<A, E, R, void, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
863
- ): ActResp<void, never, R>
864
- } = this.makeUseAndHandleMutation({
865
- successMessage: suppressToast,
866
- failMessage: suppressToast,
867
- defectMessage: suppressToast
868
- }) as any
869
-
870
- /**
871
- * The same as @see useAndHandleMutation, but does not act on success, error or defect by default.
872
- * Actions for success, error and defect can be provided in the Options.
873
- * @deprecated use `Command.fn` and friends instead
874
- */
875
- readonly useAndHandleMutationCustom: {
876
- /**
877
- * The same as @see useAndHandleMutation, but does not act on success, error or defect by default.
878
- * Actions for success, error and defect can be provided in the Options.
879
- * @deprecated use `Command.fn` and friends instead
880
- */
881
- <
882
- I,
883
- E extends ResponseErrors,
884
- A,
885
- R,
886
- Request extends Req,
887
- Name extends string,
888
- A2 = A,
889
- E2 extends ResponseErrors = E,
890
- R2 = R,
891
- ESuccess = never,
892
- RSuccess = never,
893
- EError = never,
894
- RError = never,
895
- EDefect = never,
896
- RDefect = never
897
- >(
898
- self: RequestHandlerWithInput<I, A, E, R, Request, Name>,
899
- action: string,
900
- options?: LowOptsOptional<A, E, R, I, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
901
- ): Resp<I, A2, E2, R2>
902
- /**
903
- * The same as @see useAndHandleMutation, but does not act on success, error or defect by default.
904
- * Actions for success, error and defect can be provided in the Options.
905
- * @deprecated use `Command.fn` and friends instead
906
- */
907
- <
908
- E extends ResponseErrors,
909
- A,
910
- R,
911
- Request extends Req,
912
- Name extends string,
913
- A2 = A,
914
- E2 extends ResponseErrors = E,
915
- R2 = R,
916
- ESuccess = never,
917
- RSuccess = never,
918
- EError = never,
919
- RError = never,
920
- EDefect = never,
921
- RDefect = never
922
- >(
923
- self: RequestHandler<A, E, R, Request, Name>,
924
- action: string,
925
- options?: LowOptsOptional<A, E, R, void, A2, E2, R2, ESuccess, RSuccess, EError, RError, EDefect, RDefect>
926
- ): ActResp<A2, E2, R2>
927
- } = (self: any, action: string, options: any) => {
928
- const unsafe = _useMutation({
929
- ...self,
930
- handler: Effect.isEffect(self.handler)
931
- ? (pipe(
932
- Effect.annotateCurrentSpan({ action }),
933
- Effect.andThen(self.handler)
934
- ) as any)
935
- : (...args: any[]) =>
936
- pipe(
937
- Effect.annotateCurrentSpan({ action }),
938
- Effect.andThen(self.handler(...args))
939
- )
940
- }, options ? dropUndefinedT(options) : undefined)
941
-
942
- type MH = NonNullable<NonNullable<typeof options>["mapHandler"]>
943
- const mh = options?.mapHandler ?? identity as MH
944
-
945
- const [a, b] = asResult(
946
- mapHandler(
947
- mapHandler(unsafe as any, mh),
948
- Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause))
949
- ) as any
950
- )
951
-
952
- return tuple(
953
- computed(() => mutationResultToVue(a.value)),
954
- handleRequest(b as any, self.id, action, {
955
- onSuccess: suppressToast,
956
- onDefect: suppressToast,
957
- onFail: suppressToast,
958
- ...options
959
- })
960
- ) as any
961
- }
962
-
963
- /**
964
- * Effect results are converted to Exit, so errors are ignored by default.
965
- * you should use the result ref to render errors!
966
- * @deprecated use `Command.fn` and friends instead
967
- */
968
- readonly useSafeMutationWithState: {
969
- /**
970
- * Effect results are converted to Exit, so errors are ignored by default.
971
- * you should use the result ref to render errors!
972
- * @deprecated use `Command.fn` and friends instead
973
- */
974
- <I, E, A, R, Request extends Req, Name extends string, A2 = A, E2 = E, R2 = R>(
975
- self: RequestHandlerWithInput<I, A, E, R, Request, Name>,
976
- options?: MutationOptions<A, E, R, A2, E2, R2, I>
977
- ): readonly [
978
- ComputedRef<Res<A, E>>,
979
- (i: I) => Effect.Effect<Exit.Exit<A2, E2>, never, R2>
980
- ]
981
- /**
982
- * Effect results are converted to Exit, so errors are ignored by default.
983
- * you should use the result ref to render errors!
984
- * @deprecated use `Command.fn` and friends instead
985
- */
986
- <E, A, R, Request extends Req, Name extends string, A2 = A, E2 = E, R2 = R>(
987
- self: RequestHandler<A, E, R, Request, Name>,
988
- options?: MutationOptions<A, E, R, A2, E2, R2>
989
- ): readonly [
990
- ComputedRef<Res<A, E>>,
991
- Effect.Effect<Exit.Exit<A2, E2>, never, R2>
992
- ]
993
- } = <I, E, A, R, Request extends Req, Name extends string, A2 = A, E2 = E, R2 = R>(
994
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
995
- options?: MutationOptions<A, E, R, A2, E2, R2, I>
996
- ) => {
997
- const [a, b] = this.useSafeMutation(self as any, options)
998
-
999
- return tuple(
1000
- computed(() => mutationResultToVue(a.value)),
1001
- b
1002
- ) as any
1003
- }
1004
-
1005
- /** @deprecated use OmegaForm */
1006
- readonly buildFormFromSchema = <
1007
- From extends Record<PropertyKey, any>,
1008
- To extends Record<PropertyKey, any>,
1009
- C extends Record<PropertyKey, any>,
1010
- OnSubmitA
1011
- >(
1012
- s:
1013
- & S.Codec<To>
1014
- & { new(c: C): any; extend: any; fields: S.Struct.Fields },
1015
- state: Ref<Omit<From, "_tag">>,
1016
- onSubmit: (a: To) => Effect.Effect<OnSubmitA, never, RT>
1017
- ) => {
1018
- const fields = buildFieldInfoFromFieldsRoot(s).fields
1019
- const schema = S.Struct(Struct.omit(s.fields, ["_tag"])) as unknown as S.Codec<any> & {
1020
- readonly DecodingServices: never
1021
- }
1022
- const parse = S.decodeUnknownSync(schema)
1023
- const isDirty = ref(false)
1024
- const isValid = ref(true)
1025
- const isLoading = ref(false)
1026
- const runPromise = Effect.runPromiseWith(this.getRuntime())
1027
-
1028
- const submit1 =
1029
- (onSubmit: (a: To) => Effect.Effect<OnSubmitA, never, never>) =>
1030
- async <T extends Promise<{ valid: boolean }>>(e: T) => {
1031
- isLoading.value = true
1032
- try {
1033
- const r = await e
1034
- if (!r.valid) return
1035
- return await runPromise(onSubmit(new (s as any)(await runPromise(parse(state.value)))) as any)
1036
- } finally {
1037
- isLoading.value = false
1038
- }
1039
- }
1040
- const submit = submit1(onSubmit as any)
1041
-
1042
- watch(
1043
- state,
1044
- (v) => {
1045
- // TODO: do better
1046
- isDirty.value = JSON.stringify(v) !== JSON.stringify(state.value)
1047
- },
1048
- { deep: true }
1049
- )
1050
-
1051
- const submitFromState = Effect.gen(function*() {
1052
- return yield* (onSubmit(yield* parse(state.value)) as any)
1053
- })
1054
-
1055
- const submitFromStatePromise = () => runPromise(submitFromState as any)
1056
-
1057
- return {
1058
- fields,
1059
- /** optimized for Vuetify v-form submit callback */
1060
- submit,
1061
- /** optimized for Native form submit callback or general use */
1062
- submitFromState,
1063
- submitFromStatePromise,
1064
- isDirty,
1065
- isValid,
1066
- isLoading
1067
- }
1068
- }
1069
- }
1070
-
1071
- // @effect-diagnostics-next-line missingEffectServiceDependency:off
1072
- export class LegacyMutation extends ServiceMap.Service<LegacyMutation>()("LegacyMutation", {
1073
- make: Effect.gen(function*() {
1074
- const intl = yield* I18n
1075
- const toast = yield* Toast
1076
-
1077
- return <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => new LegacyMutationImpl(getRuntime, toast, intl)
1078
- })
1079
- }) {
1080
- static readonly DefaultWithoutDependencies = Layer.effect(this, this.make)
1081
- static readonly Default = this.DefaultWithoutDependencies
326
+ )) as any
1082
327
  }
1083
328
 
1084
- export type ClientFrom<M extends Requests> = RequestHandlers<never, never, M, M["meta"]["moduleName"]>
329
+ export type ClientFrom<M extends RequestsAny> = RequestHandlers<never, never, M, ExtractModuleName<M>>
1085
330
 
1086
331
  export class QueryImpl<R> {
1087
- constructor(readonly getRuntime: () => ServiceMap.ServiceMap<R>) {
332
+ readonly getRuntime: () => Context.Context<R>
333
+
334
+ constructor(getRuntime: () => Context.Context<R>) {
335
+ this.getRuntime = getRuntime
1088
336
  this.useQuery = makeQuery(this.getRuntime)
337
+ this.useStreamQuery = makeStreamQuery(this.getRuntime)
1089
338
  }
1090
339
  /**
1091
340
  * Effect results are passed to the caller, including errors.
@@ -1093,6 +342,12 @@ export class QueryImpl<R> {
1093
342
  */
1094
343
  readonly useQuery: ReturnType<typeof makeQuery<R>>
1095
344
 
345
+ /**
346
+ * Stream results are accumulated as an array of chunks and returned as reactive state.
347
+ * @deprecated use client helpers instead (.query())
348
+ */
349
+ readonly useStreamQuery: ReturnType<typeof makeStreamQuery<R>>
350
+
1096
351
  /**
1097
352
  * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1098
353
  * which ensures that either there always is a latest value, or an error occurs on load.
@@ -1104,52 +359,18 @@ export class QueryImpl<R> {
1104
359
  * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1105
360
  * which ensures that either there always is a latest value, or an error occurs on load.
1106
361
  * So that Suspense and error boundaries can be used.
1107
- * @deprecated use client helpers instead (.suspense())
1108
- */
1109
- <
1110
- E,
1111
- A,
1112
- Request extends Req,
1113
- Name extends string
1114
- >(
1115
- self: RequestHandler<A, E, R, Request, Name>
1116
- ): {
1117
- /**
1118
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1119
- * which ensures that either there always is a latest value, or an error occurs on load.
1120
- * So that Suspense and error boundaries can be used.
1121
- */
1122
- <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, E, TData>): Promise<
1123
- readonly [
1124
- ComputedRef<AsyncResult.AsyncResult<TData, E>>,
1125
- ComputedRef<TData>,
1126
- (
1127
- options?: RefetchOptions
1128
- ) => Effect.Effect<QueryObserverResult<TData, E>>,
1129
- UseQueryReturnType<any, any>
1130
- ]
1131
- >
1132
- }
1133
- /**
1134
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1135
- * which ensures that either there always is a latest value, or an error occurs on load.
1136
- * So that Suspense and error boundaries can be used.
362
+ * When `I = void` the input argument may be omitted.
1137
363
  */
1138
364
  <
1139
- Arg,
365
+ I,
1140
366
  E,
1141
367
  A,
1142
368
  Request extends Req,
1143
369
  Name extends string
1144
370
  >(
1145
- self: RequestHandlerWithInput<Arg, A, E, R, Request, Name>
371
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
1146
372
  ): {
1147
- /**
1148
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1149
- * which ensures that either there always is a latest value, or an error occurs on load.
1150
- * So that Suspense and error boundaries can be used.
1151
- */
1152
- <TData = A>(arg: Arg | WatchSource<Arg>, options?: CustomUndefinedInitialQueryOptions<A, E, TData>): Promise<
373
+ <TData = A>(arg: I | WatchSource<I>, options?: CustomUndefinedInitialQueryOptions<A, E, TData>): Promise<
1153
374
  readonly [
1154
375
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
1155
376
  ComputedRef<TData>,
@@ -1160,10 +381,10 @@ export class QueryImpl<R> {
1160
381
  ]
1161
382
  >
1162
383
  }
1163
- } = <Arg, E, A, Request extends Req, Name extends string>(
1164
- self: RequestHandlerWithInput<Arg, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>
384
+ } = <I, E, A, Request extends Req, Name extends string>(
385
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
1165
386
  ) => {
1166
- const runPromise = Effect.runPromiseWith(this.getRuntime())
387
+ const runPromise = makeRunPromise(this.getRuntime())
1167
388
  const q = this.useQuery(self as any) as any
1168
389
  return (argOrOptions?: any, options?: any) => {
1169
390
  const [resultRef, latestRef, fetch, uqrt] = q(argOrOptions, { ...options, suspense: true } // experimental_prefetchInRender: true }
@@ -1188,7 +409,7 @@ export class QueryImpl<R> {
1188
409
  if (!isMounted.value) {
1189
410
  return yield* Effect.interrupt
1190
411
  }
1191
- const result = resultRef.value
412
+ const result = yield* awaitResolvedSuspenseResult(resultRef)
1192
413
  if (AsyncResult.isInitial(result)) {
1193
414
  console.error("Internal Error: Promise should be resolved already", {
1194
415
  self,
@@ -1214,10 +435,64 @@ export class QueryImpl<R> {
1214
435
  }
1215
436
 
1216
437
  // somehow mrt.runtimeEffect doesnt work sync, but this workaround works fine? not sure why though as the layers are generally only sync
1217
- const managedRuntimeRt = <A, E>(mrt: ManagedRuntime.ManagedRuntime<A, E>) => mrt.runSync(Effect.services<A>())
438
+ const managedRuntimeRt = <A, E>(mrt: ManagedRuntime.ManagedRuntime<A, E>) => mrt.runSync(Effect.context<A>())
1218
439
 
1219
440
  type Base = I18n | Toast
1220
- type Mix = ApiClientFactory | Commander | LegacyMutation | Base
441
+ type Mix = ApiClientFactory | Commander | Base
442
+
443
+ type InvalidationResources = Record<string, Record<string, unknown>>
444
+ type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I
445
+ : never
446
+
447
+ type CommandInvalidationResources<Req> = Req extends {
448
+ readonly type: "command"
449
+ readonly "~invalidationResources"?: infer Resources
450
+ } ? NonNullable<Resources> extends InvalidationResources ? NonNullable<Resources> : never
451
+ : Req extends {
452
+ readonly type: "command"
453
+ readonly config?: infer Config
454
+ } ? Config extends {
455
+ readonly invalidationResources?: infer LegacyResources
456
+ } ? NonNullable<LegacyResources> extends InvalidationResources ? NonNullable<LegacyResources> : never
457
+ : Config extends {
458
+ readonly invalidatesQueries?: InvalidationCallback<infer LegacyResources, any, any, any>
459
+ } ? NonNullable<LegacyResources> extends InvalidationResources ? NonNullable<LegacyResources> : never
460
+ : never
461
+ : never
462
+
463
+ type InvalidationResourcesForUnion<M extends RequestsAny> = {
464
+ [K in keyof M]: CommandInvalidationResources<M[K]>
465
+ }[keyof M]
466
+
467
+ type InvalidationResourcesFor<M extends RequestsAny> = [InvalidationResourcesForUnion<M>] extends [never] ? never
468
+ : UnionToIntersection<InvalidationResourcesForUnion<M>> extends infer R ? R extends InvalidationResources ? R
469
+ : never
470
+ : never
471
+
472
+ type QueryInvalidationFactory<M extends RequestsAny> = (client: ClientFrom<M>) => QueryInvalidation<M>
473
+
474
+ type StrictResourcesArg<Shape, Actual extends Shape = Shape> =
475
+ & Actual
476
+ & Record<Exclude<keyof Actual, keyof Shape>, never>
477
+
478
+ type ClientForArgs<
479
+ M extends RequestsAny,
480
+ Resources extends InvalidationResourcesFor<M> = InvalidationResourcesFor<M>
481
+ > = [InvalidationResourcesFor<M>] extends [never] ? [
482
+ queryInvalidation?: QueryInvalidationFactory<M>,
483
+ invalidationResources?: StrictResourcesArg<
484
+ InvalidationResourcesFor<M>,
485
+ Resources
486
+ >
487
+ ]
488
+ : [
489
+ queryInvalidation: QueryInvalidationFactory<M> | undefined,
490
+ invalidationResources: StrictResourcesArg<
491
+ InvalidationResourcesFor<M>,
492
+ Resources
493
+ >
494
+ ]
495
+
1221
496
  export const makeClient = <RT_, RTHooks>(
1222
497
  // global, but only accessible after startup has completed
1223
498
  getBaseMrt: () => ManagedRuntime.ManagedRuntime<RT_ | Mix, never>,
@@ -1225,126 +500,190 @@ export const makeClient = <RT_, RTHooks>(
1225
500
  rtHooks: Layer.Layer<RTHooks, never, Mix>
1226
501
  ) => {
1227
502
  type RT = RT_ | Mix
1228
- const getRt = Effect.services<RT>()
1229
503
  const getBaseRt = () => managedRuntimeRt(getBaseMrt())
1230
504
  const makeCommand = makeUseCommand<RT, RTHooks>(rtHooks)
1231
- const makeMutation = Effect.gen(function*() {
1232
- const mut = yield* LegacyMutation
1233
-
1234
- return mut(() => getBaseMrt().runSync(getRt))
1235
- })
1236
505
  let cmd: Effect.Success<typeof makeCommand>
1237
506
  const useCommand = () => cmd ??= getBaseMrt().runSync(makeCommand)
1238
- let mut: Effect.Success<typeof makeMutation>
1239
- const getMutation = () => mut ??= getBaseMrt().runSync(makeMutation)
1240
507
 
1241
508
  let m: ReturnType<typeof useMutationInt>
1242
509
  const useMutation = () => m ??= useMutationInt()
1243
510
 
1244
- const keys = [
1245
- "useSafeMutationWithState",
1246
- "useAndHandleMutation",
1247
- "useAndHandleMutationResult",
1248
- "useAndHandleMutationSilently",
1249
- "useAndHandleMutationCustom",
1250
- "makeUseAndHandleMutation",
1251
- "useHandleRequestWithToast",
1252
- "buildFormFromSchema",
1253
- "useSafeMutation"
1254
- ] as const satisfies readonly (keyof ReturnType<typeof getMutation>)[]
1255
- type mut = Pick<LegacyMutationImpl<RT>, typeof keys[number]>
1256
-
1257
- const mutations = keys.reduce(
1258
- (prev, cur) => {
1259
- ;(prev as any)[cur] = ((...args: [any]) => {
1260
- return (getMutation() as any)[cur](...args)
1261
- }) as any
1262
- return prev
1263
- },
1264
- {} as Pick<LegacyMutationImpl<RT>, typeof keys[number]>
1265
- )
511
+ let sm2: ReturnType<typeof makeStreamMutation2>
512
+ const useStreamMutation2 = () => sm2 ??= makeStreamMutation2()
1266
513
 
1267
514
  const query = new QueryImpl(getBaseRt)
1268
515
  const useQuery = query.useQuery
1269
516
  const useSuspenseQuery = query.useSuspenseQuery
517
+ const useStreamQuery = query.useStreamQuery
518
+
519
+ const mergeInvalidation = (
520
+ a?: MutationOptionsBase["queryInvalidation"],
521
+ b?: MutationOptionsBase["queryInvalidation"]
522
+ ): MutationOptionsBase["queryInvalidation"] | undefined => {
523
+ if (!a && !b) {
524
+ return undefined
525
+ }
526
+ return (defaultKey, name, input, output) => [
527
+ ...(a?.(defaultKey, name, input, output) ?? []),
528
+ ...(b?.(defaultKey, name, input, output) ?? [])
529
+ ]
530
+ }
531
+
532
+ const withDefaultInvalidation = (
533
+ mut: any,
534
+ defaultInvalidation?: MutationOptionsBase["queryInvalidation"]
535
+ ) => {
536
+ if (!defaultInvalidation) return mut
537
+ const apply = (callerOpts?: MutationOptionsBase) => ({
538
+ ...callerOpts,
539
+ queryInvalidation: callerOpts?.queryInvalidation
540
+ ? mergeInvalidation(defaultInvalidation, callerOpts.queryInvalidation)
541
+ : defaultInvalidation
542
+ })
543
+ return (input: any, callerOpts?: MutationOptionsBase) => mut(input, apply(callerOpts))
544
+ }
1270
545
 
1271
- const mapQuery = <M extends Requests>(
546
+ const makeQueryResources = <Resources extends InvalidationResources>(resources: Resources | undefined) => {
547
+ if (!resources) {
548
+ return {} as Record<string, Record<string, unknown>>
549
+ }
550
+ return resources as Record<string, Record<string, unknown>>
551
+ }
552
+
553
+ const mapQuery = <M extends RequestsAny>(
1272
554
  client: ClientFrom<M>
1273
555
  ) => {
1274
556
  const queries = Struct.keys(client).reduce(
1275
557
  (acc, key) => {
1276
- ;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(client[key] as any), {
1277
- id: client[key].id
1278
- })
1279
- ;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(client[key] as any), {
1280
- id: client[key].id
1281
- })
558
+ const requestType = client[key].Request.type
559
+ const isStream = client[key].Request.stream
560
+ if (requestType === "query" && !isStream) {
561
+ ;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(client[key] as any), {
562
+ id: client[key].id
563
+ })
564
+ ;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(client[key] as any), {
565
+ id: client[key].id
566
+ })
567
+ } else if (requestType === "query" && isStream) {
568
+ ;(acc as any)[camelCase(key) + "Query"] = Object.assign(useStreamQuery(client[key] as any), {
569
+ id: client[key].id
570
+ })
571
+ }
1282
572
  return acc
1283
573
  },
1284
574
  {} as
1285
575
  & {
1286
576
  // apparently can't get JSDoc in here..
1287
- [Key in keyof typeof client as `${ToCamel<string & Key>}Query`]: Queries<RT, typeof client[Key]>["query"]
577
+ [
578
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
579
+ : `${ToCamel<string & Key>}Query`
580
+ ]: Queries<RT, QueryHandler<typeof client[Key]>>["query"]
1288
581
  }
1289
582
  // todo: or suspense as an Option?
1290
583
  & {
1291
584
  // apparently can't get JSDoc in here..
1292
- [Key in keyof typeof client as `${ToCamel<string & Key>}SuspenseQuery`]: Queries<
585
+ [
586
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
587
+ : `${ToCamel<string & Key>}SuspenseQuery`
588
+ ]: Queries<
1293
589
  RT,
1294
- typeof client[Key]
590
+ QueryHandler<typeof client[Key]>
1295
591
  >["suspense"]
1296
592
  }
593
+ & {
594
+ [
595
+ Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
596
+ : `${ToCamel<string & Key>}Query`
597
+ ]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["query"]
598
+ }
1297
599
  )
1298
600
  return queries
1299
601
  }
1300
602
 
1301
- const mapRequest = <M extends Requests>(
603
+ const mapRequest = <M extends RequestsAny>(
1302
604
  client: ClientFrom<M>
1303
605
  ) => {
1304
606
  const Command = useCommand()
1305
607
  const mutations = Struct.keys(client).reduce(
1306
608
  (acc, key) => {
609
+ if (!(client[key].Request.type === "command" && !client[key].Request.stream)) {
610
+ return acc
611
+ }
1307
612
  const mut = client[key].handler
1308
- const fn = Command.fn(client[key].id)
1309
- const wrap = Command.wrap({ mutate: Effect.isEffect(mut) ? () => mut : mut, id: client[key].id })
613
+ const request = mut
614
+ const fn = (options?: any) => Command.fn(client[key].id, options)
615
+ const wrap = (options?: any) => Command.wrap({ mutate: request, id: client[key].id }, options)
1310
616
  ;(acc as any)[camelCase(key) + "Request"] = Object.assign(
1311
617
  mut,
1312
- fn, // to get the i18n key etc.
1313
- { wrap, fn }
618
+ Command.fn(client[key].id), // to get the i18n key etc.
619
+ { wrap, fn, request }
1314
620
  )
1315
621
  return acc
1316
622
  },
1317
623
  {} as {
1318
- [Key in keyof typeof client as `${ToCamel<string & Key>}Request`]: RequestWithExtensions<
624
+ [
625
+ Key in keyof typeof client as CommandHandler<typeof client[Key]> extends never ? never
626
+ : `${ToCamel<string & Key>}Request`
627
+ ]: CommandRequestWithExtensions<
1319
628
  RT | RTHooks,
1320
- typeof client[Key]
629
+ CommandHandler<typeof client[Key]>
1321
630
  >
1322
631
  }
1323
632
  )
1324
633
  return mutations
1325
634
  }
1326
635
 
1327
- const mapMutation = <M extends Requests>(
1328
- client: ClientFrom<M>
636
+ const mapMutation = <M extends RequestsAny>(
637
+ client: ClientFrom<M>,
638
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
639
+ invalidationResources?: InvalidationResourcesFor<M>
1329
640
  ) => {
1330
641
  const Command = useCommand()
1331
642
  const mutation = useMutation()
643
+ const invalidation = queryInvalidation?.(client)
644
+ const queryResources = makeQueryResources(invalidationResources)
1332
645
  const mutations = Struct.keys(client).reduce(
1333
646
  (acc, key) => {
1334
- const mut: any = mutation(client[key] as any)
1335
- const fn = Command.fn(client[key].id)
1336
- const wrap = Command.wrap({ mutate: Effect.isEffect(mut) ? () => mut : mut, id: client[key].id })
1337
- ;(acc as any)[camelCase(key) + "Mutation"] = Object.assign(
1338
- mut,
1339
- fn, // to get the i18n key etc.
1340
- { wrap, fn }
1341
- )
647
+ if (!(client[key].Request.type === "command" && !client[key].Request.stream)) {
648
+ return acc
649
+ }
650
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
651
+ | InvalidationCallback<InvalidationResourcesFor<M>>
652
+ | undefined
653
+ const fromRequest = fromRequestConfig
654
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
655
+ fromRequestConfig(
656
+ defaultKey,
657
+ queryResources as never,
658
+ input as never,
659
+ output as never
660
+ ) as InvalidationEntry[])
661
+ : undefined
662
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
663
+ const makeProjectedMutation = (handler: any): any => {
664
+ const mut: any = withDefaultInvalidation(mutation(handler), mergedInvalidation)
665
+ return Object.assign(mut, {
666
+ wrap: (options?: any) => Command.wrap({ mutate: mut, id: client[key].id }, options),
667
+ fn: (options?: any) => Command.fn(client[key].id, options),
668
+ project: (projectionSchema: any) => {
669
+ const projected = {
670
+ ...handler,
671
+ handler: projectHandler(handler.handler, client[key].Request.success, projectionSchema)
672
+ }
673
+ return makeProjectedMutation(projected)
674
+ }
675
+ })
676
+ }
677
+ ;(acc as any)[camelCase(key) + "Mutation"] = makeProjectedMutation(client[key] as any)
1342
678
  return acc
1343
679
  },
1344
680
  {} as {
1345
- [Key in keyof typeof client as `${ToCamel<string & Key>}Mutation`]: MutationWithExtensions<
681
+ [
682
+ Key in keyof typeof client as CommandHandler<typeof client[Key]> extends never ? never
683
+ : `${ToCamel<string & Key>}Mutation`
684
+ ]: MutationWithExtensions<
1346
685
  RT | RTHooks,
1347
- typeof client[Key]
686
+ CommandHandler<typeof client[Key]>
1348
687
  >
1349
688
  }
1350
689
  )
@@ -1353,8 +692,9 @@ export const makeClient = <RT_, RTHooks>(
1353
692
 
1354
693
  // make available .query, .suspense and .mutate for each operation
1355
694
  // and a .helpers with all mutations and queries
1356
- const mapClient = <M extends Requests>(
1357
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
695
+ const mapClient = <M extends RequestsAny>(
696
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
697
+ invalidationResources?: InvalidationResourcesFor<M>
1358
698
  ) =>
1359
699
  (
1360
700
  client: ClientFrom<M>
@@ -1362,71 +702,183 @@ export const makeClient = <RT_, RTHooks>(
1362
702
  const Command = useCommand()
1363
703
  const mutation = useMutation()
1364
704
  const invalidation = queryInvalidation?.(client)
705
+ const queryResources = makeQueryResources(invalidationResources)
1365
706
  const extended = Struct.keys(client).reduce(
1366
707
  (acc, key) => {
708
+ const requestType = client[key].Request.type
709
+ const isStream = client[key].Request.stream
1367
710
  const fn = Command.fn(client[key].id)
1368
- const mutate = extendM(
1369
- mutation(
1370
- client[key] as any,
1371
- invalidation?.[key] ? { queryInvalidation: invalidation[key] } : undefined
1372
- ),
1373
- (mutate) =>
1374
- Object.assign(
1375
- mutate,
1376
- fn, // to get the i18n key etc.
1377
- {
1378
- wrap: Command.wrap({ mutate: Effect.isEffect(mutate) ? () => mutate : mutate, id: client[key].id }),
1379
- fn
1380
- }
1381
- )
1382
- )
1383
-
1384
711
  const h_ = client[key].handler
1385
- const h = Effect.isEffect(h_)
1386
- ? () => h_
1387
- : (...args: [any]) => h_(...args)
712
+ const request = h_
1388
713
  ;(acc as any)[key] = Object.assign(
1389
- h,
1390
- client[key],
1391
- fn, // to get the i18n key etc.
1392
- {
1393
- mutate,
1394
- query: useQuery(client[key] as any),
1395
- suspense: useSuspenseQuery(client[key] as any),
1396
- wrap: Command.wrap({ mutate: h, id: client[key].id }),
1397
- fn
1398
- }
714
+ requestType === "query" && !isStream
715
+ ? {
716
+ ...client[key],
717
+ request,
718
+ query: useQuery(client[key] as any),
719
+ suspense: useSuspenseQuery(client[key] as any),
720
+ project: (projectionSchema: any) => {
721
+ const successSchema = client[key].Request.success
722
+ const projectionHash = projectionSchemaHash(projectionSchema)
723
+ const projected = projectHandler(h_ as any, successSchema, projectionSchema)
724
+ const fakeHandler = {
725
+ handler: projected,
726
+ id: client[key].id,
727
+ Request: client[key].Request,
728
+ options: client[key].options,
729
+ queryKeyProjectionHash: projectionHash
730
+ }
731
+ return {
732
+ request: projected,
733
+ query: useQuery(fakeHandler as any),
734
+ suspense: useSuspenseQuery(fakeHandler as any)
735
+ }
736
+ }
737
+ }
738
+ : requestType === "query" && isStream
739
+ ? {
740
+ ...client[key],
741
+ request,
742
+ query: useStreamQuery(client[key] as any)
743
+ }
744
+ : requestType === "command" && isStream
745
+ ? (() => {
746
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
747
+ | InvalidationCallback<InvalidationResourcesFor<M>>
748
+ | undefined
749
+ const fromRequest = fromRequestConfig
750
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
751
+ fromRequestConfig(
752
+ defaultKey,
753
+ queryResources as never,
754
+ input as never,
755
+ output as never
756
+ ) as InvalidationEntry[])
757
+ : undefined
758
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
759
+ const streamCmd = useCommand()
760
+ return {
761
+ ...client[key],
762
+ request,
763
+ query: useStreamQuery(client[key] as any),
764
+ fn: (options?: any) => streamCmd.streamFn(client[key].id as any, options),
765
+ mutate: (() => {
766
+ const sm2Act = useStreamMutation2()(client[key] as any, mergedInvalidation)
767
+ const sm2Handler = (input: any, _ctx: any) => (sm2Act as (i: any) => any)(input)
768
+ return Object.assign(sm2Act, {
769
+ id: client[key].id,
770
+ wrap: (options?: any) => streamCmd.streamWrap(sm2Handler, client[key].id as any, options)
771
+ })
772
+ })()
773
+ }
774
+ })()
775
+ : {
776
+ mutate: ((handler: any) => {
777
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
778
+ | InvalidationCallback<InvalidationResourcesFor<M>>
779
+ | undefined
780
+ const fromRequest = fromRequestConfig
781
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
782
+ fromRequestConfig(
783
+ defaultKey,
784
+ queryResources as never,
785
+ input as never,
786
+ output as never
787
+ ) as InvalidationEntry[])
788
+ : undefined
789
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
790
+ const makeProjectedMutation = (h: any): any => {
791
+ const mutate = withDefaultInvalidation(mutation(h), mergedInvalidation)
792
+ return Object.assign(
793
+ mutate,
794
+ {
795
+ wrap: (options?: any) =>
796
+ Command.wrap({
797
+ mutate,
798
+ id: client[key].id
799
+ }, options),
800
+ fn: (options?: any) => Command.fn(client[key].id, options),
801
+ project: (projectionSchema: any) => {
802
+ const projected = {
803
+ ...h,
804
+ handler: projectHandler(h.handler, client[key].Request.success, projectionSchema)
805
+ }
806
+ return makeProjectedMutation(projected)
807
+ }
808
+ }
809
+ )
810
+ }
811
+ return makeProjectedMutation(handler)
812
+ })(client[key] as any),
813
+ ...client[key],
814
+ ...fn, // to get the i18n key etc.
815
+ request,
816
+ fn: (options?: any) => Command.fn(client[key].id, options),
817
+ wrap: (options?: any) => Command.wrap({ mutate: h_, id: client[key].id }, options)
818
+ }
1399
819
  )
1400
820
  return acc
1401
821
  },
1402
822
  {} as {
1403
823
  [Key in keyof typeof client]:
1404
824
  & typeof client[Key]
1405
- & RequestWithExtensions<RT | RTHooks, typeof client[Key]>
1406
- & { mutate: MutationWithExtensions<RT | RTHooks, typeof client[Key]> }
1407
- & Queries<RT, typeof client[Key]>
825
+ & (QueryHandler<typeof client[Key]> extends never ? {}
826
+ :
827
+ & QueryRequestWithExtensions<QueryHandler<typeof client[Key]>>
828
+ & Queries<RT, QueryHandler<typeof client[Key]>>
829
+ & QueryProjection<RT, QueryHandler<typeof client[Key]>>)
830
+ & (QueryStreamHandler<typeof client[Key]> extends never ? {}
831
+ : StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>)
832
+ & (CommandHandler<typeof client[Key]> extends never ? {}
833
+ : CommandRequestWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>>)
834
+ & (CommandHandler<typeof client[Key]> extends never ? {}
835
+ : { mutate: MutationWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>> })
836
+ & (CommandStreamHandler<typeof client[Key]> extends never ? {}
837
+ : {
838
+ fn: StreamFnStreamExtension<RT | RTHooks, CommandStreamHandler<typeof client[Key]>>
839
+ mutate: StreamMutation2WithExtensions<RT | RTHooks, CommandStreamHandler<typeof client[Key]>>
840
+ })
841
+ & {
842
+ Input: typeof client[Key] extends RequestStreamHandlerWithInput<infer I, any, any, any, any, any, any> ? I
843
+ : typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I
844
+ : never
845
+ }
1408
846
  }
1409
847
  )
1410
- return Object.assign(extended, { helpers: { ...mapRequest(client), ...mapMutation(client), ...mapQuery(client) } })
848
+ return Object.assign(extended, {
849
+ helpers: {
850
+ ...mapRequest(client),
851
+ ...mapMutation(client, queryInvalidation, invalidationResources),
852
+ ...mapQuery(client)
853
+ }
854
+ })
1411
855
  }
1412
856
 
1413
857
  // TODO: Clean up this delay initialisation messs
1414
858
  // TODO; invalidateQueries should perhaps be configured in the Request impl themselves?
1415
- const clientFor__ = <M extends Requests>(
859
+ const clientFor__ = <M extends RequestsAny>(
1416
860
  m: M,
1417
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
1418
- ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation))))
861
+ queryInvalidation?: QueryInvalidationFactory<M>,
862
+ invalidationResources?: InvalidationResourcesFor<M>
863
+ ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation, invalidationResources))))
1419
864
 
1420
865
  // delay client creation until first access
1421
866
  // the idea is that we don't need the useNuxtApp().$runtime (only available at later initialisation stage)
1422
867
  // until we are at a place where it is available..
1423
- const clientFor = <M extends Requests>(
868
+ const clientFor = <
869
+ M extends RequestsAny,
870
+ Resources extends InvalidationResourcesFor<M> = InvalidationResourcesFor<M>
871
+ >(
1424
872
  m: M,
1425
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
873
+ ...args: ClientForArgs<M, Resources>
1426
874
  ) => {
875
+ const [queryInvalidation, invalidationResources] = args as [
876
+ QueryInvalidationFactory<M> | undefined,
877
+ InvalidationResourcesFor<M> | undefined
878
+ ]
1427
879
  type Client = ReturnType<typeof clientFor__<M>>
1428
880
  let client: Client | undefined = undefined
1429
- const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation))
881
+ const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation, invalidationResources))
1430
882
 
1431
883
  // initialize on first use..
1432
884
  const proxy = Struct.keys(m).concat(["helpers"]).reduce((acc, key) => {
@@ -1444,16 +896,12 @@ export const makeClient = <RT_, RTHooks>(
1444
896
  return proxy
1445
897
  }
1446
898
 
1447
- const legacy: Legacy<RT> = {
1448
- ...mutations,
1449
- ...query
1450
- }
1451
-
1452
899
  const Command: CommanderResolved<RT, RTHooks> = {
1453
900
  ...{
1454
901
  // delay initialisation until first use...
1455
902
  fn: (...args: [any]) => useCommand().fn(...args),
1456
903
  wrap: (...args: [any]) => useCommand().wrap(...args),
904
+ streamFn: (...args: [any]) => useCommand().streamFn(...args),
1457
905
  alt: (...args: [any]) => useCommand().alt(...args),
1458
906
  alt2: (...args: [any]) => useCommand().alt2(...args)
1459
907
  } as ReturnType<typeof useCommand>,
@@ -1463,22 +911,17 @@ export const makeClient = <RT_, RTHooks>(
1463
911
  return {
1464
912
  Command,
1465
913
  useCommand,
1466
- clientFor,
1467
- legacy
914
+ clientFor
1468
915
  }
1469
916
  }
1470
917
 
1471
- export interface Legacy<R>
1472
- extends
1473
- Pick<QueryImpl<R>, "useQuery" | "useSuspenseQuery">,
1474
- Omit<LegacyMutationImpl<R>, "getRuntime" | "toast" | "intl">
1475
- {}
1476
-
1477
918
  export type QueryInvalidation<M> = {
1478
- [K in keyof M]?: (defaultKey: string[], name: string) => {
1479
- filters?: InvalidateQueryFilters | undefined
1480
- options?: InvalidateOptions | undefined
1481
- }[]
919
+ [K in keyof M]?: (
920
+ defaultKey: string[],
921
+ name: string,
922
+ input?: unknown,
923
+ output?: Exit.Exit<unknown, unknown>
924
+ ) => InvalidationEntry[]
1482
925
  }
1483
926
 
1484
927
  export type ToCamel<S extends string | number | symbol> = S extends string
@@ -1486,17 +929,21 @@ export type ToCamel<S extends string | number | symbol> = S extends string
1486
929
  : Uncapitalize<S>
1487
930
  : never
1488
931
 
1489
- export interface CommandBase<I = void, A = void> {
932
+ export interface CommandBase<I = void, A = void, RA = unknown, RE = unknown> {
1490
933
  handle: (input: I) => A
1491
934
  waiting: boolean
1492
935
  blocked: boolean
1493
936
  allowed: boolean
1494
937
  action: string
1495
938
  label: string
939
+ /** formatted progress info for current `running` state, when `progress` was supplied */
940
+ progress?: Progress | undefined
941
+ /** reactive result state, available on stream-backed commands */
942
+ result?: AsyncResult.AsyncResult<RA, RE>
1496
943
  }
1497
944
 
1498
- export interface EffectCommand<I = void, A = unknown, E = unknown> extends CommandBase<I, Fiber<A, E>> {}
945
+ export interface EffectCommand<I = void, A = unknown, E = unknown> extends CommandBase<I, Fiber<A, E>, A, E> {}
1499
946
 
1500
- export interface CommandFromRequest<I extends abstract new(...args: any) => any, A = unknown, E = unknown>
1501
- extends EffectCommand<ConstructorParameters<I>[0], A, E>
947
+ export interface CommandFromRequest<I extends { readonly make: (...args: any[]) => any }, A = unknown, E = unknown>
948
+ extends EffectCommand<HandlerInput<I>, A, E>
1502
949
  {}