@effect-app/vue 4.0.0-beta.33 → 4.0.0-beta.331

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 (148) hide show
  1. package/CHANGELOG.md +2463 -0
  2. package/dist/atomQuery.d.ts +120 -0
  3. package/dist/atomQuery.d.ts.map +1 -0
  4. package/dist/atomQuery.js +434 -0
  5. package/dist/commander.d.ts +634 -0
  6. package/dist/commander.d.ts.map +1 -0
  7. package/dist/commander.js +1068 -0
  8. package/dist/{experimental/confirm.d.ts → confirm.d.ts} +6 -4
  9. package/dist/confirm.d.ts.map +1 -0
  10. package/dist/confirm.js +26 -0
  11. package/dist/dependencyMetadata.d.ts +18 -0
  12. package/dist/dependencyMetadata.d.ts.map +1 -0
  13. package/dist/dependencyMetadata.js +67 -0
  14. package/dist/errorReporter.d.ts +6 -4
  15. package/dist/errorReporter.d.ts.map +1 -1
  16. package/dist/errorReporter.js +14 -19
  17. package/dist/form.d.ts +5 -5
  18. package/dist/form.d.ts.map +1 -1
  19. package/dist/form.js +42 -16
  20. package/dist/internal/tanstackQuery.d.ts +9 -0
  21. package/dist/internal/tanstackQuery.d.ts.map +1 -0
  22. package/dist/internal/tanstackQuery.js +204 -0
  23. package/dist/intl.d.ts +15 -0
  24. package/dist/intl.d.ts.map +1 -0
  25. package/dist/intl.js +9 -0
  26. package/dist/lib.d.ts +7 -9
  27. package/dist/lib.d.ts.map +1 -1
  28. package/dist/lib.js +35 -10
  29. package/dist/liveQueryInvalidation.d.ts +16 -0
  30. package/dist/liveQueryInvalidation.d.ts.map +1 -0
  31. package/dist/liveQueryInvalidation.js +115 -0
  32. package/dist/makeClient.d.ts +236 -336
  33. package/dist/makeClient.d.ts.map +1 -1
  34. package/dist/makeClient.js +417 -408
  35. package/dist/makeContext.d.ts.map +1 -1
  36. package/dist/makeIntl.d.ts.map +1 -1
  37. package/dist/{experimental/makeUseCommand.d.ts → makeUseCommand.d.ts} +4 -3
  38. package/dist/makeUseCommand.d.ts.map +1 -0
  39. package/dist/makeUseCommand.js +13 -0
  40. package/dist/mutate.d.ts +109 -40
  41. package/dist/mutate.d.ts.map +1 -1
  42. package/dist/mutate.js +235 -58
  43. package/dist/query.d.ts +158 -45
  44. package/dist/query.d.ts.map +1 -1
  45. package/dist/query.js +380 -122
  46. package/dist/queryLifetime.d.ts +25 -0
  47. package/dist/queryLifetime.d.ts.map +1 -0
  48. package/dist/queryLifetime.js +54 -0
  49. package/dist/routeParams.d.ts +4 -4
  50. package/dist/routeParams.d.ts.map +1 -1
  51. package/dist/routeParams.js +4 -3
  52. package/dist/runtime.d.ts +1 -14
  53. package/dist/runtime.d.ts.map +1 -1
  54. package/dist/runtime.js +2 -26
  55. package/dist/suspense.d.ts +19 -0
  56. package/dist/suspense.d.ts.map +1 -0
  57. package/dist/suspense.js +38 -0
  58. package/dist/toast.d.ts +2 -0
  59. package/dist/toast.d.ts.map +1 -0
  60. package/dist/toast.js +2 -0
  61. package/dist/withToast.d.ts +2 -0
  62. package/dist/withToast.d.ts.map +1 -0
  63. package/dist/withToast.js +2 -0
  64. package/docs/atom-query-api-redesign.md +207 -0
  65. package/docs/mutation-command-atoms.md +40 -0
  66. package/docs/query-key-invalidation.md +107 -0
  67. package/examples/streamMutation.ts +72 -0
  68. package/package.json +36 -95
  69. package/src/atomQuery.ts +600 -0
  70. package/src/commander.ts +3408 -0
  71. package/src/{experimental/confirm.ts → confirm.ts} +13 -15
  72. package/src/dependencyMetadata.ts +84 -0
  73. package/src/errorReporter.ts +65 -75
  74. package/src/form.ts +57 -21
  75. package/src/index.ts +7 -7
  76. package/src/internal/tanstackQuery.ts +308 -0
  77. package/src/intl.ts +12 -0
  78. package/src/lib.ts +49 -21
  79. package/src/liveQueryInvalidation.ts +138 -0
  80. package/src/makeClient.ts +971 -1159
  81. package/src/makeIntl.ts +2 -2
  82. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +9 -6
  83. package/src/mutate.ts +406 -153
  84. package/src/query.ts +763 -260
  85. package/src/queryLifetime.ts +74 -0
  86. package/src/routeParams.ts +7 -7
  87. package/src/runtime.ts +1 -31
  88. package/src/suspense.ts +43 -0
  89. package/src/toast.ts +1 -0
  90. package/src/withToast.ts +1 -0
  91. package/test/Mutation.test.ts +181 -24
  92. package/test/dependencyInvalidation.test.ts +444 -0
  93. package/test/dist/dependencyInvalidation.test.d.ts.map +1 -0
  94. package/test/dist/form.test.d.ts.map +1 -1
  95. package/test/dist/inactiveInvalidation.test.d.ts.map +1 -0
  96. package/test/dist/interrupt-refetch-repro.test.d.ts.map +1 -0
  97. package/test/dist/lib.test.d.ts.map +1 -0
  98. package/test/dist/liveQueryInvalidation.test.d.ts.map +1 -0
  99. package/test/dist/query-span.test.d.ts.map +1 -0
  100. package/test/dist/queryOptions.test.d.ts.map +1 -0
  101. package/test/dist/streamFinal.test.d.ts.map +1 -0
  102. package/test/dist/streamFn.test.d.ts.map +1 -0
  103. package/test/dist/stubs.d.ts +3751 -120
  104. package/test/dist/stubs.d.ts.map +1 -1
  105. package/test/dist/stubs.js +190 -30
  106. package/test/dist/suspense-regression.test.d.ts.map +1 -0
  107. package/test/dist/suspense.test.d.ts.map +1 -0
  108. package/test/form-validation-errors.test.ts +24 -19
  109. package/test/form.test.ts +25 -6
  110. package/test/inactiveInvalidation.test.ts +172 -0
  111. package/test/interrupt-refetch-repro.test.ts +625 -0
  112. package/test/lib.test.ts +240 -0
  113. package/test/liveQueryInvalidation.test.ts +108 -0
  114. package/test/makeClient.test.ts +437 -38
  115. package/test/query-span.test.ts +92 -0
  116. package/test/queryOptions.test.ts +88 -0
  117. package/test/streamFinal.test.ts +64 -0
  118. package/test/streamFn.test.ts +457 -0
  119. package/test/stubs.ts +227 -42
  120. package/test/suspense-regression.test.ts +168 -0
  121. package/test/suspense.test.ts +160 -0
  122. package/tsconfig.examples.json +20 -0
  123. package/tsconfig.json +9 -2
  124. package/tsconfig.src.json +34 -34
  125. package/tsconfig.test.json +2 -2
  126. package/vitest.config.ts +5 -5
  127. package/dist/experimental/commander.d.ts +0 -359
  128. package/dist/experimental/commander.d.ts.map +0 -1
  129. package/dist/experimental/commander.js +0 -557
  130. package/dist/experimental/confirm.d.ts.map +0 -1
  131. package/dist/experimental/confirm.js +0 -28
  132. package/dist/experimental/intl.d.ts +0 -16
  133. package/dist/experimental/intl.d.ts.map +0 -1
  134. package/dist/experimental/intl.js +0 -5
  135. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  136. package/dist/experimental/makeUseCommand.js +0 -13
  137. package/dist/experimental/toast.d.ts +0 -47
  138. package/dist/experimental/toast.d.ts.map +0 -1
  139. package/dist/experimental/toast.js +0 -41
  140. package/dist/experimental/withToast.d.ts +0 -25
  141. package/dist/experimental/withToast.d.ts.map +0 -1
  142. package/dist/experimental/withToast.js +0 -45
  143. package/eslint.config.mjs +0 -24
  144. package/src/experimental/commander.ts +0 -1835
  145. package/src/experimental/intl.ts +0 -9
  146. package/src/experimental/toast.ts +0 -66
  147. package/src/experimental/withToast.ts +0 -99
  148. package/tsconfig.json.bak +0 -12
package/src/makeClient.ts CHANGED
@@ -1,162 +1,351 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { type InvalidateOptions, type InvalidateQueryFilters, isCancelledError, type QueryObserverResult, type RefetchOptions, type UseQueryReturnType } from "@tanstack/vue-query"
3
2
  import { camelCase } from "change-case"
4
- import { Cause, Data, Effect, Exit, Layer, type ManagedRuntime, Match, Option, S, ServiceMap, Struct } from "effect-app"
5
3
  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"
