@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/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 type * as Context from "effect-app/Context"
7
+ import * as Context from "effect-app/Context"
8
8
  import * as Effect from "effect-app/Effect"
9
- import type * as Layer from "effect-app/Layer"
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 { type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
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: any) => Effect.Effect<any, any, any>,
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: S.Top
36
+ projectionSchema: ProjSchema
35
37
  ) => {
36
- const encode = S.encodeEffect(successSchema)
37
- const decode = S.decodeEffectConcurrently(projectionSchema)
38
- return (i: any) => handler(i).pipe(Effect.flatMap(encode), Effect.flatMap(decode))
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
- : { query: MissingDependencies<RT, R> & {}; suspense: MissingDependencies<RT, R> & {} }
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
- * Runs as a tracked Vue Query and returns reactive state with accumulated chunks.
267
- * Data is an array of all chunks received so far.
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
- : { query: MissingDependencies<RT, R> & {} }
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(getRuntime: () => Context.Context<R>) {
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.useStreamQuery = makeStreamQuery(this.getRuntime)
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>(arg: I | WatchSource<I>, options?: CustomUndefinedInitialQueryOptions<A, E, TData>): Promise<
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 as any) as any
389
- return (argOrOptions?: any, options?: any) => {
390
- const [resultRef, latestRef, fetch, uqrt] = q(argOrOptions, { ...options, suspense: true } // experimental_prefetchInRender: true }
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
- // we want to throw on error so that we can catch cancelled error and skip handling it
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
- const result = yield* awaitResolvedSuspenseResult(resultRef)
413
- if (AsyncResult.isInitial(result)) {
414
- console.error("Internal Error: Promise should be resolved already", {
415
- self,
416
- argOrOptions,
417
- options,
418
- r,
419
- resultRef
420
- })
421
- return yield* Effect.die(
422
- "Internal Error: Promise should be resolved already"
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 (AsyncResult.isFailure(result)) {
426
- return yield* Exit.failCause(result.cause)
562
+ if (Exit.isFailure(exit)) {
563
+ return yield* Exit.failCause(exit.cause)
427
564
  }
428
565
 
429
- return [resultRef, latestRef, fetch, uqrt] as const
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 query = new QueryImpl(getBaseRt)
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 requestType = client[key].Request.type
559
- const isStream = client[key].Request.stream
560
- if (requestType === "query" && !isStream) {
561
- ;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(client[key] as any), {
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) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(client[key] as any), {
819
+ ;(acc as any)[camelCase(key) + "QueryNew"] = Object.assign(useQueryNew(handler), {
565
820
  id: client[key].id
566
821
  })
567
- } else if (requestType === "query" && isStream) {
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 requestType = client[key].Request.type
709
- const isStream = client[key].Request.stream
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_ = client[key].handler
1018
+ const h_ = handler.handler
712
1019
  const request = h_
713
1020
  ;(acc as any)[key] = Object.assign(
714
- requestType === "query" && !isStream
1021
+ isQueryHandler(handler)
715
1022
  ? {
716
- ...client[key],
1023
+ ...handler,
717
1024
  request,
718
- query: useQuery(client[key] as any),
719
- suspense: useSuspenseQuery(client[key] as any),
720
- project: (projectionSchema: any) => {
721
- const successSchema = client[key].Request.success
722
- const projectionHash = projectionSchemaHash(projectionSchema)
723
- const projected = projectHandler(h_ as any, successSchema, projectionSchema)
724
- const fakeHandler = {
725
- handler: projected,
726
- id: client[key].id,
727
- Request: client[key].Request,
728
- options: client[key].options,
729
- queryKeyProjectionHash: projectionHash
730
- }
731
- return {
732
- request: projected,
733
- query: useQuery(fakeHandler as any),
734
- suspense: useSuspenseQuery(fakeHandler as any)
735
- }
736
- }
1025
+ ...queryHelpersFor(handler),
1026
+ project: projectQueryFor(handler)
737
1027
  }
738
- : requestType === "query" && isStream
1028
+ : isStreamQueryHandler(handler)
739
1029
  ? {
740
1030
  ...client[key],
741
1031
  request,
742
- query: useStreamQuery(client[key] as any)
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