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

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 (100) hide show
  1. package/CHANGELOG.md +1275 -0
  2. package/dist/commander.d.ts +450 -0
  3. package/dist/commander.d.ts.map +1 -0
  4. package/dist/commander.js +687 -0
  5. package/dist/confirm.d.ts +19 -0
  6. package/dist/confirm.d.ts.map +1 -0
  7. package/dist/confirm.js +24 -0
  8. package/dist/errorReporter.d.ts +4 -4
  9. package/dist/errorReporter.d.ts.map +1 -1
  10. package/dist/errorReporter.js +12 -18
  11. package/dist/form.d.ts +13 -4
  12. package/dist/form.d.ts.map +1 -1
  13. package/dist/form.js +41 -12
  14. package/dist/index.d.ts +1 -1
  15. package/dist/intl.d.ts +15 -0
  16. package/dist/intl.d.ts.map +1 -0
  17. package/dist/intl.js +9 -0
  18. package/dist/lib.d.ts +6 -8
  19. package/dist/lib.d.ts.map +1 -1
  20. package/dist/lib.js +34 -7
  21. package/dist/makeClient.d.ts +191 -290
  22. package/dist/makeClient.d.ts.map +1 -1
  23. package/dist/makeClient.js +231 -361
  24. package/dist/makeContext.d.ts +1 -1
  25. package/dist/makeContext.d.ts.map +1 -1
  26. package/dist/makeIntl.d.ts +1 -1
  27. package/dist/makeIntl.d.ts.map +1 -1
  28. package/dist/makeUseCommand.d.ts +8 -0
  29. package/dist/makeUseCommand.d.ts.map +1 -0
  30. package/dist/makeUseCommand.js +13 -0
  31. package/dist/mutate.d.ts +57 -25
  32. package/dist/mutate.d.ts.map +1 -1
  33. package/dist/mutate.js +160 -33
  34. package/dist/query.d.ts +11 -15
  35. package/dist/query.d.ts.map +1 -1
  36. package/dist/query.js +19 -27
  37. package/dist/routeParams.d.ts +1 -1
  38. package/dist/runtime.d.ts +5 -2
  39. package/dist/runtime.d.ts.map +1 -1
  40. package/dist/runtime.js +27 -17
  41. package/dist/toast.d.ts +46 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/toast.js +32 -0
  44. package/dist/withToast.d.ts +26 -0
  45. package/dist/withToast.d.ts.map +1 -0
  46. package/dist/withToast.js +49 -0
  47. package/eslint.config.mjs +2 -2
  48. package/examples/streamMutation.ts +85 -0
  49. package/package.json +48 -48
  50. package/src/{experimental/commander.ts → commander.ts} +1158 -275
  51. package/src/{experimental/confirm.ts → confirm.ts} +10 -14
  52. package/src/errorReporter.ts +62 -74
  53. package/src/form.ts +55 -16
  54. package/src/intl.ts +12 -0
  55. package/src/lib.ts +46 -13
  56. package/src/makeClient.ts +670 -1038
  57. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +4 -4
  58. package/src/mutate.ts +306 -72
  59. package/src/query.ts +39 -50
  60. package/src/runtime.ts +39 -18
  61. package/src/{experimental/toast.ts → toast.ts} +11 -25
  62. package/src/{experimental/withToast.ts → withToast.ts} +15 -6
  63. package/test/Mutation.test.ts +130 -10
  64. package/test/dist/form.test.d.ts.map +1 -1
  65. package/test/dist/lib.test.d.ts.map +1 -0
  66. package/test/dist/streamFinal.test.d.ts.map +1 -0
  67. package/test/dist/stubs.d.ts +3220 -117
  68. package/test/dist/stubs.d.ts.map +1 -1
  69. package/test/dist/stubs.js +132 -25
  70. package/test/form-validation-errors.test.ts +23 -19
  71. package/test/form.test.ts +20 -2
  72. package/test/lib.test.ts +240 -0
  73. package/test/makeClient.test.ts +240 -38
  74. package/test/streamFinal.test.ts +110 -0
  75. package/test/stubs.ts +172 -42
  76. package/tsconfig.examples.json +20 -0
  77. package/tsconfig.json +0 -1
  78. package/tsconfig.json.bak +5 -2
  79. package/tsconfig.src.json +34 -34
  80. package/tsconfig.test.json +2 -2
  81. package/vitest.config.ts +5 -5
  82. package/dist/experimental/commander.d.ts +0 -359
  83. package/dist/experimental/commander.d.ts.map +0 -1
  84. package/dist/experimental/commander.js +0 -557
  85. package/dist/experimental/confirm.d.ts +0 -19
  86. package/dist/experimental/confirm.d.ts.map +0 -1
  87. package/dist/experimental/confirm.js +0 -28
  88. package/dist/experimental/intl.d.ts +0 -16
  89. package/dist/experimental/intl.d.ts.map +0 -1
  90. package/dist/experimental/intl.js +0 -5
  91. package/dist/experimental/makeUseCommand.d.ts +0 -8
  92. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  93. package/dist/experimental/makeUseCommand.js +0 -13
  94. package/dist/experimental/toast.d.ts +0 -47
  95. package/dist/experimental/toast.d.ts.map +0 -1
  96. package/dist/experimental/toast.js +0 -41
  97. package/dist/experimental/withToast.d.ts +0 -25
  98. package/dist/experimental/withToast.d.ts.map +0 -1
  99. package/dist/experimental/withToast.js +0 -45
  100. package/src/experimental/intl.ts +0 -9