4
+ import type { ExtractModuleName, HandlerInput, RequestHandlers, RequestHandlerWithInput, RequestsAny, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
5
+ import type { CauseException } from "effect-app/client/errors"
6
+ import type { InvalidationCallback } from "effect-app/client/makeClient"
7
+ import * as Context from "effect-app/Context"
8
+ import * as Effect from "effect-app/Effect"
9
+ import * as Layer from "effect-app/Layer"
10
+ import * as S from "effect-app/Schema"
11
+ import * as Cause from "effect/Cause"
12
+ import * as Data from "effect/Data"
13
+ import * as Exit from "effect/Exit"
11
14
  import { type Fiber } from "effect/Fiber"
12
- import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
13
- import { computed, type ComputedRef, onBeforeUnmount, type Ref, ref, watch, type WatchSource } from "vue"
14
- import { reportMessage } from "./errorReporter.js"
15
- import { type Commander, CommanderStatic } from "./experimental/commander.js"
16
- import { I18n } from "./experimental/intl.js"
17
- import { type CommanderResolved, makeUseCommand } from "./experimental/makeUseCommand.js"
18
- import { Toast } from "./experimental/toast.js"
19
- import { buildFieldInfoFromFieldsRoot } from "./form.js"
20
- import { reportRuntimeError } from "./lib.js"
21
- import { asResult, makeMutation, type MutationOptions, type MutationOptionsBase, mutationResultToVue, type Res, useMakeMutation } from "./mutate.js"
22
- import { type CustomUndefinedInitialQueryOptions, makeQuery } from "./query.js"
23
-
24
- const mapHandler = <A, E, R, I = void, A2 = A, E2 = E, R2 = R>(
25
- handler: Effect.Effect<A, E, R> | ((i: I) => Effect.Effect<A, E, R>),
26
- map: (self: Effect.Effect<A, E, R>, i: I) => Effect.Effect<A2, E2, R2>
27
- ) => Effect.isEffect(handler) ? map(handler, undefined as any) : (i: I) => map(handler(i), i)
28
-
29
- export interface RequestExtensions<RT, Id extends string, I, A, E, R> {
15
+ import * as Hash from "effect/Hash"
16
+ import type * as ManagedRuntime from "effect/ManagedRuntime"
17
+ import type * as Stream from "effect/Stream"
18
+ import * as Struct from "effect/Struct"
19
+ import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
20
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity"
21
+ import { type ComputedRef, effectScope, onBeforeUnmount, onScopeDispose, ref, type WatchSource } from "vue"
22
+ import { type AtomClientRuntime, invalidateAndAwait, invalidateSoft, makeAtomClientRuntime } from "./atomQuery.ts"
23
+ import { type Commander, CommanderStatic, type Progress } from "./commander.ts"
24
+ import { makeTanstackQuery, makeTanstackQueryCacheUpdater, makeTanstackQueryClient, makeTanstackQueryInvalidator } from "./internal/tanstackQuery.ts"
25
+ import { type I18n } from "./intl.ts"
26
+ import { type CommanderResolved, makeUseCommand } from "./makeUseCommand.ts"
27
+ import { atomQueryInvalidator, combineQueryInvalidators, type InvalidationEntry, makeMutation, makeStreamMutation2, type MutationOptionsBase, type QueryInvalidator, useMakeMutation } from "./mutate.ts"
28
+ import { atomQueryCacheUpdater, type AtomQueryNewOptions, combineQueryCacheUpdaters, type CustomUndefinedInitialQueryOptions, makeQuery, makeQueryAtom, makeQueryFamily, makeQueryNew, makeStreamQuery, makeStreamQueryAtom, makeStreamQueryFamily, makeStreamQueryNew, optionalAtomQueryCacheUpdater, type QueryObserverResult, type RefetchOptions, setQueryCacheUpdater, type StreamQueryAtomFamily, type SuspenseQueryView, type UseQueryReturnType } from "./query.ts"
29
+ import { makeRunPromise } from "./runtime.ts"
30
+ import { latestDefined } from "./suspense.ts"
31
+ import { type Toast } from "./toast.ts"
32
+
33
+ export type { Progress }
34
+
35
+ const useScopedSuspenseSetup = <A>(setup: () => A) => {
36
+ const scope = effectScope()
37
+ const controller = new AbortController()
38
+ const value = scope.run(setup)
39
+ if (value === undefined) {
40
+ throw new Error("Internal Error: suspense setup scope did not initialize")
41
+ }
42
+
43
+ const isMounted = ref(true)
44
+ let stopped = false
45
+ const stop = () => {
46
+ if (stopped) return
47
+ stopped = true
48
+ isMounted.value = false
49
+ controller.abort()
50
+ scope.stop()
51
+ }
52
+ onBeforeUnmount(stop)
53
+ onScopeDispose(stop)
54
+
55
+ return [value, isMounted, controller.signal] as const
56
+ }
57
+
58
+ /**
59
+ * A suspense query settled with an interrupt-only cause while its component was still MOUNTED.
60
+ *
61
+ * That combination classifies the interrupt's origin: a navigation/unmount cancel interrupts the
62
+ * suspense *fiber* (via the scope's AbortSignal) or is hidden by effect-core before it can settle —
63
+ * it never yields a still-mounted `Failure` exit. A `Failure(interrupt-only)` observed while mounted
64
+ * therefore came from the QUERY side — e.g. the server returned an RPC interrupt `Exit` (over a 200),
65
+ * or the fetch was superseded — and silencing it leaves the user staring at a blank page.
66
+ *
67
+ * Error boundaries can rely on this split: keep silencing raw interrupt-only causes (those are
68
+ * genuinely "user navigated away"), and render this error as "the request was interrupted — reload?".
69
+ */
70
+ export class SuspenseInterruptedError extends Data.TaggedError("SuspenseInterruptedError")<{
71
+ readonly originalCause: Cause.Cause<unknown>
72
+ readonly source: "suspense" | "suspenseNew"
73
+ }> {
74
+ override get message() {
75
+ return "The request was interrupted while the page was still active"
76
+ }
77
+ }
78
+
79
+ /** Structural check (by `_tag`) so consumers compiled against older published types can detect it. */
80
+ export const isSuspenseInterruptedError = (e: unknown): e is SuspenseInterruptedError =>
81
+ typeof e === "object" && e !== null && "_tag" in e && e._tag === "SuspenseInterruptedError"
82
+
83
+ // TODO: optimize - work from encoded shape directly
84
+ const projectHandler = <
85
+ I,
86
+ A,
87
+ E,
88
+ R,
89
+ SuccessSchema extends S.Top & { readonly "EncodingServices": R },
90
+ ProjSchema extends S.Top & { readonly "DecodingServices": R }
91
+ >(
92
+ handler: (i: I) => Effect.Effect<A, E, R>,
93
+ successSchema: SuccessSchema,
94
+ projectionSchema: ProjSchema
95
+ ) => {
96
+ const encode = S.encodeUnknownEffect(successSchema)
97
+ const decode = S.decodeUnknownEffect(projectionSchema)
98
+ return (i: I) => handler(i).pipe(Effect.flatMap(encode), Effect.flatMap(decode))
99
+ }
100
+
101
+ const projectionSchemaHash = (schema: S.Top) => String(Hash.hash(schema.ast))
102
+
103
+ export interface CommandRequestExtensions<RT, Id extends string, I, A, E, R> {
30
104
  /** Defines a Command based on this call, taking the `id` of the call as the `id` of the Command.
31
105
  * The Request function will be taken as the first member of the Command, the Command required input will be the Request input.
32
106
  * see Command.wrap for details */
33
- wrap: Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
107
+ wrap: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
108
+ options?: Commander.FnOptions<Id, I18nKey, State>
109
+ ) => Commander.CommanderWrap<RT, Id, I18nKey, State, I, A, E, R>
34
110
  /** Defines a Command based on this call, taking the `id` of the call as the `id` of the Command.
35
111
  * see Command.fn for details */
36
- fn: Commander.CommanderFn<RT, Id, Id, undefined>
112
+ fn: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
113
+ options?: Commander.FnOptions<Id, I18nKey, State>
114
+ ) => Commander.CommanderFn<RT, Id, I18nKey, State>
37
115
  }
38
116
 
39
117
  /** my other doc */
40
- export interface RequestExtWithInput<
118
+ export interface RequestExt<
41
119
  RT,
42
120
  Id extends string,
43
121
  I,
44
122
  A,
45
123
  E,
46
124
  R
