@effect-app/vue 4.0.0-beta.271 → 4.0.0-beta.273
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.
- package/CHANGELOG.md +16 -0
- package/dist/atomQuery.d.ts +90 -0
- package/dist/atomQuery.d.ts.map +1 -0
- package/dist/atomQuery.js +275 -0
- package/dist/internal/tanstackQuery.d.ts +8 -0
- package/dist/internal/tanstackQuery.d.ts.map +1 -0
- package/dist/internal/tanstackQuery.js +145 -0
- package/dist/makeClient.d.ts +75 -13
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +203 -78
- package/dist/mutate.d.ts +20 -12
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +65 -64
- package/dist/query.d.ts +114 -12
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +275 -179
- package/docs/atom-query-api-redesign.md +191 -0
- package/package.json +2 -2
- package/src/atomQuery.ts +361 -0
- package/src/internal/tanstackQuery.ts +221 -0
- package/src/makeClient.ts +382 -91
- package/src/mutate.ts +101 -110
- package/src/query.ts +564 -243
- package/test/dist/stubs.d.ts +169 -2
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/dist/stubs.js +11 -6
- package/test/makeClient.test.ts +110 -0
- package/test/stubs.ts +10 -5
- package/tsconfig.json.bak +72 -14
package/src/makeClient.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { isCancelledError, type QueryObserverResult, type RefetchOptions, type UseQueryReturnType } from "@tanstack/vue-query"
|
|
3
2
|
import { camelCase } from "change-case"
|
|
4
3
|
import { type ApiClientFactory, type Req } from "effect-app/client"
|
|
5
4
|
import type { ExtractModuleName, HandlerInput, RequestHandlers, RequestHandlerWithInput, RequestsAny, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
|
|
5
|
+
import type { CauseException } from "effect-app/client/errors"
|
|
6
6
|
import type { InvalidationCallback } from "effect-app/client/makeClient"
|
|
7
|
-
import
|
|
7
|
+
import * as Context from "effect-app/Context"
|
|
8
8
|
import * as Effect from "effect-app/Effect"
|
|
9
|
-
import
|
|
9
|
+
import * as Layer from "effect-app/Layer"
|
|
10
10
|
import * as S from "effect-app/Schema"
|
|
11
11
|
import * as Exit from "effect/Exit"
|
|
12
12
|
import { type Fiber } from "effect/Fiber"
|
|
@@ -14,28 +14,30 @@ import * as Hash from "effect/Hash"
|
|
|
14
14
|
import type * as ManagedRuntime from "effect/ManagedRuntime"
|
|
15
15
|
import type * as Stream from "effect/Stream"
|
|
16
16
|
import * as Struct from "effect/Struct"
|
|
17
|
-
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
18
|
-
import
|
|
17
|
+
import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
18
|
+
import * as Reactivity from "effect/unstable/reactivity/Reactivity"
|
|
19
|
+
import { computed, type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
|
|
20
|
+
import { type AtomClientRuntime, invalidateAndAwait, makeAtomClientRuntime } from "./atomQuery.ts"
|
|
19
21
|
import { type Commander, CommanderStatic, type Progress } from "./commander.ts"
|
|
22
|
+
import { makeTanstackQuery, makeTanstackQueryClient, makeTanstackQueryInvalidator } from "./internal/tanstackQuery.ts"
|
|
20
23
|
import { type I18n } from "./intl.ts"
|
|
21
24
|
import { type CommanderResolved, makeUseCommand } from "./makeUseCommand.ts"
|
|
22
|
-
import { type InvalidationEntry, makeMutation, makeStreamMutation2, type MutationOptionsBase, useMakeMutation } from "./mutate.ts"
|
|
23
|
-
import { type CustomUndefinedInitialQueryOptions, makeQuery, makeStreamQuery } from "./query.ts"
|
|
25
|
+
import { atomQueryInvalidator, combineQueryInvalidators, type InvalidationEntry, makeMutation, makeStreamMutation2, type MutationOptionsBase, type QueryInvalidator, useMakeMutation } from "./mutate.ts"
|
|
26
|
+
import { type AtomQueryNewOptions, type CustomUndefinedInitialQueryOptions, makeQuery, makeQueryAtom, makeQueryFamily, makeQueryNew, makeStreamQuery, makeStreamQueryAtom, makeStreamQueryFamily, makeStreamQueryNew, type QueryObserverResult, type RefetchOptions, type StreamQueryAtomFamily, type SuspenseQueryView, type UseQueryReturnType } from "./query.ts"
|
|
24
27
|
import { makeRunPromise } from "./runtime.ts"
|
|
25
|
-
import { awaitResolvedSuspenseResult } from "./suspense.ts"
|
|
26
28
|
import { type Toast } from "./toast.ts"
|
|
27
29
|
|
|
28
30
|
export type { Progress }
|
|
29
31
|
|
|
30
32
|
// TODO: optimize - work from encoded shape directly
|
|
31
|
-
const projectHandler = (
|
|
32
|
-
handler: (i:
|
|
33
|
+
const projectHandler = <I, A, E, R, ProjSchema extends S.Decoder<unknown, never>>(
|
|
34
|
+
handler: (i: I) => Effect.Effect<A, E, R>,
|
|
33
35
|
successSchema: S.Top,
|
|
34
|
-
projectionSchema:
|
|
36
|
+
projectionSchema: ProjSchema
|
|
35
37
|
) => {
|
|
36
|
-
const encode = S.
|
|
37
|
-
const decode = S.
|
|
38
|
-
return (i:
|
|
38
|
+
const encode = S.encodeSync(successSchema as S.Encoder<A>)
|
|
39
|
+
const decode = S.decodeSync(projectionSchema)
|
|
40
|
+
return (i: I) => handler(i).pipe(Effect.map((value) => decode(encode(value))))
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const projectionSchemaHash = (schema: S.Top) => String(Hash.hash(schema.ast))
|
|
@@ -202,16 +204,38 @@ export type StreamMutation2WithExtensions<RT, Req> = Req extends
|
|
|
202
204
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
203
205
|
declare const useQuery_: QueryImpl<any>["useQuery"]
|
|
204
206
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
207
|
+
declare const useQueryNew_: QueryImpl<any>["useQueryNew"]
|
|
208
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
209
|
+
declare const useQueryAtom_: QueryImpl<any>["useQueryAtom"]
|
|
210
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
211
|
+
declare const useQueryFamily_: QueryImpl<any>["useQueryFamily"]
|
|
212
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
213
|
+
declare const useSuspenseQueryNew_: QueryImpl<any>["useSuspenseQueryNew"]
|
|
214
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
205
215
|
declare const useSuspenseQuery_: QueryImpl<any>["useSuspenseQuery"]
|
|
206
216
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
207
217
|
declare const useStreamQuery_: QueryImpl<any>["useStreamQuery"]
|
|
218
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
219
|
+
declare const useStreamQueryFamily_: QueryImpl<any>["useStreamQueryFamily"]
|
|
220
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
221
|
+
declare const useStreamQueryAtom_: QueryImpl<any>["useStreamQueryAtom"]
|
|
222
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
223
|
+
declare const useStreamQueryNew_: QueryImpl<any>["useStreamQueryNew"]
|
|
208
224
|
|
|
209
225
|
export interface ProjectResult<RT, I, B, E, R, Request extends Req, Id extends string> {
|
|
210
226
|
request: (i: I) => Effect.Effect<B, E, R>
|
|
227
|
+
family: Exclude<R, RT> extends never ? ReturnType<typeof useQueryFamily_<I, E, B, Request, Id>>
|
|
228
|
+
: MissingDependencies<RT, R> & {}
|
|
211
229
|
query: Exclude<R, RT> extends never ? ReturnType<typeof useQuery_<I, E, B, Request, Id>>
|
|
212
230
|
: MissingDependencies<RT, R> & {}
|
|
231
|
+
queryNew: Exclude<R, RT> extends never ? ReturnType<typeof useQueryNew_<I, E, B, Request, Id>>
|
|
232
|
+
: MissingDependencies<RT, R> & {}
|
|
213
233
|
suspense: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQuery_<I, E, B, Request, Id>>
|
|
214
234
|
: MissingDependencies<RT, R> & {}
|
|
235
|
+
suspenseNew: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQueryNew_<I, E, B, Request, Id>>
|
|
236
|
+
: MissingDependencies<RT, R> & {}
|
|
237
|
+
atom: Exclude<R, RT> extends never ? ReturnType<typeof useQueryAtom_<I, E, B, Request, Id>>
|
|
238
|
+
: MissingDependencies<RT, R> & {}
|
|
215
239
|
}
|
|
216
240
|
|
|
217
241
|
export type QueryProjection<RT, HandlerReq> = HandlerReq extends
|
|
@@ -240,12 +264,30 @@ export interface QueryResultExtensions<Request extends Req, Id extends string, I
|
|
|
240
264
|
* When `I = void` the input argument may be omitted.
|
|
241
265
|
*/
|
|
242
266
|
query: ReturnType<typeof useQuery_<I, E, A, Request, Id>>
|
|
267
|
+
/**
|
|
268
|
+
* Atom-native query helper with object return shape.
|
|
269
|
+
* Additive migration surface; `.query()` remains the compatibility tuple API.
|
|
270
|
+
*/
|
|
271
|
+
queryNew: ReturnType<typeof useQueryNew_<I, E, A, Request, Id>>
|
|
243
272
|
// TODO or suspense as Option?
|
|
244
273
|
/**
|
|
245
274
|
* Like `.query`, but returns a Promise for setup-time awaiting.
|
|
246
275
|
* Use this when integrating with Vue Suspense / error boundaries.
|
|
247
276
|
*/
|
|
248
277
|
suspense: ReturnType<typeof useSuspenseQuery_<I, E, A, Request, Id>>
|
|
278
|
+
/**
|
|
279
|
+
* Promise-based setup helper with object return shape.
|
|
280
|
+
* Additive migration surface; `.suspense()` remains the compatibility tuple API.
|
|
281
|
+
*/
|
|
282
|
+
suspenseNew: ReturnType<typeof useSuspenseQueryNew_<I, E, A, Request, Id>>
|
|
283
|
+
/**
|
|
284
|
+
* Raw query atom for composition outside Vue refs.
|
|
285
|
+
*/
|
|
286
|
+
atom: ReturnType<typeof useQueryAtom_<I, E, A, Request, Id>>
|
|
287
|
+
/**
|
|
288
|
+
* Raw query atom family for composing query graphs before choosing a Vue observer.
|
|
289
|
+
*/
|
|
290
|
+
family: ReturnType<typeof useQueryFamily_<I, E, A, Request, Id>>
|
|
249
291
|
}
|
|
250
292
|
|
|
251
293
|
export type MissingDependencies<RT, R> = {
|
|
@@ -256,26 +298,45 @@ export type MissingDependencies<RT, R> = {
|
|
|
256
298
|
export type Queries<RT, Req> = Req extends
|
|
257
299
|
RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
|
|
258
300
|
? Request["type"] extends "query" ? Exclude<R, RT> extends never ? QueryResultExtensions<Request, Id, I, A, E>
|
|
259
|
-
: {
|
|
301
|
+
: {
|
|
302
|
+
atom: MissingDependencies<RT, R> & {}
|
|
303
|
+
family: MissingDependencies<RT, R> & {}
|
|
304
|
+
query: MissingDependencies<RT, R> & {}
|
|
305
|
+
queryNew: MissingDependencies<RT, R> & {}
|
|
306
|
+
suspense: MissingDependencies<RT, R> & {}
|
|
307
|
+
suspenseNew: MissingDependencies<RT, R> & {}
|
|
308
|
+
}
|
|
260
309
|
: never
|
|
261
310
|
: never
|
|
262
311
|
|
|
263
312
|
export interface StreamQueryExtensions<Request extends Req, Id extends string, I, A, E> {
|
|
313
|
+
atom: ReturnType<typeof useStreamQueryAtom_<I, E, A, Request, Id>>
|
|
314
|
+
family: StreamQueryAtomFamily<I, A, E>
|
|
264
315
|
/**
|
|
265
316
|
* Stream helper for query-stream requests.
|
|
266
|
-
*
|
|
267
|
-
*
|
|
317
|
+
* Legacy compatibility helper. Collects the whole stream into an array before
|
|
318
|
+
* publishing data.
|
|
268
319
|
* When `I = void` the input argument may be omitted.
|
|
269
320
|
*/
|
|
270
321
|
query: ReturnType<typeof useStreamQuery_<I, E, A, Request, Id>>
|
|
322
|
+
/**
|
|
323
|
+
* Atom-native stream query helper. Exposes incremental pull state and a `pull`
|
|
324
|
+
* command instead of collecting the whole stream first.
|
|
325
|
+
*/
|
|
326
|
+
queryNew: ReturnType<typeof useStreamQueryNew_<I, E, A, Request, Id>>
|
|
271
327
|
}
|
|
272
328
|
export type StreamQueries<RT, HandlerReq> = HandlerReq extends
|
|
273
329
|
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer _Final>
|
|
274
330
|
? Exclude<R, RT> extends never ? StreamQueryExtensions<Request, Id, I, A, E>
|
|
275
|
-
: {
|
|
331
|
+
: {
|
|
332
|
+
atom: MissingDependencies<RT, R> & {}
|
|
333
|
+
family: MissingDependencies<RT, R> & {}
|
|
334
|
+
query: MissingDependencies<RT, R> & {}
|
|
335
|
+
queryNew: MissingDependencies<RT, R> & {}
|
|
336
|
+
}
|
|
276
337
|
: never
|
|
277
338
|
|
|
278
|
-
const _useMutation = makeMutation()
|
|
339
|
+
const _useMutation = makeMutation(atomQueryInvalidator)
|
|
279
340
|
|
|
280
341
|
const wrapWithSpan = (self: { id: string }, mut: any) => {
|
|
281
342
|
const span = (eff: Effect.Effect<any, any, any>) =>
|
|
@@ -308,8 +369,8 @@ export const useMutation: typeof _useMutation = (<
|
|
|
308
369
|
* Executes query cache invalidation based on default rules or provided option.
|
|
309
370
|
* adds a span with the mutation id
|
|
310
371
|
*/
|
|
311
|
-
export const useMutationInt = (): typeof _useMutation => {
|
|
312
|
-
const _useMutation = useMakeMutation()
|
|
372
|
+
export const useMutationInt = (queryInvalidator: QueryInvalidator): typeof _useMutation => {
|
|
373
|
+
const _useMutation = useMakeMutation(queryInvalidator)
|
|
313
374
|
return (<
|
|
314
375
|
I,
|
|
315
376
|
E,
|
|
@@ -328,13 +389,31 @@ export const useMutationInt = (): typeof _useMutation => {
|
|
|
328
389
|
|
|
329
390
|
export type ClientFrom<M extends RequestsAny> = RequestHandlers<never, never, M, ExtractModuleName<M>>
|
|
330
391
|
|
|
392
|
+
export interface MakeClientOptions {
|
|
393
|
+
/**
|
|
394
|
+
* Selects the engine behind legacy `.query()` / `.suspense()` helpers.
|
|
395
|
+
* Atom-native `.atom()` / `.family()` / `.queryNew()` / `.suspenseNew()` always use Atom.
|
|
396
|
+
*/
|
|
397
|
+
readonly legacyQueryEngine?: "atom" | "tanstack"
|
|
398
|
+
}
|
|
399
|
+
|
|
331
400
|
export class QueryImpl<R> {
|
|
332
401
|
readonly getRuntime: () => Context.Context<R>
|
|
333
402
|
|
|
334
|
-
constructor(
|
|
403
|
+
constructor(
|
|
404
|
+
getRuntime: () => Context.Context<R>,
|
|
405
|
+
getAtomRt: () => AtomClientRuntime,
|
|
406
|
+
legacyUseQuery?: ReturnType<typeof makeQuery<R>>
|
|
407
|
+
) {
|
|
335
408
|
this.getRuntime = getRuntime
|
|
336
|
-
this.useQuery = makeQuery(this.getRuntime)
|
|
337
|
-
this.
|
|
409
|
+
this.useQuery = legacyUseQuery ?? makeQuery(this.getRuntime, getAtomRt)
|
|
410
|
+
this.useQueryNew = makeQueryNew(this.getRuntime, getAtomRt)
|
|
411
|
+
this.useQueryAtom = makeQueryAtom(this.getRuntime, getAtomRt)
|
|
412
|
+
this.useQueryFamily = makeQueryFamily(this.getRuntime, getAtomRt)
|
|
413
|
+
this.useStreamQuery = makeStreamQuery(this.getRuntime, getAtomRt)
|
|
414
|
+
this.useStreamQueryFamily = makeStreamQueryFamily(this.getRuntime, getAtomRt)
|
|
415
|
+
this.useStreamQueryAtom = makeStreamQueryAtom(this.getRuntime, getAtomRt)
|
|
416
|
+
this.useStreamQueryNew = makeStreamQueryNew(this.getRuntime, getAtomRt)
|
|
338
417
|
}
|
|
339
418
|
/**
|
|
340
419
|
* Effect results are passed to the caller, including errors.
|
|
@@ -342,12 +421,24 @@ export class QueryImpl<R> {
|
|
|
342
421
|
*/
|
|
343
422
|
readonly useQuery: ReturnType<typeof makeQuery<R>>
|
|
344
423
|
|
|
424
|
+
readonly useQueryNew: ReturnType<typeof makeQueryNew<R>>
|
|
425
|
+
|
|
426
|
+
readonly useQueryAtom: ReturnType<typeof makeQueryAtom<R>>
|
|
427
|
+
|
|
428
|
+
readonly useQueryFamily: ReturnType<typeof makeQueryFamily<R>>
|
|
429
|
+
|
|
345
430
|
/**
|
|
346
431
|
* Stream results are accumulated as an array of chunks and returned as reactive state.
|
|
347
432
|
* @deprecated use client helpers instead (.query())
|
|
348
433
|
*/
|
|
349
434
|
readonly useStreamQuery: ReturnType<typeof makeStreamQuery<R>>
|
|
350
435
|
|
|
436
|
+
readonly useStreamQueryFamily: ReturnType<typeof makeStreamQueryFamily<R>>
|
|
437
|
+
|
|
438
|
+
readonly useStreamQueryAtom: ReturnType<typeof makeStreamQueryAtom<R>>
|
|
439
|
+
|
|
440
|
+
readonly useStreamQueryNew: ReturnType<typeof makeStreamQueryNew<R>>
|
|
441
|
+
|
|
351
442
|
/**
|
|
352
443
|
* The difference with useQuery is that this function will return a Promise you can await in the Setup,
|
|
353
444
|
* which ensures that either there always is a latest value, or an error occurs on load.
|
|
@@ -370,7 +461,10 @@ export class QueryImpl<R> {
|
|
|
370
461
|
>(
|
|
371
462
|
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
372
463
|
): {
|
|
373
|
-
<TData = A>(
|
|
464
|
+
<TData = A>(
|
|
465
|
+
arg: I | WatchSource<I>,
|
|
466
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
467
|
+
): Promise<
|
|
374
468
|
readonly [
|
|
375
469
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
376
470
|
ComputedRef<TData>,
|
|
@@ -385,10 +479,19 @@ export class QueryImpl<R> {
|
|
|
385
479
|
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
386
480
|
) => {
|
|
387
481
|
const runPromise = makeRunPromise(this.getRuntime())
|
|
388
|
-
const q = this.useQuery(self
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
|
|
482
|
+
const q = this.useQuery(self)
|
|
483
|
+
return <TData = A>(
|
|
484
|
+
argOrOptions: I | WatchSource<I>,
|
|
485
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
486
|
+
) => {
|
|
487
|
+
const [resultRef, latestRef, fetch, uqrt] = q<TData>(argOrOptions, options)
|
|
488
|
+
const latestDefinedRef = computed<TData>(() => {
|
|
489
|
+
const latest = latestRef.value
|
|
490
|
+
if (latest === undefined) {
|
|
491
|
+
throw new Error("Internal Error: suspense resolved without a latest value")
|
|
492
|
+
}
|
|
493
|
+
return latest
|
|
494
|
+
})
|
|
392
495
|
|
|
393
496
|
const isMounted = ref(true)
|
|
394
497
|
onBeforeUnmount(() => {
|
|
@@ -397,36 +500,89 @@ export class QueryImpl<R> {
|
|
|
397
500
|
|
|
398
501
|
// @effect-diagnostics effect/missingEffectError:off
|
|
399
502
|
const eff = Effect.gen(function*() {
|
|
400
|
-
|
|
401
|
-
// what's the difference with just calling `fetch` ?
|
|
402
|
-
// 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
|
|
403
|
-
const r = yield* Effect.tryPromise(() => uqrt.suspense()).pipe(
|
|
404
|
-
Effect.catchTag("UnknownError", (err) =>
|
|
405
|
-
isCancelledError(err.cause)
|
|
406
|
-
? Effect.interrupt
|
|
407
|
-
: Effect.die(err.cause))
|
|
408
|
-
)
|
|
503
|
+
const exit = yield* uqrt.awaitResult().pipe(Effect.exit)
|
|
409
504
|
if (!isMounted.value) {
|
|
410
505
|
return yield* Effect.interrupt
|
|
411
506
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
507
|
+
if (Exit.isFailure(exit)) {
|
|
508
|
+
return yield* Exit.failCause(exit.cause)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return [resultRef, latestDefinedRef, fetch, uqrt] as const
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
return runPromise(eff)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
readonly useSuspenseQueryNew: {
|
|
519
|
+
<
|
|
520
|
+
I,
|
|
521
|
+
E,
|
|
522
|
+
A,
|
|
523
|
+
Request extends Req,
|
|
524
|
+
Name extends string
|
|
525
|
+
>(
|
|
526
|
+
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
527
|
+
): {
|
|
528
|
+
<TData = A>(
|
|
529
|
+
arg: I | WatchSource<I>,
|
|
530
|
+
options?: AtomQueryNewOptions<A, TData>
|
|
531
|
+
): Promise<SuspenseQueryView<TData, E>>
|
|
532
|
+
}
|
|
533
|
+
} = <I, E, A, Request extends Req, Name extends string>(
|
|
534
|
+
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
535
|
+
) => {
|
|
536
|
+
const runPromise = makeRunPromise(this.getRuntime())
|
|
537
|
+
const q = this.useQueryNew(self)
|
|
538
|
+
return <TData = A>(
|
|
539
|
+
argOrOptions: I | WatchSource<I>,
|
|
540
|
+
options?: AtomQueryNewOptions<A, TData>
|
|
541
|
+
) => {
|
|
542
|
+
const view = q<TData>(argOrOptions, options)
|
|
543
|
+
const data = computed<TData>(() => {
|
|
544
|
+
const latest = view.data.value
|
|
545
|
+
if (latest === undefined) {
|
|
546
|
+
throw new Error("Internal Error: suspenseNew resolved without a latest value")
|
|
547
|
+
}
|
|
548
|
+
return latest
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
const isMounted = ref(true)
|
|
552
|
+
onBeforeUnmount(() => {
|
|
553
|
+
isMounted.value = false
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
// @effect-diagnostics effect/missingEffectError:off
|
|
557
|
+
const eff = Effect.gen(function*() {
|
|
558
|
+
const exit = yield* view.awaitResult().pipe(Effect.exit)
|
|
559
|
+
if (!isMounted.value) {
|
|
560
|
+
return yield* Effect.interrupt
|
|
424
561
|
}
|
|
425
|
-
if (
|
|
426
|
-
return yield* Exit.failCause(
|
|
562
|
+
if (Exit.isFailure(exit)) {
|
|
563
|
+
return yield* Exit.failCause(exit.cause)
|
|
427
564
|
}
|
|
428
565
|
|
|
429
|
-
|
|
566
|
+
const fetch = (_options?: RefetchOptions) => view.refetch()
|
|
567
|
+
const handle = {
|
|
568
|
+
awaitResult: view.awaitResult,
|
|
569
|
+
refetch: view.refetch,
|
|
570
|
+
refresh: view.refresh,
|
|
571
|
+
registry: view.registry,
|
|
572
|
+
atom: view.atom
|
|
573
|
+
}
|
|
574
|
+
return Object.assign(
|
|
575
|
+
[
|
|
576
|
+
view.result,
|
|
577
|
+
data,
|
|
578
|
+
fetch,
|
|
579
|
+
handle
|
|
580
|
+
] as const,
|
|
581
|
+
{
|
|
582
|
+
...view,
|
|
583
|
+
data
|
|
584
|
+
}
|
|
585
|
+
)
|
|
430
586
|
})
|
|
431
587
|
|
|
432
588
|
return runPromise(eff)
|
|
@@ -493,11 +649,31 @@ type ClientForArgs<
|
|
|
493
649
|
>
|
|
494
650
|
]
|
|
495
651
|
|
|
652
|
+
const makeResolvedAtomQueryInvalidator = <R>(getContext: () => Context.Context<R>): QueryInvalidator => {
|
|
653
|
+
let reactivity: Context.Service.Shape<typeof Reactivity.Reactivity> | undefined
|
|
654
|
+
const getReactivity = () => {
|
|
655
|
+
if (reactivity !== undefined) return reactivity
|
|
656
|
+
const service = Context.getOrUndefined(getContext(), Reactivity.Reactivity)
|
|
657
|
+
if (service === undefined) {
|
|
658
|
+
throw new Error("Reactivity service is missing from the client runtime")
|
|
659
|
+
}
|
|
660
|
+
return reactivity = service
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return {
|
|
664
|
+
invalidateAndAwait: (keys) =>
|
|
665
|
+
invalidateAndAwait(keys).pipe(
|
|
666
|
+
Effect.provideService(Reactivity.Reactivity, getReactivity())
|
|
667
|
+
)
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
496
671
|
export const makeClient = <RT_, RTHooks>(
|
|
497
672
|
// global, but only accessible after startup has completed
|
|
498
673
|
getBaseMrt: () => ManagedRuntime.ManagedRuntime<RT_ | Mix, never>,
|
|
499
674
|
clientFor_: ReturnType<typeof ApiClientFactory["makeFor"]>,
|
|
500
|
-
rtHooks: Layer.Layer<RTHooks, never, Mix
|
|
675
|
+
rtHooks: Layer.Layer<RTHooks, never, Mix>,
|
|
676
|
+
options?: MakeClientOptions
|
|
501
677
|
) => {
|
|
502
678
|
type RT = RT_ | Mix
|
|
503
679
|
const getBaseRt = () => managedRuntimeRt(getBaseMrt())
|
|
@@ -505,16 +681,91 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
505
681
|
let cmd: Effect.Success<typeof makeCommand>
|
|
506
682
|
const useCommand = () => cmd ??= getBaseMrt().runSync(makeCommand)
|
|
507
683
|
|
|
684
|
+
// one AtomRuntime for the query engine, built lazily from the live app context;
|
|
685
|
+
// shares the ManagedRuntime memoMap so layers + Reactivity are the same instances.
|
|
686
|
+
let atomRt: AtomClientRuntime | undefined
|
|
687
|
+
const getAtomRt =
|
|
688
|
+
() => (atomRt ??= makeAtomClientRuntime(() => Layer.succeedContext(getBaseRt()), getBaseMrt().memoMap))
|
|
689
|
+
|
|
690
|
+
const legacyQueryEngine = options?.legacyQueryEngine ?? "tanstack"
|
|
691
|
+
let tanstackQueryClient: ReturnType<typeof makeTanstackQueryClient> | undefined
|
|
692
|
+
const getTanstackQueryClient = () => tanstackQueryClient ??= makeTanstackQueryClient()
|
|
693
|
+
const atomInvalidator = makeResolvedAtomQueryInvalidator(getBaseRt)
|
|
694
|
+
const queryInvalidator = legacyQueryEngine === "tanstack"
|
|
695
|
+
? combineQueryInvalidators(atomInvalidator, makeTanstackQueryInvalidator(getTanstackQueryClient()))
|
|
696
|
+
: atomInvalidator
|
|
697
|
+
|
|
508
698
|
let m: ReturnType<typeof useMutationInt>
|
|
509
|
-
const useMutation = () => m ??= useMutationInt()
|
|
699
|
+
const useMutation = () => m ??= useMutationInt(queryInvalidator)
|
|
510
700
|
|
|
511
701
|
let sm2: ReturnType<typeof makeStreamMutation2>
|
|
512
|
-
const useStreamMutation2 = () => sm2 ??= makeStreamMutation2()
|
|
702
|
+
const useStreamMutation2 = () => sm2 ??= makeStreamMutation2(queryInvalidator)
|
|
513
703
|
|
|
514
|
-
const
|
|
704
|
+
const legacyUseQuery = legacyQueryEngine === "tanstack"
|
|
705
|
+
? makeTanstackQuery(getBaseRt, getTanstackQueryClient())
|
|
706
|
+
: undefined
|
|
707
|
+
const query = new QueryImpl(getBaseRt, getAtomRt, legacyUseQuery)
|
|
515
708
|
const useQuery = query.useQuery
|
|
709
|
+
const useQueryNew = query.useQueryNew
|
|
710
|
+
const useQueryAtom = query.useQueryAtom
|
|
711
|
+
const useQueryFamily = query.useQueryFamily
|
|
516
712
|
const useSuspenseQuery = query.useSuspenseQuery
|
|
713
|
+
const useSuspenseQueryNew = query.useSuspenseQueryNew
|
|
517
714
|
const useStreamQuery = query.useStreamQuery
|
|
715
|
+
const useStreamQueryFamily = query.useStreamQueryFamily
|
|
716
|
+
const useStreamQueryAtom = query.useStreamQueryAtom
|
|
717
|
+
const useStreamQueryNew = query.useStreamQueryNew
|
|
718
|
+
|
|
719
|
+
const isQueryHandler = <H extends { readonly Request: Req }>(handler: H): handler is QueryHandler<H> =>
|
|
720
|
+
handler.Request.type === "query" && !handler.Request.stream
|
|
721
|
+
|
|
722
|
+
const isStreamQueryHandler = <H extends { readonly Request: Req }>(handler: H): handler is QueryStreamHandler<H> =>
|
|
723
|
+
handler.Request.type === "query" && handler.Request.stream
|
|
724
|
+
|
|
725
|
+
const queryHelpersFor = <I, A, E, Request extends Req, Name extends string>(
|
|
726
|
+
handler: RequestHandlerWithInput<I, A, E, RT, Request, Name>
|
|
727
|
+
) => ({
|
|
728
|
+
family: useQueryFamily(handler),
|
|
729
|
+
atom: useQueryAtom(handler),
|
|
730
|
+
query: useQuery(handler),
|
|
731
|
+
queryNew: useQueryNew(handler),
|
|
732
|
+
suspense: useSuspenseQuery(handler),
|
|
733
|
+
suspenseNew: useSuspenseQueryNew(handler)
|
|
734
|
+
})
|
|
735
|
+
|
|
736
|
+
const streamQueryHelpersFor = <I, A, E, Request extends Req, Name extends string>(
|
|
737
|
+
handler: RequestStreamHandlerWithInput<I, A, E, RT, Request, Name>
|
|
738
|
+
) => ({
|
|
739
|
+
family: useStreamQueryFamily(handler),
|
|
740
|
+
atom: useStreamQueryAtom(handler),
|
|
741
|
+
query: useStreamQuery(handler),
|
|
742
|
+
queryNew: useStreamQueryNew(handler)
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
const projectQueryFor = <I, A, E, Request extends Req, Name extends string>(
|
|
746
|
+
handler: RequestHandlerWithInput<I, A, E, RT, Request, Name>
|
|
747
|
+
) =>
|
|
748
|
+
<ProjSchema extends S.Decoder<unknown, never>>(projectionSchema: ProjSchema) => {
|
|
749
|
+
const successSchema = handler.Request.success
|
|
750
|
+
const projectionHash = projectionSchemaHash(projectionSchema)
|
|
751
|
+
const projected = projectHandler(handler.handler, successSchema, projectionSchema)
|
|
752
|
+
const projectedHandler = {
|
|
753
|
+
handler: projected,
|
|
754
|
+
id: handler.id,
|
|
755
|
+
Request: handler.Request,
|
|
756
|
+
queryKeyProjectionHash: projectionHash
|
|
757
|
+
}
|
|
758
|
+
if (handler.options) {
|
|
759
|
+
return {
|
|
760
|
+
request: projected,
|
|
761
|
+
...queryHelpersFor({ ...projectedHandler, options: handler.options })
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
request: projected,
|
|
766
|
+
...queryHelpersFor(projectedHandler)
|
|
767
|
+
}
|
|
768
|
+
}
|
|
518
769
|
|
|
519
770
|
const mergeInvalidation = (
|
|
520
771
|
a?: MutationOptionsBase["queryInvalidation"],
|
|
@@ -555,23 +806,51 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
555
806
|
) => {
|
|
556
807
|
const queries = Struct.keys(client).reduce(
|
|
557
808
|
(acc, key) => {
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
809
|
+
const handler = client[key]
|
|
810
|
+
if (isQueryHandler(handler)) {
|
|
811
|
+
Object.assign(acc, {
|
|
812
|
+
[camelCase(key) + "QueryFamily"]: Object.assign(useQueryFamily(handler), {
|
|
813
|
+
id: client[key].id
|
|
814
|
+
})
|
|
815
|
+
})
|
|
816
|
+
;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(handler), {
|
|
562
817
|
id: client[key].id
|
|
563
818
|
})
|
|
564
|
-
;(acc as any)[camelCase(key) + "
|
|
819
|
+
;(acc as any)[camelCase(key) + "QueryNew"] = Object.assign(useQueryNew(handler), {
|
|
565
820
|
id: client[key].id
|
|
566
821
|
})
|
|
567
|
-
|
|
568
|
-
;(acc as any)[camelCase(key) + "Query"] = Object.assign(useStreamQuery(client[key] as any), {
|
|
822
|
+
;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(handler), {
|
|
569
823
|
id: client[key].id
|
|
570
824
|
})
|
|
825
|
+
;(acc as any)[camelCase(key) + "SuspenseQueryNew"] = Object.assign(
|
|
826
|
+
useSuspenseQueryNew(handler),
|
|
827
|
+
{
|
|
828
|
+
id: client[key].id
|
|
829
|
+
}
|
|
830
|
+
)
|
|
831
|
+
} else if (isStreamQueryHandler(handler)) {
|
|
832
|
+
const streamHelpers = streamQueryHelpersFor(handler)
|
|
833
|
+
Object.assign(acc, {
|
|
834
|
+
[camelCase(key) + "QueryFamily"]: Object.assign(streamHelpers.family, {
|
|
835
|
+
id: client[key].id
|
|
836
|
+
}),
|
|
837
|
+
[camelCase(key) + "Query"]: Object.assign(streamHelpers.query, {
|
|
838
|
+
id: client[key].id
|
|
839
|
+
}),
|
|
840
|
+
[camelCase(key) + "QueryNew"]: Object.assign(streamHelpers.queryNew, {
|
|
841
|
+
id: client[key].id
|
|
842
|
+
})
|
|
843
|
+
})
|
|
571
844
|
}
|
|
572
845
|
return acc
|
|
573
846
|
},
|
|
574
847
|
{} as
|
|
848
|
+
& {
|
|
849
|
+
[
|
|
850
|
+
Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
|
|
851
|
+
: `${ToCamel<string & Key>}QueryFamily`
|
|
852
|
+
]: Queries<RT, QueryHandler<typeof client[Key]>>["family"]
|
|
853
|
+
}
|
|
575
854
|
& {
|
|
576
855
|
// apparently can't get JSDoc in here..
|
|
577
856
|
[
|
|
@@ -579,6 +858,12 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
579
858
|
: `${ToCamel<string & Key>}Query`
|
|
580
859
|
]: Queries<RT, QueryHandler<typeof client[Key]>>["query"]
|
|
581
860
|
}
|
|
861
|
+
& {
|
|
862
|
+
[
|
|
863
|
+
Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
|
|
864
|
+
: `${ToCamel<string & Key>}QueryNew`
|
|
865
|
+
]: Queries<RT, QueryHandler<typeof client[Key]>>["queryNew"]
|
|
866
|
+
}
|
|
582
867
|
// todo: or suspense as an Option?
|
|
583
868
|
& {
|
|
584
869
|
// apparently can't get JSDoc in here..
|
|
@@ -590,12 +875,33 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
590
875
|
QueryHandler<typeof client[Key]>
|
|
591
876
|
>["suspense"]
|
|
592
877
|
}
|
|
878
|
+
& {
|
|
879
|
+
[
|
|
880
|
+
Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
|
|
881
|
+
: `${ToCamel<string & Key>}SuspenseQueryNew`
|
|
882
|
+
]: Queries<
|
|
883
|
+
RT,
|
|
884
|
+
QueryHandler<typeof client[Key]>
|
|
885
|
+
>["suspenseNew"]
|
|
886
|
+
}
|
|
887
|
+
& {
|
|
888
|
+
[
|
|
889
|
+
Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
|
|
890
|
+
: `${ToCamel<string & Key>}QueryFamily`
|
|
891
|
+
]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["family"]
|
|
892
|
+
}
|
|
593
893
|
& {
|
|
594
894
|
[
|
|
595
895
|
Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
|
|
596
896
|
: `${ToCamel<string & Key>}Query`
|
|
597
897
|
]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["query"]
|
|
598
898
|
}
|
|
899
|
+
& {
|
|
900
|
+
[
|
|
901
|
+
Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
|
|
902
|
+
: `${ToCamel<string & Key>}QueryNew`
|
|
903
|
+
]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["queryNew"]
|
|
904
|
+
}
|
|
599
905
|
)
|
|
600
906
|
return queries
|
|
601
907
|
}
|
|
@@ -705,41 +1011,25 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
705
1011
|
const queryResources = makeQueryResources(invalidationResources)
|
|
706
1012
|
const extended = Struct.keys(client).reduce(
|
|
707
1013
|
(acc, key) => {
|
|
708
|
-
const
|
|
709
|
-
const
|
|
1014
|
+
const handler = client[key]
|
|
1015
|
+
const requestType = handler.Request.type
|
|
1016
|
+
const isStream = handler.Request.stream
|
|
710
1017
|
const fn = Command.fn(client[key].id)
|
|
711
|
-
const h_ =
|
|
1018
|
+
const h_ = handler.handler
|
|
712
1019
|
const request = h_
|
|
713
1020
|
;(acc as any)[key] = Object.assign(
|
|
714
|
-
|
|
1021
|
+
isQueryHandler(handler)
|
|
715
1022
|
? {
|
|
716
|
-
...
|
|
1023
|
+
...handler,
|
|
717
1024
|
request,
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
project: (projectionSchema: any) => {
|
|
721
|
-
const successSchema = client[key].Request.success
|
|
722
|
-
const projectionHash = projectionSchemaHash(projectionSchema)
|
|
723
|
-
const projected = projectHandler(h_ as any, successSchema, projectionSchema)
|
|
724
|
-
const fakeHandler = {
|
|
725
|
-
handler: projected,
|
|
726
|
-
id: client[key].id,
|
|
727
|
-
Request: client[key].Request,
|
|
728
|
-
options: client[key].options,
|
|
729
|
-
queryKeyProjectionHash: projectionHash
|
|
730
|
-
}
|
|
731
|
-
return {
|
|
732
|
-
request: projected,
|
|
733
|
-
query: useQuery(fakeHandler as any),
|
|
734
|
-
suspense: useSuspenseQuery(fakeHandler as any)
|
|
735
|
-
}
|
|
736
|
-
}
|
|
1025
|
+
...queryHelpersFor(handler),
|
|
1026
|
+
project: projectQueryFor(handler)
|
|
737
1027
|
}
|
|
738
|
-
:
|
|
1028
|
+
: isStreamQueryHandler(handler)
|
|
739
1029
|
? {
|
|
740
1030
|
...client[key],
|
|
741
1031
|
request,
|
|
742
|
-
|
|
1032
|
+
...streamQueryHelpersFor(handler)
|
|
743
1033
|
}
|
|
744
1034
|
: requestType === "command" && isStream
|
|
745
1035
|
? (() => {
|
|
@@ -911,7 +1201,8 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
911
1201
|
return {
|
|
912
1202
|
Command,
|
|
913
1203
|
useCommand,
|
|
914
|
-
clientFor
|
|
1204
|
+
clientFor,
|
|
1205
|
+
tanstackQueryClient: getTanstackQueryClient()
|
|
915
1206
|
}
|
|
916
1207
|
}
|
|
917
1208
|
|