package/src/makeClient.ts CHANGED
@@ -1,32 +1,45 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { type InvalidateOptions, type InvalidateQueryFilters, 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"
4
+ import { type Context, Effect, Exit, Hash, type Layer, type ManagedRuntime, S, Struct } from "effect-app"
5
5
  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"
6
+ import type { ExtractModuleName, RequestHandler, RequestHandlers, RequestHandlerWithInput, RequestInputFromMake, RequestsAny, RequestStreamHandler, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
7
+ import type { InvalidationCallback } from "effect-app/client/makeClient"
8
+ import type * as ExitResult from "effect/Exit"
11
9
  import { type Fiber } from "effect/Fiber"
12
10
  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"
11
+ import { computed, type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
12
+ import { type Commander, CommanderStatic, type Progress } from "./commander.js"
13
+ import { type I18n } from "./intl.js"
14
+ import { type CommanderResolved, makeUseCommand } from "./makeUseCommand.js"
15
+ import { makeMutation, makeStreamMutation, type MutationOptionsBase, useMakeMutation } from "./mutate.js"
22
16
  import { type CustomUndefinedInitialQueryOptions, makeQuery } from "./query.js"
17
+ import { makeRunPromise } from "./runtime.js"
18
+ import { type Toast } from "./toast.js"
23
19
 
24
20
  const mapHandler = <A, E, R, I = void, A2 = A, E2 = E, R2 = R>(
25
21
  handler: Effect.Effect<A, E, R> | ((i: I) => Effect.Effect<A, E, R>),
26
22
  map: (self: Effect.Effect<A, E, R>, i: I) => Effect.Effect<A2, E2, R2>
27
23
  ) => Effect.isEffect(handler) ? map(handler, undefined as any) : (i: I) => map(handler(i), i)
28
24
 
29
- export interface RequestExtensions<RT, Id extends string, I, A, E, R> {
25
+ // TODO: optimize - work from encoded shape directly
26
+ const projectHandler = (
27
+ handler: Effect.Effect<any, any, any> | ((i: any) => Effect.Effect<any, any, any>),
28
+ successSchema: S.Top,
29
+ projectionSchema: S.Top
30
+ ) => {
31
+ const encode = S.encodeEffect(successSchema)
32
+ const decode = S.decodeEffectConcurrently(projectionSchema)
33
+ return mapHandler(handler, (self) =>
34
+ self.pipe(
35
+ Effect.flatMap(encode),
36
+ Effect.flatMap(decode)
37
+ ))
38
+ }
39
+
40
+ const projectionSchemaHash = (schema: S.Top) => String(Hash.hash(schema.ast))
41
+
42
+ export interface CommandRequestExtensions<RT, Id extends string, I, A, E, R> {
30
43
  /** Defines a Command based on this call, taking the `id` of the call as the `id` of the Command.
31
44
  * The Request function will be taken as the first member of the Command, the Command required input will be the Request input.
32
45
  * see Command.wrap for details */
@@ -44,16 +57,14 @@ export interface RequestExtWithInput<
44
57
  A,
45
58
  E,
46
59
  R
47
- > extends Commander.CommandContextLocal<Id, Id>, RequestExtensions<RT, Id, I, A, E, R> {
60
+ > extends Commander.CommandContextLocal<Id, Id>, CommandRequestExtensions<RT, Id, I, A, E, R> {
48
61
  /**
49
- * Request the endpoint with input
62
+ * Send the request to the endpoint and return the raw Effect response.
63
+ * This does not perform query cache invalidation.
50
64
  */
51
- (i: I): Effect.Effect<A, E, R>
65
+ request: (i: I) => Effect.Effect<A, E, R>
52
66
  }
53
67
 
54
- /**
55
- * Request the endpoint
56
- */
57
68
  export interface RequestExt<
58
69
  RT,
59
70
  Id extends string,
@@ -63,25 +74,69 @@ export interface RequestExt<
63
74
  > extends
64
75
  Commander.CommandContextLocal<Id, Id>,
65
76
  Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>,
66
- RequestExtensions<RT, Id, void, A, E, R>,
67
- Effect.Effect<A, E, R>
77
+ CommandRequestExtensions<RT, Id, void, A, E, R>
68
78
  {
79
+ /**
80
+ * Send the request to the endpoint and return the raw Effect response.
81
+ * This does not perform query cache invalidation.
82
+ */
83
+ request: Effect.Effect<A, E, R>
69
84
  }
70
85
 
71
- export type RequestWithExtensions<RT, Req> = Req extends
86
+ export type CommandRequestWithExtensions<RT, Req> = Req extends
72
87
  RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id>
73
88
  ? RequestExtWithInput<RT, Id, I, A, E, R>
74
89
  : Req extends RequestHandler<infer A, infer E, infer R, infer _Request, infer Id> ? RequestExt<RT, Id, A, E, R>
75
90
  : never
76
91
 
92
+ export interface QueryExtensionsWithInput<I, A, E, R> {
93
+ /**
94
+ * Send the request to the endpoint and return the raw Effect response.
95
+ * This does not set up query state tracking.
96
+ */
97
+ request: (i: I) => Effect.Effect<A, E, R>
98
+ }
99
+
100
+ export interface QueryExtensions<A, E, R> {
101
+ /**
102
+ * Send the request to the endpoint and return the raw Effect response.
103
+ * This does not set up query state tracking.
104
+ */
105
+ request: Effect.Effect<A, E, R>
106
+ }
107
+
108
+ export type QueryRequestWithExtensions<Req> = Req extends
109
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer _Id>
110
+ ? QueryExtensionsWithInput<I, A, E, R>
111
+ : Req extends RequestHandler<infer A, infer E, infer R, infer _Request, infer _Id> ? QueryExtensions<A, E, R>
112
+ : never
113
+
114
+ type QueryHandler<Req> = Req extends
115
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
116
+ ? Request["type"] extends "query" ? RequestHandlerWithInput<I, A, E, R, Request, Id> : never
117
+ : Req extends RequestHandler<infer A, infer E, infer R, infer Request, infer Id>
118
+ ? Request["type"] extends "query" ? RequestHandler<A, E, R, Request, Id> : never
119
+ : never
120
+
121
+ type CommandHandler<Req> = Req extends
122
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
123
+ ? Request["type"] extends "command" ? RequestHandlerWithInput<I, A, E, R, Request, Id> : never
124
+ : Req extends RequestHandler<infer A, infer E, infer R, infer Request, infer Id>
125
+ ? Request["type"] extends "command" ? RequestHandler<A, E, R, Request, Id> : never
126
+ : never
127
+
128
+ type StreamHandler<Req> = Req extends
129
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer Final>
130
+ ? Request["type"] extends "stream" ? RequestStreamHandlerWithInput<I, A, E, R, Request, Id, Final> : never
131
+ : Req extends RequestStreamHandler<infer A, infer E, infer R, infer Request, infer Id, infer Final>
132
+ ? Request["type"] extends "stream" ? RequestStreamHandler<A, E, R, Request, Id, Final> : never
133
+ : never
134
+
77
135
  export interface MutationExtensions<RT, Id extends string, I, A, E, R> {
78
136
  /** Defines a Command based on this mutation, taking the `id` of the mutation as the `id` of the Command.
79
137
  * The Mutation function will be taken as the first member of the Command, the Command required input will be the Mutation input.
80
138
  * see Command.wrap for details */
81
139
  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.
83
- * see Command.fn for details */
84
- fn: Commander.CommanderFn<RT, Id, Id, undefined>
85
140
  }
86
141
 
87
142
  /** my other doc */
@@ -91,39 +146,147 @@ export interface MutationExtWithInput<
91
146
  I,
92
147
  A,
93
148
  E,
94
- R
95
- > extends Commander.CommandContextLocal<Id, Id>, MutationExtensions<RT, Id, I, A, E, R> {
149
+ R,
150
+ EA = unknown
151
+ > extends MutationExtensions<RT, Id, I, A, E, R> {
96
152
  /**
97
- * Call the endpoint with input
98
- * Invalidate queries based on namespace of this mutation.
99
- * Do not use for queries.
153
+ * Send the request to the endpoint and return the raw Effect response.
154
+ * Also invalidates query caches using the request namespace by default.
155
+ * Namespace invalidation targets parent namespace keys
156
+ * (for example `$project/$configuration.get` invalidates `$project`).
157
+ * Override invalidation in client options via `queryInvalidation`.
158
+ *
159
+ * Pass `options` to attach a `select` Effect that runs after the mutation
160
+ * succeeds (its output is returned to the caller) and/or override the default
161
+ * `queryInvalidation`.
100
162
  */
101
- (i: I): Effect.Effect<A, E, R>
163
+ <B = A, E2 = never, R2 = never>(
164
+ input: I,
165
+ options?: MutationOptionsBase<A, B, E2, R2>
166
+ ): Effect.Effect<B, E | E2, R | R2>
167
+
168
+ project: <ProjSchema extends S.Top>(
169
+ schema: EA extends ProjSchema["Encoded"] ? ProjSchema : never
170
+ ) => MutationExtWithInput<
171
+ RT,
172
+ Id,
173
+ I,
174
+ S.Schema.Type<ProjSchema>,
175
+ E | S.SchemaError,
176
+ R | S.Codec.DecodingServices<ProjSchema>,
177
+ S.Codec.Encoded<ProjSchema>
178
+ >
102
179
  }
103
180
 
104
181
  /**
105
- * Call the endpoint
106
- * Invalidate queries based on namespace of this mutation.
107
- * Do not use for queries.
182
+ * Send the request to the endpoint and return the raw Effect response.
183
+ * Also invalidates query caches using the request namespace by default.
184
+ * Namespace invalidation targets parent namespace keys
185
+ * (for example `$project/$configuration.get` invalidates `$project`).
186
+ * Override invalidation in client options via `queryInvalidation`.
108
187
  */
109
188
  export interface MutationExt<
110
189
  RT,
111
190
  Id extends string,
112
191
  A,
113
192
  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
- {
193
+ R,
194
+ EA = unknown
195
+ > extends MutationExtensions<RT, Id, void, A, E, R> {
196
+ /**
197
+ * Send the request to the endpoint and return the raw Effect response.
198
+ * Also invalidates query caches using the request namespace by default.
199
+ *
200
+ * Pass `options` to attach a `select` Effect that runs after the mutation
201
+ * succeeds (its output is returned to the caller) and/or override the default
202
+ * `queryInvalidation`.
203
+ */
204
+ <B = A, E2 = never, R2 = never>(
205
+ options?: MutationOptionsBase<A, B, E2, R2>
206
+ ): Effect.Effect<B, E | E2, R | R2>
207
+
208
+ project: <ProjSchema extends S.Top>(
209
+ schema: EA extends ProjSchema["Encoded"] ? ProjSchema : never
210
+ ) => MutationExt<
211
+ RT,
212
+ Id,
213
+ S.Schema.Type<ProjSchema>,
214
+ E | S.SchemaError,
215
+ R | S.Codec.DecodingServices<ProjSchema>,
216
+ S.Codec.Encoded<ProjSchema>
217
+ >
121
218
  }
122
219
 
123
220
  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>
221
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
222
+ ? MutationExtWithInput<RT, Id, I, A, E, R, S.Codec.Encoded<Request["success"]>>
223
+ : Req extends RequestHandler<infer A, infer E, infer R, infer Request, infer Id>
224
+ ? MutationExt<RT, Id, A, E, R, S.Codec.Encoded<Request["success"]>>
225
+ : never
226
+
227
+ /**
228
+ * Options for invoking a `mutateStream` factory. Supplying `progress` produces
229
+ * a tuple-with-id that carries `running` (the live AsyncResult ref) and
230
+ * `progress` (a `ComputedRef<Progress | undefined>` formatted from each value),
231
+ * which `Command.fn` / `Command.wrapStream` surface as the command's `running`
232
+ * and `progress`. When omitted, the resulting command exposes neither.
233
+ */
234
+ export type MutateStreamCallOptions<A, E> = {
235
+ progress?: (result: AsyncResult.AsyncResult<A, E>) => Progress | undefined
236
+ }
237
+
238
+ /**
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.
247
+ */
248
+ export type StreamMutationWithExtensions<Req> = Req extends
249
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
250
+ & ((options?: MutateStreamCallOptions<A, E>) =>
251
+ & readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, (input: I) => Effect.Effect<Final, never, R>]
252
+ & {
253
+ readonly id: Id
254
+ readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
255
+ readonly progress?: ComputedRef<Progress | undefined>
256
+ })
257
+ & { readonly id: Id }
258
+ : Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
259
+ & ((options?: MutateStreamCallOptions<A, E>) =>
260
+ & readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, Effect.Effect<Final, never, R>]
261
+ & {
262
+ readonly id: Id
263
+ readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
264
+ readonly progress?: ComputedRef<Progress | undefined>
265
+ })
266
+ & { readonly id: Id }
267
+ : never
268
+
269
+ /**
270
+ * The pre-built `wrapStream` CommanderWrap for a stream-type request handler.
271
+ * The command's `result` and `running` are the live stream ref.
272
+ * Callable like `wrap`: `client.myExport.wrapStream()` returns the CommandOut.
273
+ */
274
+ export type StreamCommandWithExtensions<RT, Req> = Req extends
275
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
276
+ ? Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
277
+ : Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
278
+ ? Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>
279
+ : never
280
+
281
+ /**
282
+ * The `fn` builder for a stream-type request handler — identical to calling
283
+ * `Command.fn(id)` where `id` comes from the request.
284
+ */
285
+ export type StreamFnExtension<RT, Req> = Req extends
286
+ RequestStreamHandlerWithInput<infer _I, infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
287
+ ? Commander.CommanderFn<RT, Id, Id, undefined>
288
+ : Req extends RequestStreamHandler<infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
289
+ ? Commander.CommanderFn<RT, Id, Id, undefined>
127
290
  : never
128
291
 
129
292
  // we don't really care about the RT, as we are in charge of ensuring runtime safety anyway
@@ -132,29 +295,72 @@ declare const useQuery_: QueryImpl<any>["useQuery"]
132
295
  // eslint-disable-next-line unused-imports/no-unused-vars
133
296
  declare const useSuspenseQuery_: QueryImpl<any>["useSuspenseQuery"]
134
297
 
298
+ export interface ProjectResult<RT, I, B, E, R, Request extends Req, Id extends string> {
299
+ request: (i: I) => Effect.Effect<B, E, R>
300
+ query: Exclude<R, RT> extends never ? ReturnType<typeof useQuery_<I, E, B, Request, Id>>
301
+ : MissingDependencies<RT, R> & {}
302
+ suspense: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQuery_<I, E, B, Request, Id>>
303
+ : MissingDependencies<RT, R> & {}
304
+ }
305
+
306
+ export type QueryProjection<RT, HandlerReq> = HandlerReq extends
307
+ RequestHandlerWithInput<infer I, infer _A, infer E, infer R, infer Request, infer Id>
308
+ ? Request["type"] extends "query" ? {
309
+ project: <ProjSchema extends S.Top>(
310
+ schema: S.Codec.Encoded<Request["success"]> extends ProjSchema["Encoded"] ? ProjSchema : never
311
+ ) => ProjectResult<
312
+ RT,
313
+ I,
314
+ S.Schema.Type<ProjSchema>,
315
+ E | S.SchemaError,
316
+ R | S.Codec.DecodingServices<ProjSchema>,
317
+ Request,
318
+ Id
319
+ >
320
+ }
321
+ : {}
322
+ : HandlerReq extends RequestHandler<infer _A, infer E, infer R, infer Request, infer Id>
323
+ ? Request["type"] extends "query" ? {
324
+ project: <ProjSchema extends S.Top>(
325
+ schema: S.Codec.Encoded<Request["success"]> extends ProjSchema["Encoded"] ? ProjSchema : never
326
+ ) => ProjectResult<
327
+ RT,
328
+ void,
329
+ S.Schema.Type<ProjSchema>,
330
+ E | S.SchemaError,
331
+ R | S.Codec.DecodingServices<ProjSchema>,
332
+ Request,
333
+ Id
334
+ >
335
+ }
336
+ : {}
337
+ : {}
338
+
135
339
  export interface QueriesWithInput<Request extends Req, Id extends string, I, A, E> {
136
340
  /**
137
- * Effect results are passed to the caller, including errors.
341
+ * Read helper for query requests.
342
+ * Runs as a tracked Vue Query and returns reactive state.
343
+ * Queries read state and should not be used to mutate it.
138
344
  */
139
345
  query: ReturnType<typeof useQuery_<I, E, A, Request, Id>>
140
346
  // TODO or suspense as Option?
141
347
  /**
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.
348
+ * Like `.query`, but returns a Promise for setup-time awaiting.
349
+ * Use this when integrating with Vue Suspense / error boundaries.
145
350
  */
146
351
  suspense: ReturnType<typeof useSuspenseQuery_<I, E, A, Request, Id>>
147
352
  }
148
353
  export interface QueriesWithoutInput<Request extends Req, Id extends string, A, E> {
149
354
  /**
150
- * Effect results are passed to the caller, including errors.
355
+ * Read helper for query requests.
356
+ * Runs as a tracked Vue Query and returns reactive state.
357
+ * Queries read state and should not be used to mutate it.
151
358
  */
152
359
  query: ReturnType<typeof useQuery_<E, A, Request, Id>>
153
360
  // TODO or suspense as Option?
154
361
  /**
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.
362
+ * Like `.query`, but returns a Promise for setup-time awaiting.
363
+ * Use this when integrating with Vue Suspense / error boundaries.
158
364
  */
159
365
  suspense: ReturnType<typeof useSuspenseQuery_<E, A, Request, Id>>
160
366
  }
@@ -166,184 +372,34 @@ export type MissingDependencies<RT, R> = {
166
372
 
167
373
  export type Queries<RT, Req> = Req extends
168
374
  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
- }
375
+ ? Request["type"] extends "query" ? Exclude<R, RT> extends never ? QueriesWithInput<Request, Id, I, A, E>
376
+ : {
377
+ query: MissingDependencies<RT, R> & {}
378
+ suspense: MissingDependencies<RT, R> & {}
379
+ }
380
+ : never
174
381
  : Req extends RequestHandler<infer A, infer E, infer R, infer Request, infer Id>
175
- ? Exclude<R, RT> extends never ? QueriesWithoutInput<Request, Id, A, E>
176
- : { query: MissingDependencies<RT, R> & {}; suspense: MissingDependencies<RT, R> & {} }
382
+ ? Request["type"] extends "query" ? Exclude<R, RT> extends never ? QueriesWithoutInput<Request, Id, A, E>
383
+ : { query: MissingDependencies<RT, R> & {}; suspense: MissingDependencies<RT, R> & {} }
384
+ : never
177
385
  : never
178
386
 
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
249
- }
250
-
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)
387
+ const _useMutation = makeMutation()
317
388
 
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
- )
389
+ const wrapWithSpan = (self: { id: string; handler: any }, mut: any) => {
390
+ const span = (eff: Effect.Effect<any, any, any>) =>
391
+ Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })(eff)
392
+ return Effect.isEffect(self.handler)
393
+ ? (options?: MutationOptionsBase) => span(mut(options))
394
+ : (input: any, options?: MutationOptionsBase) => span(mut(input, options))
337
395
  }
338
396
 
339
- const _useMutation = makeMutation()
340
-
341
397
  /**
342
398
  * Pass an Effect or a function that returns an Effect, e.g from a client action
343
399
  * Executes query cache invalidation based on default rules or provided option.
344
400
  * adds a span with the mutation id
345
401
  */
346
- export const useMutation: typeof _useMutation = <
402
+ export const useMutation: typeof _useMutation = (<
347
403
  I,
348
404
  E,
349
405
  A,
@@ -351,16 +407,12 @@ export const useMutation: typeof _useMutation = <
351
407
  Request extends Req,
352
408
  Name extends string
353
409
  >(
354
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
355
- options?: MutationOptionsBase
410
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>
356
411
  ) =>
357
412
  Object.assign(
358
- mapHandler(
359
- _useMutation(self as any, options),
360
- Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })
361
- ) as any,
413
+ wrapWithSpan(self, _useMutation(self as any)),
362
414
  { id: self.id }
363
- )
415
+ )) as any
364
416
 