47
- > extends Commander.CommandContextLocal<Id, Id>, RequestExtensions<RT, Id, I, A, E, R> {
125
+ > extends
126
+ Commander.CommandContextLocal<Id, Id>,
127
+ Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>,
128
+ CommandRequestExtensions<RT, Id, I, A, E, R>
129
+ {
48
130
  /**
49
- * Request the endpoint with input
131
+ * Send the request to the endpoint and return the raw Effect response.
132
+ * This does not perform query cache invalidation.
50
133
  */
51
- (i: I): Effect.Effect<A, E, R>
134
+ request: (i: I) => Effect.Effect<A, E, R>
52
135
  }
53
136
 
54
- /**
55
- * Request the endpoint
56
- */
57
- export interface RequestExt<
58
- RT,
59
- Id extends string,
60
- A,
61
- E,
62
- R
63
- > extends
64
- Commander.CommandContextLocal<Id, Id>,
65
- Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>,
66
- RequestExtensions<RT, Id, void, A, E, R>,
67
- Effect.Effect<A, E, R>
68
- {
137
+ export type CommandRequestWithExtensions<RT, Req> = Req extends
138
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id> ? RequestExt<RT, Id, I, A, E, R>
139
+ : never
140
+
141
+ export interface QueryExtensions<I, A, E, R> {
142
+ /**
143
+ * Send the request to the endpoint and return the raw Effect response.
144
+ * This does not set up query state tracking.
145
+ */
146
+ request: (i: I) => Effect.Effect<A, E, R>
69
147
  }
70
148
 
71
- export type RequestWithExtensions<RT, Req> = Req extends
72
- RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id>
73
- ? RequestExtWithInput<RT, Id, I, A, E, R>
74
- : Req extends RequestHandler<infer A, infer E, infer R, infer _Request, infer Id> ? RequestExt<RT, Id, A, E, R>
149
+ export type QueryRequestWithExtensions<Req> = Req extends
150
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer _Id> ? QueryExtensions<I, A, E, R>
151
+ : never
152
+
153
+ type QueryHandler<Req> = Req extends
154
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
155
+ ? Request["type"] extends "query" ? RequestHandlerWithInput<I, A, E, R, Request, Id> : never
156
+ : never
157
+
158
+ type CommandHandler<Req> = Req extends
159
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
160
+ ? Request["type"] extends "command" ? RequestHandlerWithInput<I, A, E, R, Request, Id> : never
161
+ : never
162
+
163
+ type QueryStreamHandler<Req> = Req extends
164
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer Final>
165
+ ? [Request["stream"], Request["type"]] extends [true, "query"]
166
+ ? RequestStreamHandlerWithInput<I, A, E, R, Request, Id, Final>
167
+ : never
168
+ : never
169
+
170
+ type CommandStreamHandler<Req> = Req extends
171
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer Final>
172
+ ? [Request["stream"], Request["type"]] extends [true, "command"]
173
+ ? RequestStreamHandlerWithInput<I, A, E, R, Request, Id, Final>
174
+ : never
75
175
  : never
76
176
 
77
177
  export interface MutationExtensions<RT, Id extends string, I, A, E, R> {
78
178
  /** Defines a Command based on this mutation, taking the `id` of the mutation as the `id` of the Command.
79
179
  * The Mutation function will be taken as the first member of the Command, the Command required input will be the Mutation input.
80
180
  * see Command.wrap for details */
81
- wrap: Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
82
- /** Defines a Command based on this mutation, taking the `id` of the mutation as the `id` of the Command.
181
+ wrap: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
182
+ options?: Commander.FnOptions<Id, I18nKey, State>
183
+ ) => Commander.CommanderWrap<RT, Id, I18nKey, State, I, A, E, R>
184
+ /** Defines a Command based on this call, taking the `id` of the mutation as the `id` of the Command.
83
185
  * see Command.fn for details */
84
- fn: Commander.CommanderFn<RT, Id, Id, undefined>
85
- }
86
-
87
- /** my other doc */
88
- export interface MutationExtWithInput<
89
- RT,
90
- Id extends string,
91
- I,
92
- A,
93
- E,
94
- R
95
- > extends Commander.CommandContextLocal<Id, Id>, MutationExtensions<RT, Id, I, A, E, R> {
96
- /**
97
- * Call the endpoint with input
98
- * Invalidate queries based on namespace of this mutation.
99
- * Do not use for queries.
100
- */
101
- (i: I): Effect.Effect<A, E, R>
186
+ fn: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
187
+ options?: Commander.FnOptions<Id, I18nKey, State>
188
+ ) => Commander.CommanderFn<RT, Id, I18nKey, State>
102
189
  }
103
190
 
104
191
  /**
105
- * Call the endpoint
106
- * Invalidate queries based on namespace of this mutation.
107
- * Do not use for queries.
192
+ * Send the request to the endpoint and return the raw Effect response.
193
+ * Also invalidates query caches using the request namespace by default.
194
+ * Namespace invalidation targets parent namespace keys
195
+ * (for example `$project/$configuration.get` invalidates `$project`).
196
+ * Override invalidation in client options via `queryInvalidation`.
197
+ *
198
+ * Pass `options` to attach a `select` Effect that runs after the mutation
199
+ * succeeds (its output is returned to the caller) and/or override the default
200
+ * `queryInvalidation`.
201
+ *
202
+ * When `I = void` the input argument may be omitted.
108
203
  */
109
204
  export interface MutationExt<
110
205
  RT,
111
206
  Id extends string,
207
+ I,
112
208
  A,
113
209
  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
- {
210
+ R,
211
+ EA = unknown
212
+ > extends MutationExtensions<RT, Id, I, A, E, R> {
213
+ <B = A, E2 = never, R2 = never>(
214
+ input: I,
215
+ options?: MutationOptionsBase<A, B, E2, R2>
216
+ ): Effect.Effect<B, E | E2, R | R2>
217
+
218
+ project: <ProjSchema extends S.Top>(
219
+ schema: EA extends ProjSchema["Encoded"] ? ProjSchema : never
220
+ ) => MutationExt<
221
+ RT,
222
+ Id,
223
+ I,
224
+ ProjSchema["Type"],
225
+ E | S.SchemaError,
226
+ R | ProjSchema["DecodingServices"],
227
+ ProjSchema["Encoded"]
228
+ >
121
229
  }
122
230
 
123
231
  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>
232
+ RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
233
+ ? MutationExt<RT, Id, I, A, E, R, Request["success"]["Encoded"]>
234
+ : never
235
+
236
+ /**
237
+ * The `streamFn` builder for a stream-type request handler, using the stream-specific overloads.
238
+ */
239
+ export type StreamFnStreamExtension<RT, Req> = Req extends
240
+ RequestStreamHandlerWithInput<infer _I, infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
241
+ ? <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
242
+ options?: Commander.FnOptions<Id, I18nKey, State>
243
+ ) => Commander.StreamGen<RT, Id, I18nKey, State> & Commander.NonGenStream<RT, Id, I18nKey, State>
244
+ : never
245
+
246
+ /**
247
+ * `mutate` factory — wraps per-invocation invalidation scaffolding
248
+ * into the stream itself (via `Stream.unwrap`) for use with `streamFn` combinators.
249
+ */
250
+ export type StreamMutation2WithExtensions<RT, Req> = Req extends
251
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer _Final> ?
252
+ & ((input: I) => Stream.Stream<A, E, R>)
253
+ & {
254
+ readonly id: Id
255
+ readonly wrap: <I18nKey extends string = Id, State extends Commander.IntlRecord | undefined = undefined>(
256
+ options?: Commander.FnOptions<Id, I18nKey, State>
257
+ ) => Commander.StreamerWrap<RT, Id, I18nKey, State, I, A, E, R>
258
+ }
127
259
  : never
128
260
 
129
261
  // we don't really care about the RT, as we are in charge of ensuring runtime safety anyway
130
262
  // eslint-disable-next-line unused-imports/no-unused-vars
131
263
  declare const useQuery_: QueryImpl<any>["useQuery"]
132
264
  // eslint-disable-next-line unused-imports/no-unused-vars
265
+ declare const useQueryNew_: QueryImpl<any>["useQueryNew"]
266
+ // eslint-disable-next-line unused-imports/no-unused-vars
267
+ declare const useQueryAtom_: QueryImpl<any>["useQueryAtom"]
268
+ // eslint-disable-next-line unused-imports/no-unused-vars
269
+ declare const useQueryFamily_: QueryImpl<any>["useQueryFamily"]
270
+ // eslint-disable-next-line unused-imports/no-unused-vars
271
+ declare const useSuspenseQueryNew_: QueryImpl<any>["useSuspenseQueryNew"]
272
+ // eslint-disable-next-line unused-imports/no-unused-vars
133
273
  declare const useSuspenseQuery_: QueryImpl<any>["useSuspenseQuery"]
274
+ // eslint-disable-next-line unused-imports/no-unused-vars
275
+ declare const useStreamQuery_: QueryImpl<any>["useStreamQuery"]
276
+ // eslint-disable-next-line unused-imports/no-unused-vars
277
+ declare const useStreamQueryFamily_: QueryImpl<any>["useStreamQueryFamily"]
278
+ // eslint-disable-next-line unused-imports/no-unused-vars
279
+ declare const useStreamQueryAtom_: QueryImpl<any>["useStreamQueryAtom"]
280
+ // eslint-disable-next-line unused-imports/no-unused-vars
281
+ declare const useStreamQueryNew_: QueryImpl<any>["useStreamQueryNew"]
282
+
283
+ export interface ProjectResult<RT, I, B, E, R, Request extends Req, Id extends string> {
284
+ request: (i: I) => Effect.Effect<B, E, R>
285
+ family: Exclude<R, RT> extends never ? ReturnType<typeof useQueryFamily_<I, E, B, Request, Id>>
286
+ : MissingDependencies<RT, R> & {}
287
+ query: Exclude<R, RT> extends never ? ReturnType<typeof useQuery_<I, E, B, Request, Id>>
288
+ : MissingDependencies<RT, R> & {}
289
+ queryNew: Exclude<R, RT> extends never ? ReturnType<typeof useQueryNew_<I, E, B, Request, Id>>
290
+ : MissingDependencies<RT, R> & {}
291
+ suspense: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQuery_<I, E, B, Request, Id>>
292
+ : MissingDependencies<RT, R> & {}
293
+ suspenseNew: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQueryNew_<I, E, B, Request, Id>>
294
+ : MissingDependencies<RT, R> & {}
295
+ atom: Exclude<R, RT> extends never ? ReturnType<typeof useQueryAtom_<I, E, B, Request, Id>>
296
+ : MissingDependencies<RT, R> & {}
297
+ }
298
+
299
+ export type QueryProjection<RT, HandlerReq> = HandlerReq extends
300
+ RequestHandlerWithInput<infer I, infer _A, infer E, infer R, infer Request, infer Id>
301
+ ? Request["type"] extends "query" ? {
302
+ project: <ProjSchema extends S.Top>(
303
+ schema: Request["success"]["Encoded"] extends ProjSchema["Encoded"] ? ProjSchema : never
304
+ ) => ProjectResult<
305
+ RT,
306
+ I,
307
+ ProjSchema["Type"],
308
+ E | S.SchemaError,
309
+ R | ProjSchema["DecodingServices"],
310
+ Request,
311
+ Id
312
+ >
313
+ }
314
+ : {}
315
+ : {}
134
316
 
135
- export interface QueriesWithInput<Request extends Req, Id extends string, I, A, E> {
317
+ export interface QueryResultExtensions<Request extends Req, Id extends string, I, A, E> {
136
318
  /**
137
- * Effect results are passed to the caller, including errors.
319
+ * Read helper for query requests.
320
+ * Runs as a tracked Vue Query and returns reactive state.
321
+ * Queries read state and should not be used to mutate it.
322
+ * When `I = void` the input argument may be omitted.
138
323
  */
139
324
  query: ReturnType<typeof useQuery_<I, E, A, Request, Id>>
325
+ /**
326
+ * Atom-native query helper with object return shape.
327
+ * Additive migration surface; `.query()` remains the compatibility tuple API.
328
+ */
329
+ queryNew: ReturnType<typeof useQueryNew_<I, E, A, Request, Id>>
140
330
  // TODO or suspense as Option?
141
331
  /**
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.
332
+ * Like `.query`, but returns a Promise for setup-time awaiting.
333
+ * Use this when integrating with Vue Suspense / error boundaries.
145
334
  */
146
335
  suspense: ReturnType<typeof useSuspenseQuery_<I, E, A, Request, Id>>
147
- }
148
- export interface QueriesWithoutInput<Request extends Req, Id extends string, A, E> {
149
336
  /**
150
- * Effect results are passed to the caller, including errors.
337
+ * Promise-based setup helper with object return shape.
338
+ * Additive migration surface; `.suspense()` remains the compatibility tuple API.
151
339
  */
152
- query: ReturnType<typeof useQuery_<E, A, Request, Id>>
153
- // TODO or suspense as Option?
340
+ suspenseNew: ReturnType<typeof useSuspenseQueryNew_<I, E, A, Request, Id>>
154
341
  /**
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.
342
+ * Raw query atom for composition outside Vue refs.
343
+ */
344
+ atom: ReturnType<typeof useQueryAtom_<I, E, A, Request, Id>>
345
+ /**
346
+ * Raw query atom family for composing query graphs before choosing a Vue observer.
158
347
  */
159
- suspense: ReturnType<typeof useSuspenseQuery_<E, A, Request, Id>>
348
+ family: ReturnType<typeof useQueryFamily_<I, E, A, Request, Id>>
160
349
  }
161
350
 
162
351
  export type MissingDependencies<RT, R> = {
@@ -166,184 +355,59 @@ export type MissingDependencies<RT, R> = {
166
355
 
167
356
  export type Queries<RT, Req> = Req extends
168
357
  RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
169
- ? Exclude<R, RT> extends never ? QueriesWithInput<Request, Id, I, A, E>
170
- : {
171
- query: MissingDependencies<RT, R> & {}
172
- suspense: MissingDependencies<RT, R> & {}
173
- }
174
- : Req extends RequestHandler<infer A, infer E, infer R, infer Request, infer Id>
175
- ? Exclude<R, RT> extends never ? QueriesWithoutInput<Request, Id, A, E>
176
- : { query: MissingDependencies<RT, R> & {}; suspense: MissingDependencies<RT, R> & {} }
358
+ ? Request["type"] extends "query" ? Exclude<R, RT> extends never ? QueryResultExtensions<Request, Id, I, A, E>
359
+ : {
360
+ atom: MissingDependencies<RT, R> & {}
361
+ family: MissingDependencies<RT, R> & {}
362
+ query: MissingDependencies<RT, R> & {}
363
+ queryNew: MissingDependencies<RT, R> & {}
364
+ suspense: MissingDependencies<RT, R> & {}
365
+ suspenseNew: MissingDependencies<RT, R> & {}
366
+ }
367
+ : never
177
368
  : never
178
369
 
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
370
+ export interface StreamQueryExtensions<Request extends Req, Id extends string, I, A, E> {
371
+ atom: ReturnType<typeof useStreamQueryAtom_<I, E, A, Request, Id>>
372
+ family: StreamQueryAtomFamily<I, A, E>
373
+ /**
374
+ * Stream helper for query-stream requests.
375
+ * Legacy compatibility helper. Collects the whole stream into an array before
376
+ * publishing data.
377
+ * When `I = void` the input argument may be omitted.
378
+ */
379
+ query: ReturnType<typeof useStreamQuery_<I, E, A, Request, Id>>
380
+ /**
381
+ * Atom-native stream query helper. Exposes incremental pull state and a `pull`
382
+ * command instead of collecting the whole stream first.
383
+ */
384
+ queryNew: ReturnType<typeof useStreamQueryNew_<I, E, A, Request, Id>>
249
385
  }
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>
386
+ export type StreamQueries<RT, HandlerReq> = HandlerReq extends
387
+ RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer _Final>
388
+ ? Exclude<R, RT> extends never ? StreamQueryExtensions<Request, Id, I, A, E>
389
+ : {
390
+ atom: MissingDependencies<RT, R> & {}
391
+ family: MissingDependencies<RT, R> & {}
392
+ query: MissingDependencies<RT, R> & {}
393
+ queryNew: MissingDependencies<RT, R> & {}
286
394
  }
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
- }
395
+ : never
299
396
 
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
- }
397
+ const _useMutation = makeMutation(atomQueryInvalidator)
311
398
 
312
- const extra = {
313
- action,
314
- message: `Unexpected Error trying to ${action}`
315
- }
316
- yield* reportRuntimeError(cause, extra)
317
-
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
- )
399
+ const wrapWithSpan = (self: { id: string }, mut: any) => {
400
+ const span = (eff: Effect.Effect<any, any, any>) =>
401
+ Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })(eff)
402
+ return (input: any, options?: MutationOptionsBase) => span(mut(input, options))
337
403
  }
