@effect-app/vue 4.0.0-beta.19 → 4.0.0-beta.190

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