365
417
  /**
366
418
  * Pass an Effect or a function that returns an Effect, e.g from a client action
@@ -369,7 +421,7 @@ export const useMutation: typeof _useMutation = <
369
421
  */
370
422
  export const useMutationInt = (): typeof _useMutation => {
371
423
  const _useMutation = useMakeMutation()
372
- return <
424
+ return (<
373
425
  I,
374
426
  E,
375
427
  A,
@@ -377,714 +429,18 @@ export const useMutationInt = (): typeof _useMutation => {
377
429
  Request extends Req,
378
430
  Name extends string
379
431
  >(
380
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
381
- options?: MutationOptionsBase
432
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>
382
433
  ) =>
383
434
  Object.assign(
384
- mapHandler(
385
- _useMutation(self as any, options),
386
- Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })
387
- ) as any,
435
+ wrapWithSpan(self, _useMutation(self as any)),
388
436
  { id: self.id }
389
- )
437
+ )) as any
390
438
  }
391
439
 
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
1082
- }
1083
-
1084
- export type ClientFrom<M extends Requests> = RequestHandlers<never, never, M, M["meta"]["moduleName"]>
440
+ export type ClientFrom<M extends RequestsAny> = RequestHandlers<never, never, M, ExtractModuleName<M>>
1085
441
 