338
404
 
339
- const _useMutation = makeMutation()
340
-
341
405
  /**
342
406
  * Pass an Effect or a function that returns an Effect, e.g from a client action
343
407
  * Executes query cache invalidation based on default rules or provided option.
344
408
  * adds a span with the mutation id
345
409
  */
346
- export const useMutation: typeof _useMutation = <
410
+ export const useMutation: typeof _useMutation = (<
347
411
  I,
348
412
  E,
349
413
  A,
@@ -351,25 +415,21 @@ export const useMutation: typeof _useMutation = <
351
415
  Request extends Req,
352
416
  Name extends string
353
417
  >(
354
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
355
- options?: MutationOptionsBase
418
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
356
419
  ) =>
357
420
  Object.assign(
358
- mapHandler(
359
- _useMutation(self as any, options),
360
- Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })
361
- ) as any,
421
+ wrapWithSpan(self, _useMutation(self as any)),
362
422
  { id: self.id }
363
- )
423
+ )) as any
364
424
 
365
425
  /**
366
426
  * Pass an Effect or a function that returns an Effect, e.g from a client action
367
427
  * Executes query cache invalidation based on default rules or provided option.
368
428
  * adds a span with the mutation id
369
429
  */
370
- export const useMutationInt = (): typeof _useMutation => {
371
- const _useMutation = useMakeMutation()
372
- return <
430
+ export const useMutationInt = (queryInvalidator: QueryInvalidator): typeof _useMutation => {
431
+ const _useMutation = useMakeMutation(queryInvalidator)
432
+ return (<
373
433
  I,
374
434
  E,
375
435
  A,
@@ -377,721 +437,65 @@ export const useMutationInt = (): typeof _useMutation => {
377
437
  Request extends Req,
378
438
  Name extends string
379
439
  >(
380
- self: RequestHandlerWithInput<I, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>,
381
- options?: MutationOptionsBase
440
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
382
441
  ) =>
383
442
  Object.assign(
384
- mapHandler(
385
- _useMutation(self as any, options),
386
- Effect.withSpan(`mutation ${self.id}`, {}, { captureStackTrace: false })
387
- ) as any,
443
+ wrapWithSpan(self, _useMutation(self as any)),
388
444
  { id: self.id }
389
- )
445
+ )) as any
390
446
  }
391
447
 
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
- ) {}
448
+ export type ClientFrom<M extends RequestsAny> = RequestHandlers<never, never, M, ExtractModuleName<M>>
398
449
 
450
+ export interface MakeClientOptions {
399
451
  /**
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
452
+ * Selects the engine behind legacy `.query()` / `.suspense()` helpers.
453
+ * Atom-native `.atom()` / `.family()` / `.queryNew()` / `.suspenseNew()` always use Atom.
403
454
  */
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)
455
+ readonly legacyQueryEngine?: "atom" | "tanstack"
456
+ }
672
457
 
673
- return tuple(
674
- a,
675
- handleRequestWithToast(b as any, self.id, action, options)
676
- )
677
- }
678
- //
458
+ export class QueryImpl<R> {
459
+ readonly getRuntime: () => Context.Context<R>
679
460
 
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
- )
461
+ constructor(
462
+ getRuntime: () => Context.Context<R>,
463
+ getAtomRt: () => AtomClientRuntime,
464
+ legacyUseQuery?: ReturnType<typeof makeQuery<R>>
465
+ ) {
466
+ this.getRuntime = getRuntime
467
+ this.useQuery = legacyUseQuery ?? makeQuery(this.getRuntime, getAtomRt)
468
+ this.useQueryNew = makeQueryNew(this.getRuntime, getAtomRt)
469
+ this.useQueryAtom = makeQueryAtom(this.getRuntime, getAtomRt)
470
+ this.useQueryFamily = makeQueryFamily(this.getRuntime, getAtomRt)
471
+ this.useStreamQuery = makeStreamQuery(this.getRuntime, getAtomRt)
472
+ this.useStreamQueryFamily = makeStreamQueryFamily(this.getRuntime, getAtomRt)
473
+ this.useStreamQueryAtom = makeStreamQueryAtom(this.getRuntime, getAtomRt)
474
+ this.useStreamQueryNew = makeStreamQueryNew(this.getRuntime, getAtomRt)
751
475
  }
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
476
  /**
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
477
+ * Effect results are passed to the caller, including errors.
478
+ * @deprecated use client helpers instead (.query())
874
479
  */
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)
480
+ readonly useQuery: ReturnType<typeof makeQuery<R>>
941
481
 
942
- type MH = NonNullable<NonNullable<typeof options>["mapHandler"]>
943
- const mh = options?.mapHandler ?? identity as MH
482
+ readonly useQueryNew: ReturnType<typeof makeQueryNew<R>>
944
483
 
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
- )
484
+ readonly useQueryAtom: ReturnType<typeof makeQueryAtom<R>>
951
485
 
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
- }
486
+ readonly useQueryFamily: ReturnType<typeof makeQueryFamily<R>>
962
487
 
963
488
  /**
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
489
+ * Stream results are accumulated as an array of chunks and returned as reactive state.
490
+ * @deprecated use client helpers instead (.query())
967
491
  */
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)
492
+ readonly useStreamQuery: ReturnType<typeof makeStreamQuery<R>>
998
493
 
999
- return tuple(
1000
- computed(() => mutationResultToVue(a.value)),
1001
- b
1002
- ) as any
1003
- }
494
+ readonly useStreamQueryFamily: ReturnType<typeof makeStreamQueryFamily<R>>
1004
495
 
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
- )
496
+ readonly useStreamQueryAtom: ReturnType<typeof makeStreamQueryAtom<R>>
1050
497
 
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"]>
1085
-
1086
- export class QueryImpl<R> {
1087
- constructor(readonly getRuntime: () => ServiceMap.ServiceMap<R>) {
1088
- this.useQuery = makeQuery(this.getRuntime)
1089
- }
1090
- /**
1091
- * Effect results are passed to the caller, including errors.
1092
- * @deprecated use client helpers instead (.query())
1093
- */
1094
- readonly useQuery: ReturnType<typeof makeQuery<R>>
498
+ readonly useStreamQueryNew: ReturnType<typeof makeStreamQueryNew<R>>
1095
499
 
1096
500
  /**
1097
501
  * The difference with useQuery is that this function will return a Promise you can await in the Setup,
@@ -1104,22 +508,21 @@ export class QueryImpl<R> {
1104
508
  * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1105
509
  * which ensures that either there always is a latest value, or an error occurs on load.
1106
510
  * So that Suspense and error boundaries can be used.
1107
- * @deprecated use client helpers instead (.suspense())
511
+ * When `I = void` the input argument may be omitted.
1108
512
  */
1109
513
  <
514
+ I,
1110
515
  E,
1111
516
  A,
1112
517
  Request extends Req,
1113
518
  Name extends string
1114
519
  >(
1115
- self: RequestHandler<A, E, R, Request, Name>
520
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
1116
521
  ): {
1117
- /**
1118
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1119
- * which ensures that either there always is a latest value, or an error occurs on load.
1120
- * So that Suspense and error boundaries can be used.
1121
- */
1122
- <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, E, TData>): Promise<
522
+ <TData = A>(
523
+ arg: I | WatchSource<I>,
524
+ options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
525
+ ): Promise<
1123
526
  readonly [
1124
527
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
1125
528
  ComputedRef<TData>,
@@ -1130,221 +533,537 @@ export class QueryImpl<R> {
1130
533
  ]
1131
534
  >
1132
535
  }
1133
- /**
1134
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1135
- * which ensures that either there always is a latest value, or an error occurs on load.
1136
- * So that Suspense and error boundaries can be used.
1137
- */
536
+ } = <I, E, A, Request extends Req, Name extends string>(
537
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
538
+ ) => {
539
+ const runPromise = makeRunPromise(this.getRuntime())
540
+ const q = this.useQuery(self)
541
+ return <TData = A>(
542
+ argOrOptions: I | WatchSource<I>,
543
+ options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
544
+ ) => {
545
+ const [
546
+ [resultRef, latestDefinedRef, fetch, uqrt],
547
+ isMounted,
548
+ signal
549
+ ] = useScopedSuspenseSetup(() => {
550
+ const [resultRef, latestRef, fetch, uqrt] = q<TData>(argOrOptions, options)
551
+ const latestDefinedRef = latestDefined(() => latestRef.value, "suspense")
552
+ return [resultRef, latestDefinedRef, fetch, uqrt] as const
553
+ })
554
+
555
+ // @effect-diagnostics effect/missingEffectError:off
556
+ const eff = Effect.gen(function*() {
557
+ const exit = yield* uqrt.awaitResult().pipe(Effect.exit)
558
+ if (!isMounted.value) {
559
+ return yield* Effect.interrupt
560
+ }
561
+ if (Exit.isFailure(exit)) {
562
+ // Still mounted, so this interrupt came from the query side (e.g. a server RPC interrupt
563
+ // Exit), not from navigation/unmount — surface it as a renderable typed error instead of a
564
+ // raw interrupt the error boundary would silence into a blank page.
565
+ if (Cause.hasInterruptsOnly(exit.cause)) {
566
+ return yield* new SuspenseInterruptedError({ originalCause: exit.cause, source: "suspense" })
567
+ }
568
+ return yield* Exit.failCause(exit.cause)
569
+ }
570
+
571
+ return [resultRef, latestDefinedRef, fetch, uqrt] as const
572
+ })
573
+
574
+ return runPromise(eff, { signal })
575
+ }
576
+ }
577
+
578
+ readonly useSuspenseQueryNew: {
1138
579
  <
1139
- Arg,
580
+ I,
1140
581
  E,
1141
582
  A,
1142
583
  Request extends Req,
1143
584
  Name extends string
1144
585
  >(
1145
- self: RequestHandlerWithInput<Arg, A, E, R, Request, Name>
586
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
1146
587
  ): {
1147
- /**
1148
- * The difference with useQuery is that this function will return a Promise you can await in the Setup,
1149
- * which ensures that either there always is a latest value, or an error occurs on load.
1150
- * So that Suspense and error boundaries can be used.
1151
- */
1152
- <TData = A>(arg: Arg | WatchSource<Arg>, options?: CustomUndefinedInitialQueryOptions<A, E, TData>): Promise<
1153
- readonly [
1154
- ComputedRef<AsyncResult.AsyncResult<TData, E>>,
1155
- ComputedRef<TData>,
1156
- (
1157
- options?: RefetchOptions
1158
- ) => Effect.Effect<QueryObserverResult<TData, E>>,
1159
- UseQueryReturnType<any, any>
1160
- ]
1161
- >
588
+ <TData = A>(
589
+ arg: I | WatchSource<I>,
590
+ options?: AtomQueryNewOptions<A, TData>
591
+ ): Promise<SuspenseQueryView<TData, E>>
1162
592
  }
1163
- } = <Arg, E, A, Request extends Req, Name extends string>(
1164
- self: RequestHandlerWithInput<Arg, A, E, R, Request, Name> | RequestHandler<A, E, R, Request, Name>
593
+ } = <I, E, A, Request extends Req, Name extends string>(
594
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
1165
595
  ) => {
1166
- const runPromise = Effect.runPromiseWith(this.getRuntime())
1167
- const q = this.useQuery(self as any) as any
1168
- return (argOrOptions?: any, options?: any) => {
1169
- const [resultRef, latestRef, fetch, uqrt] = q(argOrOptions, { ...options, suspense: true } // experimental_prefetchInRender: true }
1170
- )
1171
-
1172
- const isMounted = ref(true)
1173
- onBeforeUnmount(() => {
1174
- isMounted.value = false
596
+ const runPromise = makeRunPromise(this.getRuntime())
597
+ const q = this.useQueryNew(self)
598
+ return <TData = A>(
599
+ argOrOptions: I | WatchSource<I>,
600
+ options?: AtomQueryNewOptions<A, TData>
601
+ ) => {
602
+ const [{ view, data }, isMounted, signal] = useScopedSuspenseSetup(() => {
603
+ const view = q<TData>(argOrOptions, options)
604
+ const data = latestDefined(() => view.data.value, "suspenseNew")
605
+ return { view, data }
1175
606
  })
1176
607
 
1177
608
  // @effect-diagnostics effect/missingEffectError:off
1178
609
  const eff = Effect.gen(function*() {
1179
- // we want to throw on error so that we can catch cancelled error and skip handling it
1180
- // what's the difference with just calling `fetch` ?
1181
- // we will receive a CancelledError which we will have to ignore in our ErrorBoundary, otherwise the user ends up on an error page even if the user e.g cancelled a navigation
1182
- const r = yield* Effect.tryPromise(() => uqrt.suspense()).pipe(
1183
- Effect.catchTag("UnknownError", (err) =>
1184
- isCancelledError(err.cause)
1185
- ? Effect.interrupt
1186
- : Effect.die(err.cause))
1187
- )
610
+ const exit = yield* view.awaitResult().pipe(Effect.exit)
1188
611
  if (!isMounted.value) {
1189
612
  return yield* Effect.interrupt
1190
613
  }
1191
- const result = resultRef.value
1192
- if (AsyncResult.isInitial(result)) {
1193
- console.error("Internal Error: Promise should be resolved already", {
1194
- self,
1195
- argOrOptions,
1196
- options,
1197
- r,
1198
- resultRef
1199
- })
1200
- return yield* Effect.die(
1201
- "Internal Error: Promise should be resolved already"
1202
- )
1203
- }
1204
- if (AsyncResult.isFailure(result)) {
1205
- return yield* Exit.failCause(result.cause)
614
+ if (Exit.isFailure(exit)) {
615
+ // Still mounted → query-side interrupt (see SuspenseInterruptedError) — render, don't silence.
616
+ if (Cause.hasInterruptsOnly(exit.cause)) {
617
+ return yield* new SuspenseInterruptedError({ originalCause: exit.cause, source: "suspenseNew" })
618
+ }
619
+ return yield* Exit.failCause(exit.cause)
1206
620
  }
1207
621
 
1208
- return [resultRef, latestRef, fetch, uqrt] as const
622
+ const fetch = (_options?: RefetchOptions) => view.refetch()
623
+ const handle = {
624
+ awaitResult: view.awaitResult,
625
+ refetch: view.refetch,
626
+ refresh: view.refresh,
627
+ registry: view.registry,
628
+ atom: view.atom
629
+ }
630
+ return Object.assign(
631
+ [
632
+ view.result,
633
+ data,
634
+ fetch,
635
+ handle
636
+ ] as const,
637
+ {
638
+ ...view,
639
+ data
640
+ }
641
+ )
1209
642
  })
1210
643
 
1211
- return runPromise(eff)
644
+ return runPromise(eff, { signal })
1212
645
  }
1213
646
  }
1214
647
  }
1215
648
 
1216
649
  // 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>())
650
+ const managedRuntimeRt = <A, E>(mrt: ManagedRuntime.ManagedRuntime<A, E>) => mrt.runSync(Effect.context<A>())
1218
651
 
1219
652
  type Base = I18n | Toast
1220
- type Mix = ApiClientFactory | Commander | LegacyMutation | Base
653
+ type Mix = ApiClientFactory | Commander | Base
654
+
655
+ type InvalidationResources = Record<string, Record<string, unknown>>
656
+ type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I
657
+ : never
658
+
659
+ type CommandInvalidationResources<Req> = Req extends {
660
+ readonly type: "command"
661
+ readonly "~invalidationResources"?: infer Resources
662
+ } ? NonNullable<Resources> extends InvalidationResources ? NonNullable<Resources> : never
663
+ : Req extends {
664
+ readonly type: "command"
665
+ readonly config?: infer Config
666
+ } ? Config extends {
667
+ readonly invalidationResources?: infer LegacyResources
668
+ } ? NonNullable<LegacyResources> extends InvalidationResources ? NonNullable<LegacyResources> : never
669
+ : Config extends {
670
+ readonly invalidatesQueries?: InvalidationCallback<infer LegacyResources, any, any, any>
671
+ } ? NonNullable<LegacyResources> extends InvalidationResources ? NonNullable<LegacyResources> : never
672
+ : never
673
+ : never
674
+
675
+ type InvalidationResourcesForUnion<M extends RequestsAny> = {
676
+ [K in keyof M]: CommandInvalidationResources<M[K]>
677
+ }[keyof M]
678
+
679
+ type InvalidationResourcesFor<M extends RequestsAny> = [InvalidationResourcesForUnion<M>] extends [never] ? never
680
+ : UnionToIntersection<InvalidationResourcesForUnion<M>> extends infer R ? R extends InvalidationResources ? R
681
+ : never
682
+ : never
683
+
684
+ type QueryInvalidationFactory<M extends RequestsAny> = (client: ClientFrom<M>) => QueryInvalidation<M>
685
+
686
+ type StrictResourcesArg<Shape, Actual extends Shape = Shape> =
687
+ & Actual
688
+ & Record<Exclude<keyof Actual, keyof Shape>, never>
689
+
690
+ type ClientForArgs<
691
+ M extends RequestsAny,
692
+ Resources extends InvalidationResourcesFor<M> = InvalidationResourcesFor<M>
693
+ > = [InvalidationResourcesFor<M>] extends [never] ? [
694
+ queryInvalidation?: QueryInvalidationFactory<M>,
695
+ invalidationResources?: StrictResourcesArg<
696
+ InvalidationResourcesFor<M>,
697
+ Resources
698
+ >
699
+ ]
700
+ : [
701
+ queryInvalidation: QueryInvalidationFactory<M> | undefined,
702
+ invalidationResources: StrictResourcesArg<
703
+ InvalidationResourcesFor<M>,
704
+ Resources
705
+ >
706
+ ]
707
+
708
+ const makeResolvedAtomQueryInvalidator = <R>(getContext: () => Context.Context<R>): QueryInvalidator => {
709
+ let reactivity: Context.Service.Shape<typeof Reactivity.Reactivity> | undefined
710
+ const getReactivity = () => {
711
+ if (reactivity !== undefined) return reactivity
712
+ const service = Context.getOrUndefined(getContext(), Reactivity.Reactivity)
713
+ if (service === undefined) {
714
+ throw new Error("Reactivity service is missing from the client runtime")
715
+ }
716
+ return reactivity = service
717
+ }
718
+
719
+ return {
720
+ invalidateAndAwait: (keys) =>
721
+ invalidateAndAwait(keys).pipe(
722
+ Effect.provideService(Reactivity.Reactivity, getReactivity())
723
+ ),
724
+ invalidateSoft: (keys) =>
725
+ invalidateSoft(keys).pipe(
726
+ Effect.provideService(Reactivity.Reactivity, getReactivity())
727
+ )
728
+ }
729
+ }
730
+
1221
731
  export const makeClient = <RT_, RTHooks>(
1222
732
  // global, but only accessible after startup has completed
1223
733
  getBaseMrt: () => ManagedRuntime.ManagedRuntime<RT_ | Mix, never>,
1224
734
  clientFor_: ReturnType<typeof ApiClientFactory["makeFor"]>,
1225
- rtHooks: Layer.Layer<RTHooks, never, Mix>
735
+ rtHooks: Layer.Layer<RTHooks, never, Mix>,
736
+ options?: MakeClientOptions
1226
737
  ) => {
1227
738
  type RT = RT_ | Mix
1228
- const getRt = Effect.services<RT>()
1229
739
  const getBaseRt = () => managedRuntimeRt(getBaseMrt())
1230
740
  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
741
  let cmd: Effect.Success<typeof makeCommand>
1237
742
  const useCommand = () => cmd ??= getBaseMrt().runSync(makeCommand)
1238
- let mut: Effect.Success<typeof makeMutation>
1239
- const getMutation = () => mut ??= getBaseMrt().runSync(makeMutation)
1240
743
 
1241
- let m: ReturnType<typeof useMutationInt>
1242
- const useMutation = () => m ??= useMutationInt()
1243
-
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]>
744
+ // one AtomRuntime for the query engine, built lazily from the live app context;
745
+ // shares the ManagedRuntime memoMap so layers + Reactivity are the same instances.
746
+ let atomRt: AtomClientRuntime | undefined
747
+ const getAtomRt =
748
+ () => (atomRt ??= makeAtomClientRuntime(() => Layer.succeedContext(getBaseRt()), getBaseMrt().memoMap))
749
+
750
+ const legacyQueryEngine = options?.legacyQueryEngine ?? "tanstack"
751
+ let tanstackQueryClient: ReturnType<typeof makeTanstackQueryClient> | undefined
752
+ const getTanstackQueryClient = () => tanstackQueryClient ??= makeTanstackQueryClient()
753
+ const atomInvalidator = makeResolvedAtomQueryInvalidator(getBaseRt)
754
+ const queryInvalidator = legacyQueryEngine === "tanstack"
755
+ ? combineQueryInvalidators(atomInvalidator, makeTanstackQueryInvalidator(getTanstackQueryClient()))
756
+ : atomInvalidator
757
+ setQueryCacheUpdater(
758
+ legacyQueryEngine === "tanstack"
759
+ ? combineQueryCacheUpdaters(
760
+ makeTanstackQueryCacheUpdater(getTanstackQueryClient()),
761
+ optionalAtomQueryCacheUpdater
762
+ )
763
+ : atomQueryCacheUpdater
1265
764
  )
1266
765
 
1267
- const query = new QueryImpl(getBaseRt)
766
+ let m: ReturnType<typeof useMutationInt>
767
+ const useMutation = () => m ??= useMutationInt(queryInvalidator)
768
+
769
+ let sm2: ReturnType<typeof makeStreamMutation2>
770
+ const useStreamMutation2 = () => sm2 ??= makeStreamMutation2(queryInvalidator)
771
+
772
+ const legacyUseQuery = legacyQueryEngine === "tanstack"
773
+ ? makeTanstackQuery(getBaseRt, getTanstackQueryClient())
774
+ : undefined
775
+ const query = new QueryImpl(getBaseRt, getAtomRt, legacyUseQuery)
1268
776
  const useQuery = query.useQuery
777
+ const useQueryNew = query.useQueryNew
778
+ const useQueryAtom = query.useQueryAtom
779
+ const useQueryFamily = query.useQueryFamily
1269
780
  const useSuspenseQuery = query.useSuspenseQuery
781
+ const useSuspenseQueryNew = query.useSuspenseQueryNew
782
+ const useStreamQuery = query.useStreamQuery
783
+ const useStreamQueryFamily = query.useStreamQueryFamily
784
+ const useStreamQueryAtom = query.useStreamQueryAtom
785
+ const useStreamQueryNew = query.useStreamQueryNew
786
+
787
+ const isQueryHandler = <H extends { readonly Request: Req }>(handler: H): handler is QueryHandler<H> =>
788
+ handler.Request.type === "query" && !handler.Request.stream
789
+
790
+ const isStreamQueryHandler = <H extends { readonly Request: Req }>(handler: H): handler is QueryStreamHandler<H> =>
791
+ handler.Request.type === "query" && handler.Request.stream
792
+
793
+ const queryHelpersFor = <I, A, E, Request extends Req, Name extends string>(
794
+ handler: RequestHandlerWithInput<I, A, E, RT, Request, Name>
795
+ ) => ({
796
+ family: useQueryFamily(handler),
797
+ atom: useQueryAtom(handler),
798
+ query: useQuery(handler),
799
+ queryNew: useQueryNew(handler),
800
+ suspense: useSuspenseQuery(handler),
801
+ suspenseNew: useSuspenseQueryNew(handler)
802
+ })
803
+
804
+ const streamQueryHelpersFor = <I, A, E, Request extends Req, Name extends string>(
805
+ handler: RequestStreamHandlerWithInput<I, A, E, RT, Request, Name>
806
+ ) => ({
807
+ family: useStreamQueryFamily(handler),
808
+ atom: useStreamQueryAtom(handler),
809
+ query: useStreamQuery(handler),
810
+ queryNew: useStreamQueryNew(handler)
811
+ })
812
+
813
+ const projectQueryFor = <
814
+ I,
815
+ A,
816
+ E,
817
+ Request extends Req & { readonly success: S.Top & { readonly "EncodingServices": RT } },
818
+ Name extends string
819
+ >(
820
+ handler: RequestHandlerWithInput<I, A, E, RT, Request, Name>
821
+ ) =>
822
+ <ProjSchema extends S.Top & { readonly "DecodingServices": RT }>(projectionSchema: ProjSchema) => {
823
+ const successSchema = handler.Request.success
824
+ const projectionHash = projectionSchemaHash(projectionSchema)
825
+ const projected = projectHandler(handler.handler, successSchema, projectionSchema)
826
+ const projectedHandler = {
827
+ handler: projected,
828
+ id: handler.id,
829
+ Request: handler.Request,
830
+ queryKeyProjectionHash: projectionHash
831
+ }
832
+ if (handler.options) {
833
+ return {
834
+ request: projected,
835
+ ...queryHelpersFor({ ...projectedHandler, options: handler.options })
836
+ }
837
+ }
838
+ return {
839
+ request: projected,
840
+ ...queryHelpersFor(projectedHandler)
841
+ }
842
+ }
843
+
844
+ const mergeInvalidation = (
845
+ a?: MutationOptionsBase["queryInvalidation"],
846
+ b?: MutationOptionsBase["queryInvalidation"]
847
+ ): MutationOptionsBase["queryInvalidation"] | undefined => {
848
+ if (!a && !b) {
849
+ return undefined
850
+ }
851
+ return (defaultKey, name, input, output) => [
852
+ ...(a?.(defaultKey, name, input, output) ?? []),
853
+ ...(b?.(defaultKey, name, input, output) ?? [])
854
+ ]
855
+ }
1270
856
 
1271
- const mapQuery = <M extends Requests>(
857
+ const withDefaultInvalidation = (
858
+ mut: any,
859
+ defaultInvalidation?: MutationOptionsBase["queryInvalidation"]
860
+ ) => {
861
+ if (!defaultInvalidation) return mut
862
+ const apply = (callerOpts?: MutationOptionsBase) => ({
863
+ ...callerOpts,
864
+ queryInvalidation: callerOpts?.queryInvalidation
865
+ ? mergeInvalidation(defaultInvalidation, callerOpts.queryInvalidation)
866
+ : defaultInvalidation
867
+ })
868
+ return (input: any, callerOpts?: MutationOptionsBase) => mut(input, apply(callerOpts))
869
+ }
870
+
871
+ const makeQueryResources = <Resources extends InvalidationResources>(resources: Resources | undefined) => {
872
+ if (!resources) {
873
+ return {} as Record<string, Record<string, unknown>>
874
+ }
875
+ return resources as Record<string, Record<string, unknown>>
876
+ }
877
+
878
+ const mapQuery = <M extends RequestsAny>(
1272
879
  client: ClientFrom<M>
1273
880
  ) => {
1274
881
  const queries = Struct.keys(client).reduce(
1275
882
  (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
- })
883
+ const handler = client[key]
884
+ if (isQueryHandler(handler)) {
885
+ Object.assign(acc, {
886
+ [camelCase(key) + "QueryFamily"]: Object.assign(useQueryFamily(handler), {
887
+ id: client[key].id
888
+ })
889
+ })
890
+ ;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(handler), {
891
+ id: client[key].id
892
+ })
893
+ ;(acc as any)[camelCase(key) + "QueryNew"] = Object.assign(useQueryNew(handler), {
894
+ id: client[key].id
895
+ })
896
+ ;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(handler), {
897
+ id: client[key].id
898
+ })
899
+ ;(acc as any)[camelCase(key) + "SuspenseQueryNew"] = Object.assign(
900
+ useSuspenseQueryNew(handler),
901
+ {
902
+ id: client[key].id
903
+ }
904
+ )
905
+ } else if (isStreamQueryHandler(handler)) {
906
+ const streamHelpers = streamQueryHelpersFor(handler)
907
+ Object.assign(acc, {
908
+ [camelCase(key) + "QueryFamily"]: Object.assign(streamHelpers.family, {
909
+ id: client[key].id
910
+ }),
911
+ [camelCase(key) + "Query"]: Object.assign(streamHelpers.query, {
912
+ id: client[key].id
913
+ }),
914
+ [camelCase(key) + "QueryNew"]: Object.assign(streamHelpers.queryNew, {
915
+ id: client[key].id
916
+ })
917
+ })
918
+ }
1282
919
  return acc
1283
920
  },
1284
921
  {} as
922
+ & {
923
+ [
924
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
925
+ : `${ToCamel<string & Key>}QueryFamily`
926
+ ]: Queries<RT, QueryHandler<typeof client[Key]>>["family"]
927
+ }
1285
928
  & {
1286
929
  // apparently can't get JSDoc in here..
1287
- [Key in keyof typeof client as `${ToCamel<string & Key>}Query`]: Queries<RT, typeof client[Key]>["query"]
930
+ [
931
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
932
+ : `${ToCamel<string & Key>}Query`
933
+ ]: Queries<RT, QueryHandler<typeof client[Key]>>["query"]
934
+ }
935
+ & {
936
+ [
937
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
938
+ : `${ToCamel<string & Key>}QueryNew`
939
+ ]: Queries<RT, QueryHandler<typeof client[Key]>>["queryNew"]
1288
940
  }
1289
941
  // todo: or suspense as an Option?
1290
942
  & {
1291
943
  // apparently can't get JSDoc in here..
1292
- [Key in keyof typeof client as `${ToCamel<string & Key>}SuspenseQuery`]: Queries<
944
+ [
945
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
946
+ : `${ToCamel<string & Key>}SuspenseQuery`
947
+ ]: Queries<
1293
948
  RT,
1294
- typeof client[Key]
949
+ QueryHandler<typeof client[Key]>
1295
950
  >["suspense"]
1296
951
  }
952
+ & {
953
+ [
954
+ Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
955
+ : `${ToCamel<string & Key>}SuspenseQueryNew`
956
+ ]: Queries<
957
+ RT,
958
+ QueryHandler<typeof client[Key]>
959
+ >["suspenseNew"]
960
+ }
961
+ & {
962
+ [
963
+ Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
964
+ : `${ToCamel<string & Key>}QueryFamily`
965
+ ]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["family"]
966
+ }
967
+ & {
968
+ [
969
+ Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
970
+ : `${ToCamel<string & Key>}Query`
971
+ ]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["query"]
972
+ }
973
+ & {
974
+ [
975
+ Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
976
+ : `${ToCamel<string & Key>}QueryNew`
977
+ ]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["queryNew"]
978
+ }
1297
979
  )
1298
980
  return queries
1299
981
  }