1086
442
  export class QueryImpl<R> {
1087
- constructor(readonly getRuntime: () => ServiceMap.ServiceMap<R>) {
443
+ constructor(readonly getRuntime: () => Context.Context<R>) {
1088
444
  this.useQuery = makeQuery(this.getRuntime)
1089
445
  }
1090
446
  /**
@@ -1163,7 +519,7 @@ export class QueryImpl<R> {
1163
519
  } = <Arg, E, A, Request extends Req, Name extends string>(
1164
520
  self: RequestHandlerWithInput<Arg, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>
1165
521
  ) => {
1166
- const runPromise = Effect.runPromiseWith(this.getRuntime())
522
+ const runPromise = makeRunPromise(this.getRuntime())
1167
523
  const q = this.useQuery(self as any) as any
1168
524
  return (argOrOptions?: any, options?: any) => {
1169
525
  const [resultRef, latestRef, fetch, uqrt] = q(argOrOptions, { ...options, suspense: true } // experimental_prefetchInRender: true }
@@ -1214,10 +570,64 @@ export class QueryImpl<R> {
1214
570
  }
1215
571
 
1216
572
  // 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>())
573
+ const managedRuntimeRt = <A, E>(mrt: ManagedRuntime.ManagedRuntime<A, E>) => mrt.runSync(Effect.context<A>())
1218
574
 
1219
575
  type Base = I18n | Toast
1220
- type Mix = ApiClientFactory | Commander | LegacyMutation | Base
576
+ type Mix = ApiClientFactory | Commander | Base
577
+
578
+ type InvalidationResources = Record<string, Record<string, unknown>>
579
+ type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I
580
+ : never
581
+
582
+ type CommandInvalidationResources<Req> = Req extends {
583
+ readonly type: "command"
584
+ readonly "~invalidationResources"?: infer Resources
585
+ } ? NonNullable<Resources> extends InvalidationResources ? NonNullable<Resources> : never
586
+ : Req extends {
587
+ readonly type: "command"
588
+ readonly config?: infer Config
589
+ } ? Config extends {
590
+ readonly invalidationResources?: infer LegacyResources
591
+ } ? NonNullable<LegacyResources> extends InvalidationResources ? NonNullable<LegacyResources> : never
592
+ : Config extends {
593
+ readonly invalidatesQueries?: InvalidationCallback<infer LegacyResources, any, any, any>
594
+ } ? NonNullable<LegacyResources> extends InvalidationResources ? NonNullable<LegacyResources> : never
595
+ : never
596
+ : never
597
+
598
+ type InvalidationResourcesForUnion<M extends RequestsAny> = {
599
+ [K in keyof M]: CommandInvalidationResources<M[K]>
600
+ }[keyof M]
601
+
602
+ type InvalidationResourcesFor<M extends RequestsAny> = [InvalidationResourcesForUnion<M>] extends [never] ? never
603
+ : UnionToIntersection<InvalidationResourcesForUnion<M>> extends infer R ? R extends InvalidationResources ? R
604
+ : never
605
+ : never
606
+
607
+ type QueryInvalidationFactory<M extends RequestsAny> = (client: ClientFrom<M>) => QueryInvalidation<M>
608
+
609
+ type StrictResourcesArg<Shape, Actual extends Shape = Shape> =
610
+ & Actual
611
+ & Record<Exclude<keyof Actual, keyof Shape>, never>
612
+
613
+ type ClientForArgs<
614
+ M extends RequestsAny,
615
+ Resources extends InvalidationResourcesFor<M> = InvalidationResourcesFor<M>
616
+ > = [InvalidationResourcesFor<M>] extends [never] ? [
617
+ queryInvalidation?: QueryInvalidationFactory<M>,
618
+ invalidationResources?: StrictResourcesArg<
619
+ InvalidationResourcesFor<M>,
620
+ Resources
621
+ >
622
+ ]
623
+ : [
624
+ queryInvalidation: QueryInvalidationFactory<M> | undefined,
625
+ invalidationResources: StrictResourcesArg<
626
+ InvalidationResourcesFor<M>,
627
+ Resources
628
+ >
629
+ ]
630
+
1221
631
  export const makeClient = <RT_, RTHooks>(
1222
632
  // global, but only accessible after startup has completed
1223
633
  getBaseMrt: () => ManagedRuntime.ManagedRuntime<RT_ | Mix, never>,
@@ -1225,54 +635,66 @@ export const makeClient = <RT_, RTHooks>(
1225
635
  rtHooks: Layer.Layer<RTHooks, never, Mix>
1226
636
  ) => {
1227
637
  type RT = RT_ | Mix
1228
- const getRt = Effect.services<RT>()
1229
638
  const getBaseRt = () => managedRuntimeRt(getBaseMrt())
1230
639
  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
640
  let cmd: Effect.Success<typeof makeCommand>
1237
641
  const useCommand = () => cmd ??= getBaseMrt().runSync(makeCommand)
1238
- let mut: Effect.Success<typeof makeMutation>
1239
- const getMutation = () => mut ??= getBaseMrt().runSync(makeMutation)
1240
642
 
1241
643
  let m: ReturnType<typeof useMutationInt>
1242
644
  const useMutation = () => m ??= useMutationInt()
1243
645
 
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
- )
646
+ let sm: ReturnType<typeof makeStreamMutation>
647
+ const useStreamMutation = () => sm ??= makeStreamMutation()
1266
648
 
1267
649
  const query = new QueryImpl(getBaseRt)
1268
650
  const useQuery = query.useQuery
1269
651
  const useSuspenseQuery = query.useSuspenseQuery
1270
652
 
1271
- const mapQuery = <M extends Requests>(
653
+ const mergeInvalidation = (
654
+ a?: MutationOptionsBase["queryInvalidation"],
655
+ b?: MutationOptionsBase["queryInvalidation"]
656
+ ): MutationOptionsBase["queryInvalidation"] | undefined => {
657
+ if (!a && !b) {
658
+ return undefined
659
+ }
660
+ return (defaultKey, name, input, output) => [
661
+ ...(a?.(defaultKey, name, input, output) ?? []),
662
+ ...(b?.(defaultKey, name, input, output) ?? [])
663
+ ]
664
+ }
665
+
666
+ const withDefaultInvalidation = (
667
+ mut: any,
668
+ isWithInput: boolean,
669
+ defaultInvalidation?: MutationOptionsBase["queryInvalidation"]
670
+ ) => {
671
+ if (!defaultInvalidation) return mut
672
+ const apply = (callerOpts?: MutationOptionsBase) => ({
673
+ ...callerOpts,
674
+ queryInvalidation: callerOpts?.queryInvalidation
675
+ ? mergeInvalidation(defaultInvalidation, callerOpts.queryInvalidation)
676
+ : defaultInvalidation
677
+ })
678
+ return isWithInput
679
+ ? (input: any, callerOpts?: MutationOptionsBase) => mut(input, apply(callerOpts))
680
+ : (callerOpts?: MutationOptionsBase) => mut(apply(callerOpts))
681
+ }
682
+
683
+ const makeQueryResources = <Resources extends InvalidationResources>(resources: Resources | undefined) => {
684
+ if (!resources) {
685
+ return {} as Record<string, Record<string, unknown>>
686
+ }
687
+ return resources as Record<string, Record<string, unknown>>
688
+ }
689
+
690
+ const mapQuery = <M extends RequestsAny>(
1272
691
  client: ClientFrom<M>
1273
692
  ) => {
1274
693
  const queries = Struct.keys(client).reduce(
1275
694
  (acc, key) => {
695
+ if (client[key].Request.type !== "query") {
696
+ return acc
697
+ }
1276
698
  ;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(client[key] as any), {
1277
699
  id: client[key].id
1278
700
  })
@@ -1284,26 +706,35 @@ export const makeClient = <RT_, RTHooks>(
1284
706
  {} as
1285
707
  & {
1286
708
  // apparently can't get JSDoc in here..
1287
- [Key in keyof typeof client as `${ToCamel<string & Key>}Query`]: Queries<RT, typeof client[Key]>["query"]
709
+ [
710
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
711
+ : `${ToCamel<string & Key>}Query`
712
+ ]: Queries<RT, QueryHandler<typeof client[Key]>>["query"]
1288
713
  }
1289
714
  // todo: or suspense as an Option?
1290
715
  & {
1291
716
  // apparently can't get JSDoc in here..
1292
- [Key in keyof typeof client as `${ToCamel<string & Key>}SuspenseQuery`]: Queries<
717
+ [
718
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
719
+ : `${ToCamel<string & Key>}SuspenseQuery`
720
+ ]: Queries<
1293
721
  RT,
1294
- typeof client[Key]
722
+ QueryHandler<typeof client[Key]>
1295
723
  >["suspense"]
1296
724
  }
1297
725
  )
1298
726
  return queries
1299
727
  }
1300
728
 
1301
- const mapRequest = <M extends Requests>(
729
+ const mapRequest = <M extends RequestsAny>(
1302
730
  client: ClientFrom<M>
1303
731
  ) => {
1304
732
  const Command = useCommand()
1305
733
  const mutations = Struct.keys(client).reduce(
1306
734
  (acc, key) => {
735
+ if (client[key].Request.type !== "command") {
736
+ return acc
737
+ }
1307
738
  const mut = client[key].handler
1308
739
  const fn = Command.fn(client[key].id)
1309
740
  const wrap = Command.wrap({ mutate: Effect.isEffect(mut) ? () => mut : mut, id: client[key].id })
@@ -1315,118 +746,323 @@ export const makeClient = <RT_, RTHooks>(
1315
746
  return acc
1316
747
  },
1317
748
  {} as {
1318
- [Key in keyof typeof client as `${ToCamel<string & Key>}Request`]: RequestWithExtensions<
749
+ [
750
+ Key in keyof typeof client as CommandHandler<typeof client[Key]> extends never ? never
751
+ : `${ToCamel<string & Key>}Request`
752
+ ]: CommandRequestWithExtensions<
1319
753
  RT | RTHooks,
1320
- typeof client[Key]
754
+ CommandHandler<typeof client[Key]>
1321
755
  >
1322
756
  }
1323
757
  )
1324
758
  return mutations
1325
759
  }
1326
760
 
1327
- const mapMutation = <M extends Requests>(
1328
- client: ClientFrom<M>
761
+ const mapMutation = <M extends RequestsAny>(
762
+ client: ClientFrom<M>,
763
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
764
+ invalidationResources?: InvalidationResourcesFor<M>
1329
765
  ) => {
1330
766
  const Command = useCommand()
1331
767
  const mutation = useMutation()
768
+ const invalidation = queryInvalidation?.(client)
769
+ const queryResources = makeQueryResources(invalidationResources)
1332
770
  const mutations = Struct.keys(client).reduce(
1333
771
  (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
- )
772
+ if (client[key].Request.type !== "command") {
773
+ return acc
774
+ }
775
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
776
+ | InvalidationCallback<InvalidationResourcesFor<M>>
777
+ | undefined
778
+ const fromRequest = fromRequestConfig
779
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
780
+ fromRequestConfig(defaultKey, queryResources as never, input as never, output as never).map((entry) => ({
781
+ filters: entry.filters,
782
+ options: entry.options
783
+ })))
784
+ : undefined
785
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
786
+ const makeProjectedMutation = (handler: any): any => {
787
+ const isWithInput = !Effect.isEffect(handler.handler)
788
+ const mut: any = withDefaultInvalidation(mutation(handler), isWithInput, mergedInvalidation)
789
+ const wrap = Command.wrap({ mutate: mut, id: client[key].id })
790
+ return Object.assign(mut, {
791
+ wrap,
792
+ project: (projectionSchema: any) => {
793
+ const projected = {
794
+ ...handler,
795
+ handler: projectHandler(handler.handler, client[key].Request.success, projectionSchema)
796
+ }
797
+ return makeProjectedMutation(projected)
798
+ }
799
+ })
800
+ }
801
+ ;(acc as any)[camelCase(key) + "Mutation"] = makeProjectedMutation(client[key] as any)
1342
802
  return acc
1343
803
  },
1344
804
  {} as {
1345
- [Key in keyof typeof client as `${ToCamel<string & Key>}Mutation`]: MutationWithExtensions<
805
+ [
806
+ Key in keyof typeof client as CommandHandler<typeof client[Key]> extends never ? never
807
+ : `${ToCamel<string & Key>}Mutation`
808
+ ]: MutationWithExtensions<
1346
809
  RT | RTHooks,
1347
- typeof client[Key]
810
+ CommandHandler<typeof client[Key]>
1348
811
  >
1349
812
  }
1350
813
  )
1351
814
  return mutations
1352
815
  }
1353
816
 
817
+ const mapStreamMutation = <M extends RequestsAny>(
818
+ client: ClientFrom<M>,
819
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
820
+ invalidationResources?: InvalidationResourcesFor<M>
821
+ ) => {
822
+ const Command = useCommand()
823
+ const streamMutation = useStreamMutation()
824
+ const invalidation = queryInvalidation?.(client)
825
+ const queryResources = makeQueryResources(invalidationResources)
826
+ const streams = Struct.keys(client).reduce(
827
+ (acc, key) => {
828
+ if (client[key].Request.type !== "stream") {
829
+ return acc
830
+ }
831
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
832
+ | InvalidationCallback<InvalidationResourcesFor<M>>
833
+ | undefined
834
+ const fromRequest = fromRequestConfig
835
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
836
+ fromRequestConfig(defaultKey, queryResources as never, input as never, output as never).map((entry) => ({
837
+ filters: entry.filters,
838
+ options: entry.options
839
+ })))
840
+ : undefined
841
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
842
+ const smFactory = Object.assign(
843
+ (opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
844
+ const tuple = streamMutation(client[key] as any, mergedInvalidation)
845
+ const extras: {
846
+ id: string
847
+ running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
848
+ progress?: ComputedRef<Progress | undefined>
849
+ } = { id: client[key].id }
850
+ if (opts?.progress) {
851
+ const fmt = opts.progress
852
+ extras.running = tuple[0]
853
+ extras.progress = computed(() => fmt(tuple[0].value))
854
+ }
855
+ return Object.assign(tuple, extras)
856
+ },
857
+ { id: client[key].id }
858
+ )
859
+ ;(acc as any)[camelCase(key) + "Stream"] = Object.assign(smFactory, {
860
+ fn: Command.fn(client[key].id)
861
+ })
862
+ return acc
863
+ },
864
+ {} as {
865
+ [
866
+ Key in keyof typeof client as StreamHandler<typeof client[Key]> extends never ? never
867
+ : `${ToCamel<string & Key>}Stream`
868
+ ]:
869
+ & StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
870
+ & { fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>> }
871
+ }
872
+ )
873
+ return streams
874
+ }
875
+
1354
876
  // make available .query, .suspense and .mutate for each operation
1355
877
  // and a .helpers with all mutations and queries
1356
- const mapClient = <M extends Requests>(
1357
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
878
+ const mapClient = <M extends RequestsAny>(
879
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
880
+ invalidationResources?: InvalidationResourcesFor<M>
1358
881
  ) =>
1359
882
  (
1360
883
  client: ClientFrom<M>
1361
884
  ) => {
1362
885
  const Command = useCommand()
1363
886
  const mutation = useMutation()
887
+ const streamMutation = useStreamMutation()
1364
888
  const invalidation = queryInvalidation?.(client)
889
+ const queryResources = makeQueryResources(invalidationResources)
1365
890
  const extended = Struct.keys(client).reduce(
1366
891
  (acc, key) => {
892
+ const requestType = client[key].Request.type
1367
893
  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
894
  const h_ = client[key].handler
1385
- const h = Effect.isEffect(h_)
895
+ const wrapInput = Effect.isEffect(h_)
1386
896
  ? () => h_
1387
897
  : (...args: [any]) => h_(...args)
898
+ const request = Effect.isEffect(h_) ? h_ : wrapInput
1388
899
  ;(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
- }
900
+ requestType === "query"
901
+ ? {
902
+ ...client[key],
903
+ request,
904
+ query: useQuery(client[key] as any),
905
+ suspense: useSuspenseQuery(client[key] as any),
906
+ project: (projectionSchema: any) => {
907
+ const successSchema = client[key].Request.success
908
+ const projectionHash = projectionSchemaHash(projectionSchema)
909
+ const projected = projectHandler(h_ as any, successSchema, projectionSchema)
910
+ const fakeHandler = {
911
+ handler: projected,
912
+ id: client[key].id,
913
+ Request: client[key].Request,
914
+ options: client[key].options,
915
+ queryKeyProjectionHash: projectionHash
916
+ }
917
+ return {
918
+ request: projected,
919
+ query: useQuery(fakeHandler as any),
920
+ suspense: useSuspenseQuery(fakeHandler as any)
921
+ }
922
+ }
923
+ }
924
+ : requestType === "stream"
925
+ ? (() => {
926
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
927
+ | InvalidationCallback<InvalidationResourcesFor<M>>
928
+ | undefined
929
+ const fromRequest = fromRequestConfig
930
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
931
+ fromRequestConfig(defaultKey, queryResources as never, input as never, output as never).map((
932
+ entry
933
+ ) => ({
934
+ filters: entry.filters,
935
+ options: entry.options
936
+ })))
937
+ : undefined
938
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
939
+ const streamMutFactory = Object.assign(
940
+ (opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
941
+ const tuple = streamMutation(client[key] as any, mergedInvalidation)
942
+ const extras: {
943
+ id: string
944
+ running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
945
+ progress?: ComputedRef<Progress | undefined>
946
+ } = { id: client[key].id }
947
+ if (opts?.progress) {
948
+ const fmt = opts.progress
949
+ extras.running = tuple[0]
950
+ extras.progress = computed(() => fmt(tuple[0].value))
951
+ }
952
+ return Object.assign(tuple, extras)
953
+ },
954
+ { id: client[key].id }
955
+ )
956
+ return {
957
+ ...client[key],
958
+ request: h_,
959
+ mutateStream: streamMutFactory,
960
+ wrapStream: Command.wrapStream(streamMutFactory),
961
+ fn: Command.fn(client[key].id)
962
+ }
963
+ })()
964
+ : {
965
+ mutate: ((handler: any) => {
966
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
967
+ | InvalidationCallback<InvalidationResourcesFor<M>>
968
+ | undefined
969
+ const fromRequest = fromRequestConfig
970
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
971
+ fromRequestConfig(defaultKey, queryResources as never, input as never, output as never).map((
972
+ entry
973
+ ) => ({
974
+ filters: entry.filters,
975
+ options: entry.options
976
+ })))
977
+ : undefined
978
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
979
+ const makeProjectedMutation = (h: any): any => {
980
+ const isWithInput = !Effect.isEffect(h.handler)
981
+ const mutate = withDefaultInvalidation(mutation(h), isWithInput, mergedInvalidation)
982
+ return Object.assign(
983
+ mutate,
984
+ {
985
+ wrap: Command.wrap({
986
+ mutate,
987
+ id: client[key].id
988
+ }),
989
+ project: (projectionSchema: any) => {
990
+ const projected = {
991
+ ...h,
992
+ handler: projectHandler(h.handler, client[key].Request.success, projectionSchema)
993
+ }
994
+ return makeProjectedMutation(projected)
995
+ }
996
+ }
997
+ )
998
+ }
999
+ return makeProjectedMutation(handler)
1000
+ })(client[key] as any),
1001
+ ...client[key],
1002
+ ...fn, // to get the i18n key etc.
1003
+ request,
1004
+ fn,
1005
+ wrap: Command.wrap({ mutate: wrapInput, id: client[key].id })
1006
+ }
1399
1007
  )
1400
1008
  return acc
1401
1009
  },
1402
1010
  {} as {
1403
1011
  [Key in keyof typeof client]:
1404
1012
  & typeof client[Key]
1405
- & RequestWithExtensions<RT | RTHooks, typeof client[Key]>
1406
- & { mutate: MutationWithExtensions<RT | RTHooks, typeof client[Key]> }
1407
- & Queries<RT, typeof client[Key]>
1013
+ & (QueryHandler<typeof client[Key]> extends never ? {}
1014
+ :
1015
+ & QueryRequestWithExtensions<QueryHandler<typeof client[Key]>>
1016
+ & Queries<RT, QueryHandler<typeof client[Key]>>
1017
+ & QueryProjection<RT, QueryHandler<typeof client[Key]>>)
1018
+ & (CommandHandler<typeof client[Key]> extends never ? {}
1019
+ : CommandRequestWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>>)
1020
+ & (CommandHandler<typeof client[Key]> extends never ? {}
1021
+ : { mutate: MutationWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>> })
1022
+ & (StreamHandler<typeof client[Key]> extends never ? {}
1023
+ : {
1024
+ mutateStream: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
1025
+ wrapStream: StreamCommandWithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
1026
+ fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>>
1027
+ })
1028
+ & { Input: typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I : never }
1408
1029
  }
1409
1030
  )
1410
- return Object.assign(extended, { helpers: { ...mapRequest(client), ...mapMutation(client), ...mapQuery(client) } })
1031
+ return Object.assign(extended, {
1032
+ helpers: {
1033
+ ...mapRequest(client),
1034
+ ...mapMutation(client, queryInvalidation, invalidationResources),
1035
+ ...mapStreamMutation(client, queryInvalidation, invalidationResources),
1036
+ ...mapQuery(client)
1037
+ }
1038
+ })
1411
1039
  }
1412
1040
 
1413
1041
  // TODO: Clean up this delay initialisation messs
1414
1042
  // TODO; invalidateQueries should perhaps be configured in the Request impl themselves?
1415
- const clientFor__ = <M extends Requests>(
1043
+ const clientFor__ = <M extends RequestsAny>(
1416
1044
  m: M,
1417
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
1418
- ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation))))
1045
+ queryInvalidation?: QueryInvalidationFactory<M>,
1046
+ invalidationResources?: InvalidationResourcesFor<M>
1047
+ ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation, invalidationResources))))
1419
1048
 
1420
1049
  // delay client creation until first access
1421
1050
  // the idea is that we don't need the useNuxtApp().$runtime (only available at later initialisation stage)
1422
1051
  // until we are at a place where it is available..
1423
- const clientFor = <M extends Requests>(
1052
+ const clientFor = <
1053
+ M extends RequestsAny,
1054
+ Resources extends InvalidationResourcesFor<M> = InvalidationResourcesFor<M>
1055
+ >(
1424
1056
  m: M,
1425
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
1057
+ ...args: ClientForArgs<M, Resources>
1426
1058
  ) => {
1059
+ const [queryInvalidation, invalidationResources] = args as [
1060
+ QueryInvalidationFactory<M> | undefined,
1061
+ InvalidationResourcesFor<M> | undefined
1062
+ ]
1427
1063
  type Client = ReturnType<typeof clientFor__<M>>
1428
1064
  let client: Client | undefined = undefined
1429
- const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation))
1065
+ const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation, invalidationResources))
1430
1066
 
1431
1067
  // initialize on first use..
1432
1068
  const proxy = Struct.keys(m).concat(["helpers"]).reduce((acc, key) => {
@@ -1444,16 +1080,12 @@ export const makeClient = <RT_, RTHooks>(
1444
1080
  return proxy
1445
1081
  }
1446
1082
 
1447
- const legacy: Legacy<RT> = {
1448
- ...mutations,
1449
- ...query
1450
- }
1451
-
1452
1083
  const Command: CommanderResolved<RT, RTHooks> = {
1453
1084
  ...{
1454
1085
  // delay initialisation until first use...
1455
1086
  fn: (...args: [any]) => useCommand().fn(...args),
1456
1087
  wrap: (...args: [any]) => useCommand().wrap(...args),
1088
+ wrapStream: (...args: [any]) => useCommand().wrapStream(...args),
1457
1089
  alt: (...args: [any]) => useCommand().alt(...args),
1458
1090
  alt2: (...args: [any]) => useCommand().alt2(...args)
1459
1091
  } as ReturnType<typeof useCommand>,
@@ -1463,19 +1095,17 @@ export const makeClient = <RT_, RTHooks>(
1463
1095
  return {
1464
1096
  Command,
1465
1097
  useCommand,
1466
- clientFor,
1467
- legacy
1098
+ clientFor
1468
1099
  }
1469
1100
  }
1470
1101
 
1471
- export interface Legacy<R>
1472
- extends
1473
- Pick<QueryImpl<R>, "useQuery" | "useSuspenseQuery">,
1474
- Omit<LegacyMutationImpl<R>, "getRuntime" | "toast" | "intl">
1475
- {}
1476
-
1477
1102
  export type QueryInvalidation<M> = {
1478
- [K in keyof M]?: (defaultKey: string[], name: string) => {
1103
+ [K in keyof M]?: (
1104
+ defaultKey: string[],
1105
+ name: string,
1106
+ input?: unknown,
1107
+ output?: ExitResult.Exit<unknown, unknown>
1108
+ ) => {
1479
1109
  filters?: InvalidateQueryFilters | undefined
1480
1110
  options?: InvalidateOptions | undefined
1481
1111
  }[]
@@ -1493,10 +1123,12 @@ export interface CommandBase<I = void, A = void> {
1493
1123
  allowed: boolean
1494
1124
  action: string
1495
1125
  label: string
1126
+ /** formatted progress info for current `running` state, when `progress` was supplied */
1127
+ progress?: Progress | undefined
1496
1128
  }
1497
1129
 
1498
1130
  export interface EffectCommand<I = void, A = unknown, E = unknown> extends CommandBase<I, Fiber<A, E>> {}
1499
1131
 
1500
- export interface CommandFromRequest<I extends abstract new(...args: any) => any, A = unknown, E = unknown>
1501
- extends EffectCommand<ConstructorParameters<I>[0], A, E>
1132
+ export interface CommandFromRequest<I extends { readonly make: (...args: any[]) => any }, A = unknown, E = unknown>
1133
+ extends EffectCommand<RequestInputFromMake<I>, A, E>
1502
1134
  {}