1300
982
 
1301
- const mapRequest = <M extends Requests>(
983
+ const mapRequest = <M extends RequestsAny>(
1302
984
  client: ClientFrom<M>
1303
985
  ) => {
1304
986
  const Command = useCommand()
1305
987
  const mutations = Struct.keys(client).reduce(
1306
988
  (acc, key) => {
989
+ if (!(client[key].Request.type === "command" && !client[key].Request.stream)) {
990
+ return acc
991
+ }
1307
992
  const mut = client[key].handler
1308
- const fn = Command.fn(client[key].id)
1309
- const wrap = Command.wrap({ mutate: Effect.isEffect(mut) ? () => mut : mut, id: client[key].id })
993
+ const request = mut
994
+ const fn = (options?: any) => Command.fn(client[key].id, options)
995
+ const wrap = (options?: any) => Command.wrap({ mutate: request, id: client[key].id }, options)
1310
996
  ;(acc as any)[camelCase(key) + "Request"] = Object.assign(
1311
997
  mut,
1312
- fn, // to get the i18n key etc.
1313
- { wrap, fn }
998
+ Command.fn(client[key].id), // to get the i18n key etc.
999
+ { wrap, fn, request }
1314
1000
  )
1315
1001
  return acc
1316
1002
  },
1317
1003
  {} as {
1318
- [Key in keyof typeof client as `${ToCamel<string & Key>}Request`]: RequestWithExtensions<
1004
+ [
1005
+ Key in keyof typeof client as CommandHandler<typeof client[Key]> extends never ? never
1006
+ : `${ToCamel<string & Key>}Request`
1007
+ ]: CommandRequestWithExtensions<
1319
1008
  RT | RTHooks,
1320
- typeof client[Key]
1009
+ CommandHandler<typeof client[Key]>
1321
1010
  >
1322
1011
  }
1323
1012
  )
1324
1013
  return mutations
1325
1014
  }
1326
1015
 
1327
- const mapMutation = <M extends Requests>(
1328
- client: ClientFrom<M>
1016
+ const mapMutation = <M extends RequestsAny>(
1017
+ client: ClientFrom<M>,
1018
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
1019
+ invalidationResources?: InvalidationResourcesFor<M>
1329
1020
  ) => {
1330
1021
  const Command = useCommand()
1331
1022
  const mutation = useMutation()
1023
+ const invalidation = queryInvalidation?.(client)
1024
+ const queryResources = makeQueryResources(invalidationResources)
1332
1025
  const mutations = Struct.keys(client).reduce(
1333
1026
  (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
- )
1027
+ if (!(client[key].Request.type === "command" && !client[key].Request.stream)) {
1028
+ return acc
1029
+ }
1030
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
1031
+ | InvalidationCallback<InvalidationResourcesFor<M>>
1032
+ | undefined
1033
+ const fromRequest = fromRequestConfig
1034
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
1035
+ fromRequestConfig(
1036
+ defaultKey,
1037
+ queryResources as never,
1038
+ input,
1039
+ output as never
1040
+ ) as InvalidationEntry[])
1041
+ : undefined
1042
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
1043
+ const makeProjectedMutation = (handler: any): any => {
1044
+ const mut: any = withDefaultInvalidation(mutation(handler), mergedInvalidation)
1045
+ return Object.assign(mut, {
1046
+ wrap: (options?: any) => Command.wrap({ mutate: mut, id: client[key].id }, options),
1047
+ fn: (options?: any) => Command.fn(client[key].id, options),
1048
+ project: (projectionSchema: any) => {
1049
+ const projected = {
1050
+ ...handler,
1051
+ handler: projectHandler(handler.handler, client[key].Request.success, projectionSchema)
1052
+ }
1053
+ return makeProjectedMutation(projected)
1054
+ }
1055
+ })
1056
+ }
1057
+ ;(acc as any)[camelCase(key) + "Mutation"] = makeProjectedMutation(client[key] as any)
1342
1058
  return acc
1343
1059
  },
1344
1060
  {} as {
1345
- [Key in keyof typeof client as `${ToCamel<string & Key>}Mutation`]: MutationWithExtensions<
1061
+ [
1062
+ Key in keyof typeof client as CommandHandler<typeof client[Key]> extends never ? never
1063
+ : `${ToCamel<string & Key>}Mutation`
1064
+ ]: MutationWithExtensions<
1346
1065
  RT | RTHooks,
1347
- typeof client[Key]
1066
+ CommandHandler<typeof client[Key]>
1348
1067
  >
1349
1068
  }
1350
1069
  )
@@ -1353,8 +1072,9 @@ export const makeClient = <RT_, RTHooks>(
1353
1072
 
1354
1073
  // make available .query, .suspense and .mutate for each operation
1355
1074
  // and a .helpers with all mutations and queries
1356
- const mapClient = <M extends Requests>(
1357
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
1075
+ const mapClient = <M extends RequestsAny>(
1076
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
1077
+ invalidationResources?: InvalidationResourcesFor<M>
1358
1078
  ) =>
1359
1079
  (
1360
1080
  client: ClientFrom<M>
@@ -1362,71 +1082,167 @@ export const makeClient = <RT_, RTHooks>(
1362
1082
  const Command = useCommand()
1363
1083
  const mutation = useMutation()
1364
1084
  const invalidation = queryInvalidation?.(client)
1085
+ const queryResources = makeQueryResources(invalidationResources)
1365
1086
  const extended = Struct.keys(client).reduce(
1366
1087
  (acc, key) => {
1088
+ const handler = client[key]
1089
+ const requestType = handler.Request.type
1090
+ const isStream = handler.Request.stream
1367
1091
  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
- const h_ = client[key].handler
1385
- const h = Effect.isEffect(h_)
1386
- ? () => h_
1387
- : (...args: [any]) => h_(...args)
1092
+ const h_ = handler.handler
1093
+ const request = h_
1388
1094
  ;(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
- }
1095
+ isQueryHandler(handler)
1096
+ ? {
1097
+ ...handler,
1098
+ request,
1099
+ ...queryHelpersFor(handler),
1100
+ project: projectQueryFor(handler)
1101
+ }
1102
+ : isStreamQueryHandler(handler)
1103
+ ? {
1104
+ ...client[key],
1105
+ request,
1106
+ ...streamQueryHelpersFor(handler)
1107
+ }
1108
+ : requestType === "command" && isStream
1109
+ ? (() => {
1110
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
1111
+ | InvalidationCallback<InvalidationResourcesFor<M>>
1112
+ | undefined
1113
+ const fromRequest = fromRequestConfig
1114
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
1115
+ fromRequestConfig(
1116
+ defaultKey,
1117
+ queryResources as never,
1118
+ input,
1119
+ output as never
1120
+ ) as InvalidationEntry[])
1121
+ : undefined
1122
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
1123
+ const streamCmd = useCommand()
1124
+ return {
1125
+ ...client[key],
1126
+ request,
1127
+ query: useStreamQuery(client[key] as any),
1128
+ fn: (options?: any) => streamCmd.streamFn(client[key].id as any, options),
1129
+ mutate: (() => {
1130
+ const sm2Act = useStreamMutation2()(client[key] as any, mergedInvalidation)
1131
+ const sm2Handler = (input: any, _ctx: any) => (sm2Act as (i: any) => any)(input)
1132
+ return Object.assign(sm2Act, {
1133
+ id: client[key].id,
1134
+ wrap: (options?: any) => streamCmd.streamWrap(sm2Handler, client[key].id as any, options)
1135
+ })
1136
+ })()
1137
+ }
1138
+ })()
1139
+ : {
1140
+ mutate: ((handler: any) => {
1141
+ const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
1142
+ | InvalidationCallback<InvalidationResourcesFor<M>>
1143
+ | undefined
1144
+ const fromRequest = fromRequestConfig
1145
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
1146
+ fromRequestConfig(
1147
+ defaultKey,
1148
+ queryResources as never,
1149
+ input,
1150
+ output as never
1151
+ ) as InvalidationEntry[])
1152
+ : undefined
1153
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
1154
+ const makeProjectedMutation = (h: any): any => {
1155
+ const mutate = withDefaultInvalidation(mutation(h), mergedInvalidation)
1156
+ return Object.assign(
1157
+ mutate,
1158
+ {
1159
+ wrap: (options?: any) =>
1160
+ Command.wrap({
1161
+ mutate,
1162
+ id: client[key].id
1163
+ }, options),
1164
+ fn: (options?: any) => Command.fn(client[key].id, options),
1165
+ project: (projectionSchema: any) => {
1166
+ const projected = {
1167
+ ...h,
1168
+ handler: projectHandler(h.handler, client[key].Request.success, projectionSchema)
1169
+ }
1170
+ return makeProjectedMutation(projected)
1171
+ }
1172
+ }
1173
+ )
1174
+ }
1175
+ return makeProjectedMutation(handler)
1176
+ })(client[key] as any),
1177
+ ...client[key],
1178
+ ...fn, // to get the i18n key etc.
1179
+ request,
1180
+ fn: (options?: any) => Command.fn(client[key].id, options),
1181
+ wrap: (options?: any) => Command.wrap({ mutate: h_, id: client[key].id }, options)
1182
+ }
1399
1183
  )
1400
1184
  return acc
1401
1185
  },
1402
1186
  {} as {
1403
1187
  [Key in keyof typeof client]:
1404
1188
  & typeof client[Key]
1405
- & RequestWithExtensions<RT | RTHooks, typeof client[Key]>
1406
- & { mutate: MutationWithExtensions<RT | RTHooks, typeof client[Key]> }
1407
- & Queries<RT, typeof client[Key]>
1189
+ & (QueryHandler<typeof client[Key]> extends never ? {}
1190
+ :
1191
+ & QueryRequestWithExtensions<QueryHandler<typeof client[Key]>>
1192
+ & Queries<RT, QueryHandler<typeof client[Key]>>
1193
+ & QueryProjection<RT, QueryHandler<typeof client[Key]>>)
1194
+ & (QueryStreamHandler<typeof client[Key]> extends never ? {}
1195
+ : StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>)
1196
+ & (CommandHandler<typeof client[Key]> extends never ? {}
1197
+ : CommandRequestWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>>)
1198
+ & (CommandHandler<typeof client[Key]> extends never ? {}
1199
+ : { mutate: MutationWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>> })
1200
+ & (CommandStreamHandler<typeof client[Key]> extends never ? {}
1201
+ : {
1202
+ fn: StreamFnStreamExtension<RT | RTHooks, CommandStreamHandler<typeof client[Key]>>
1203
+ mutate: StreamMutation2WithExtensions<RT | RTHooks, CommandStreamHandler<typeof client[Key]>>
1204
+ })
1205
+ & {
1206
+ Input: typeof client[Key] extends RequestStreamHandlerWithInput<infer I, any, any, any, any, any, any> ? I
1207
+ : typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I
1208
+ : never
1209
+ }
1408
1210
  }
1409
1211
  )
1410
- return Object.assign(extended, { helpers: { ...mapRequest(client), ...mapMutation(client), ...mapQuery(client) } })
1212
+ return Object.assign(extended, {
1213
+ helpers: {
1214
+ ...mapRequest(client),
1215
+ ...mapMutation(client, queryInvalidation, invalidationResources),
1216
+ ...mapQuery(client)
1217
+ }
1218
+ })
1411
1219
  }
1412
1220
 
1413
1221
  // TODO: Clean up this delay initialisation messs
1414
1222
  // TODO; invalidateQueries should perhaps be configured in the Request impl themselves?
1415
- const clientFor__ = <M extends Requests>(
1223
+ const clientFor__ = <M extends RequestsAny>(
1416
1224
  m: M,
1417
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
1418
- ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation))))
1225
+ queryInvalidation?: QueryInvalidationFactory<M>,
1226
+ invalidationResources?: InvalidationResourcesFor<M>
1227
+ ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation, invalidationResources))))
1419
1228
 
1420
1229
  // delay client creation until first access
1421
1230
  // the idea is that we don't need the useNuxtApp().$runtime (only available at later initialisation stage)
1422
1231
  // until we are at a place where it is available..
1423
- const clientFor = <M extends Requests>(
1232
+ const clientFor = <
1233
+ M extends RequestsAny,
1234
+ Resources extends InvalidationResourcesFor<M> = InvalidationResourcesFor<M>
1235
+ >(
1424
1236
  m: M,
1425
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
1237
+ ...args: ClientForArgs<M, Resources>
1426
1238
  ) => {
1239
+ const [queryInvalidation, invalidationResources] = args as [
1240
+ QueryInvalidationFactory<M> | undefined,
1241
+ InvalidationResourcesFor<M> | undefined
1242
+ ]
1427
1243
  type Client = ReturnType<typeof clientFor__<M>>
1428
1244
  let client: Client | undefined = undefined
1429
- const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation))
1245
+ const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation, invalidationResources))
1430
1246
 
1431
1247
  // initialize on first use..
1432
1248
  const proxy = Struct.keys(m).concat(["helpers"]).reduce((acc, key) => {
@@ -1444,16 +1260,12 @@ export const makeClient = <RT_, RTHooks>(
1444
1260
  return proxy
1445
1261
  }
1446
1262
 
1447
- const legacy: Legacy<RT> = {
1448
- ...mutations,
1449
- ...query
1450
- }
1451
-
1452
1263
  const Command: CommanderResolved<RT, RTHooks> = {
1453
1264
  ...{
1454
1265
  // delay initialisation until first use...
1455
1266
  fn: (...args: [any]) => useCommand().fn(...args),
1456
1267
  wrap: (...args: [any]) => useCommand().wrap(...args),
1268
+ streamFn: (...args: [any]) => useCommand().streamFn(...args),
1457
1269
  alt: (...args: [any]) => useCommand().alt(...args),
1458
1270
  alt2: (...args: [any]) => useCommand().alt2(...args)
1459
1271
  } as ReturnType<typeof useCommand>,
@@ -1464,21 +1276,17 @@ export const makeClient = <RT_, RTHooks>(
1464
1276
  Command,
1465
1277
  useCommand,
1466
1278
  clientFor,
1467
- legacy
1279
+ tanstackQueryClient: getTanstackQueryClient()
1468
1280
  }
1469
1281
  }
1470
1282
 
1471
- export interface Legacy<R>
1472
- extends
1473
- Pick<QueryImpl<R>, "useQuery" | "useSuspenseQuery">,
1474
- Omit<LegacyMutationImpl<R>, "getRuntime" | "toast" | "intl">
1475
- {}
1476
-
1477
1283
  export type QueryInvalidation<M> = {
1478
- [K in keyof M]?: (defaultKey: string[], name: string) => {
1479
- filters?: InvalidateQueryFilters | undefined
1480
- options?: InvalidateOptions | undefined
1481
- }[]
1284
+ [K in keyof M]?: (
1285
+ defaultKey: string[],
1286
+ name: string,
1287
+ input?: unknown,
1288
+ output?: Exit.Exit<unknown, unknown>
1289
+ ) => InvalidationEntry[]
1482
1290
  }
1483
1291
 
1484
1292
  export type ToCamel<S extends string | number | symbol> = S extends string
@@ -1486,17 +1294,21 @@ export type ToCamel<S extends string | number | symbol> = S extends string
1486
1294
  : Uncapitalize<S>
1487
1295
  : never
1488
1296
 
1489
- export interface CommandBase<I = void, A = void> {
1297
+ export interface CommandBase<I = void, A = void, RA = unknown, RE = unknown> {
1490
1298
  handle: (input: I) => A
1491
1299
  waiting: boolean
1492
1300
  blocked: boolean
1493
1301
  allowed: boolean
1494
1302
  action: string
1495
1303
  label: string
1304
+ /** formatted progress info for current `running` state, when `progress` was supplied */
1305
+ progress?: Progress | undefined
1306
+ /** reactive result state, available on stream-backed commands */
1307
+ result?: AsyncResult.AsyncResult<RA, RE>
1496
1308
  }
1497
1309
 
1498
- export interface EffectCommand<I = void, A = unknown, E = unknown> extends CommandBase<I, Fiber<A, E>> {}
1310
+ export interface EffectCommand<I = void, A = unknown, E = unknown> extends CommandBase<I, Fiber<A, E>, A, E> {}
1499
1311
 
1500
- export interface CommandFromRequest<I extends abstract new(...args: any) => any, A = unknown, E = unknown>
1501
- extends EffectCommand<ConstructorParameters<I>[0], A, E>
1312
+ export interface CommandFromRequest<I extends { readonly make: (...args: any[]) => any }, A = unknown, E = unknown>
1313
+ extends EffectCommand<HandlerInput<I>, A, E>
1502
1314
  {}