@effect-app/vue 4.0.0-beta.272 → 4.0.0-beta.274
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 +17 -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 +9 -0
- package/dist/internal/tanstackQuery.d.ts.map +1 -0
- package/dist/internal/tanstackQuery.js +155 -0
- package/dist/makeClient.d.ts +75 -13
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +205 -77
- 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 +122 -13
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +291 -181
- 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 +242 -0
- package/src/makeClient.ts +404 -92
- package/src/mutate.ts +101 -110
- package/src/query.ts +596 -247
- 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,37 @@ 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, makeTanstackQueryCacheUpdater, 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 { 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"
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const projectHandler = <
|
|
34
|
+
I,
|
|
35
|
+
A,
|
|
36
|
+
E,
|
|
37
|
+
R,
|
|
38
|
+
SuccessSchema extends S.Top & { readonly "EncodingServices": R },
|
|
39
|
+
ProjSchema extends S.Top & { readonly "DecodingServices": R }
|
|
40
|
+
>(
|
|
41
|
+
handler: (i: I) => Effect.Effect<A, E, R>,
|
|
42
|
+
successSchema: SuccessSchema,
|
|
43
|
+
projectionSchema: ProjSchema
|
|
35
44
|
) => {
|
|
36
|
-
const encode = S.
|
|
37
|
-
const decode = S.
|
|
38
|
-
return (i:
|
|
45
|
+
const encode = S.encodeUnknownEffect(successSchema)
|
|
46
|
+
const decode = S.decodeUnknownEffect(projectionSchema)
|
|
47
|
+
return (i: I) => handler(i).pipe(Effect.flatMap(encode), Effect.flatMap(decode))
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
const projectionSchemaHash = (schema: S.Top) => String(Hash.hash(schema.ast))
|
|
@@ -202,16 +211,38 @@ export type StreamMutation2WithExtensions<RT, Req> = Req extends
|
|
|
202
211
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
203
212
|
declare const useQuery_: QueryImpl<any>["useQuery"]
|
|
204
213
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
214
|
+
declare const useQueryNew_: QueryImpl<any>["useQueryNew"]
|
|
215
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
216
|
+
declare const useQueryAtom_: QueryImpl<any>["useQueryAtom"]
|
|
217
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
218
|
+
declare const useQueryFamily_: QueryImpl<any>["useQueryFamily"]
|
|
219
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
220
|
+
declare const useSuspenseQueryNew_: QueryImpl<any>["useSuspenseQueryNew"]
|
|
221
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
205
222
|
declare const useSuspenseQuery_: QueryImpl<any>["useSuspenseQuery"]
|
|
206
223
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
207
224
|
declare const useStreamQuery_: QueryImpl<any>["useStreamQuery"]
|
|
225
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
226
|
+
declare const useStreamQueryFamily_: QueryImpl<any>["useStreamQueryFamily"]
|
|
227
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
228
|
+
declare const useStreamQueryAtom_: QueryImpl<any>["useStreamQueryAtom"]
|
|
229
|
+
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
230
|
+
declare const useStreamQueryNew_: QueryImpl<any>["useStreamQueryNew"]
|
|
208
231
|
|
|
209
232
|
export interface ProjectResult<RT, I, B, E, R, Request extends Req, Id extends string> {
|
|
210
233
|
request: (i: I) => Effect.Effect<B, E, R>
|
|
234
|
+
family: Exclude<R, RT> extends never ? ReturnType<typeof useQueryFamily_<I, E, B, Request, Id>>
|
|
235
|
+
: MissingDependencies<RT, R> & {}
|
|
211
236
|
query: Exclude<R, RT> extends never ? ReturnType<typeof useQuery_<I, E, B, Request, Id>>
|
|
212
237
|
: MissingDependencies<RT, R> & {}
|
|
238
|
+
queryNew: Exclude<R, RT> extends never ? ReturnType<typeof useQueryNew_<I, E, B, Request, Id>>
|
|
239
|
+
: MissingDependencies<RT, R> & {}
|
|
213
240
|
suspense: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQuery_<I, E, B, Request, Id>>
|
|
214
241
|
: MissingDependencies<RT, R> & {}
|
|
242
|
+
suspenseNew: Exclude<R, RT> extends never ? ReturnType<typeof useSuspenseQueryNew_<I, E, B, Request, Id>>
|
|
243
|
+
: MissingDependencies<RT, R> & {}
|
|
244
|
+
atom: Exclude<R, RT> extends never ? ReturnType<typeof useQueryAtom_<I, E, B, Request, Id>>
|
|
245
|
+
: MissingDependencies<RT, R> & {}
|
|
215
246
|
}
|
|
216
247
|
|
|
217
248
|
export type QueryProjection<RT, HandlerReq> = HandlerReq extends
|
|
@@ -240,12 +271,30 @@ export interface QueryResultExtensions<Request extends Req, Id extends string, I
|
|
|
240
271
|
* When `I = void` the input argument may be omitted.
|
|
241
272
|
*/
|
|
242
273
|
query: ReturnType<typeof useQuery_<I, E, A, Request, Id>>
|
|
274
|
+
/**
|
|
275
|
+
* Atom-native query helper with object return shape.
|
|
276
|
+
* Additive migration surface; `.query()` remains the compatibility tuple API.
|
|
277
|
+
*/
|
|
278
|
+
queryNew: ReturnType<typeof useQueryNew_<I, E, A, Request, Id>>
|
|
243
279
|
// TODO or suspense as Option?
|
|
244
280
|
/**
|
|
245
281
|
* Like `.query`, but returns a Promise for setup-time awaiting.
|
|
246
282
|
* Use this when integrating with Vue Suspense / error boundaries.
|
|
247
283
|
*/
|
|
248
284
|
suspense: ReturnType<typeof useSuspenseQuery_<I, E, A, Request, Id>>
|
|
285
|
+
/**
|
|
286
|
+
* Promise-based setup helper with object return shape.
|
|
287
|
+
* Additive migration surface; `.suspense()` remains the compatibility tuple API.
|
|
288
|
+
*/
|
|
289
|
+
suspenseNew: ReturnType<typeof useSuspenseQueryNew_<I, E, A, Request, Id>>
|
|
290
|
+
/**
|
|
291
|
+
* Raw query atom for composition outside Vue refs.
|
|
292
|
+
*/
|
|
293
|
+
atom: ReturnType<typeof useQueryAtom_<I, E, A, Request, Id>>
|
|
294
|
+
/**
|
|
295
|
+
* Raw query atom family for composing query graphs before choosing a Vue observer.
|
|
296
|
+
*/
|
|
297
|
+
family: ReturnType<typeof useQueryFamily_<I, E, A, Request, Id>>
|
|
249
298
|
}
|
|
250
299
|
|
|
251
300
|
export type MissingDependencies<RT, R> = {
|
|
@@ -256,26 +305,45 @@ export type MissingDependencies<RT, R> = {
|
|
|
256
305
|
export type Queries<RT, Req> = Req extends
|
|
257
306
|
RequestHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id>
|
|
258
307
|
? Request["type"] extends "query" ? Exclude<R, RT> extends never ? QueryResultExtensions<Request, Id, I, A, E>
|
|
259
|
-
: {
|
|
308
|
+
: {
|
|
309
|
+
atom: MissingDependencies<RT, R> & {}
|
|
310
|
+
family: MissingDependencies<RT, R> & {}
|
|
311
|
+
query: MissingDependencies<RT, R> & {}
|
|
312
|
+
queryNew: MissingDependencies<RT, R> & {}
|
|
313
|
+
suspense: MissingDependencies<RT, R> & {}
|
|
314
|
+
suspenseNew: MissingDependencies<RT, R> & {}
|
|
315
|
+
}
|
|
260
316
|
: never
|
|
261
317
|
: never
|
|
262
318
|
|
|
263
319
|
export interface StreamQueryExtensions<Request extends Req, Id extends string, I, A, E> {
|
|
320
|
+
atom: ReturnType<typeof useStreamQueryAtom_<I, E, A, Request, Id>>
|
|
321
|
+
family: StreamQueryAtomFamily<I, A, E>
|
|
264
322
|
/**
|
|
265
323
|
* Stream helper for query-stream requests.
|
|
266
|
-
*
|
|
267
|
-
*
|
|
324
|
+
* Legacy compatibility helper. Collects the whole stream into an array before
|
|
325
|
+
* publishing data.
|
|
268
326
|
* When `I = void` the input argument may be omitted.
|
|
269
327
|
*/
|
|
270
328
|
query: ReturnType<typeof useStreamQuery_<I, E, A, Request, Id>>
|
|
329
|
+
/**
|
|
330
|
+
* Atom-native stream query helper. Exposes incremental pull state and a `pull`
|
|
331
|
+
* command instead of collecting the whole stream first.
|
|
332
|
+
*/
|
|
333
|
+
queryNew: ReturnType<typeof useStreamQueryNew_<I, E, A, Request, Id>>
|
|
271
334
|
}
|
|
272
335
|
export type StreamQueries<RT, HandlerReq> = HandlerReq extends
|
|
273
336
|
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer Request, infer Id, infer _Final>
|
|
274
337
|
? Exclude<R, RT> extends never ? StreamQueryExtensions<Request, Id, I, A, E>
|
|
275
|
-
: {
|
|
338
|
+
: {
|
|
339
|
+
atom: MissingDependencies<RT, R> & {}
|
|
340
|
+
family: MissingDependencies<RT, R> & {}
|
|
341
|
+
query: MissingDependencies<RT, R> & {}
|
|
342
|
+
queryNew: MissingDependencies<RT, R> & {}
|
|
343
|
+
}
|
|
276
344
|
: never
|
|
277
345
|
|
|
278
|
-
const _useMutation = makeMutation()
|
|
346
|
+
const _useMutation = makeMutation(atomQueryInvalidator)
|
|
279
347
|
|
|
280
348
|
const wrapWithSpan = (self: { id: string }, mut: any) => {
|
|
281
349
|
const span = (eff: Effect.Effect<any, any, any>) =>
|
|
@@ -308,8 +376,8 @@ export const useMutation: typeof _useMutation = (<
|
|
|
308
376
|
* Executes query cache invalidation based on default rules or provided option.
|
|
309
377
|
* adds a span with the mutation id
|
|
310
378
|
*/
|
|
311
|
-
export const useMutationInt = (): typeof _useMutation => {
|
|
312
|
-
const _useMutation = useMakeMutation()
|
|
379
|
+
export const useMutationInt = (queryInvalidator: QueryInvalidator): typeof _useMutation => {
|
|
380
|
+
const _useMutation = useMakeMutation(queryInvalidator)
|
|
313
381
|
return (<
|
|
314
382
|
I,
|
|
315
383
|
E,
|
|
@@ -328,13 +396,31 @@ export const useMutationInt = (): typeof _useMutation => {
|
|
|
328
396
|
|
|
329
397
|
export type ClientFrom<M extends RequestsAny> = RequestHandlers<never, never, M, ExtractModuleName<M>>
|
|
330
398
|
|
|
399
|
+
export interface MakeClientOptions {
|
|
400
|
+
/**
|
|
401
|
+
* Selects the engine behind legacy `.query()` / `.suspense()` helpers.
|
|
402
|
+
* Atom-native `.atom()` / `.family()` / `.queryNew()` / `.suspenseNew()` always use Atom.
|
|
403
|
+
*/
|
|
404
|
+
readonly legacyQueryEngine?: "atom" | "tanstack"
|
|
405
|
+
}
|
|
406
|
+
|
|
331
407
|
export class QueryImpl<R> {
|
|
332
408
|
readonly getRuntime: () => Context.Context<R>
|
|
333
409
|
|
|
334
|
-
constructor(
|
|
410
|
+
constructor(
|
|
411
|
+
getRuntime: () => Context.Context<R>,
|
|
412
|
+
getAtomRt: () => AtomClientRuntime,
|
|
413
|
+
legacyUseQuery?: ReturnType<typeof makeQuery<R>>
|
|
414
|
+
) {
|
|
335
415
|
this.getRuntime = getRuntime
|
|
336
|
-
this.useQuery = makeQuery(this.getRuntime)
|
|
337
|
-
this.
|
|
416
|
+
this.useQuery = legacyUseQuery ?? makeQuery(this.getRuntime, getAtomRt)
|
|
417
|
+
this.useQueryNew = makeQueryNew(this.getRuntime, getAtomRt)
|
|
418
|
+
this.useQueryAtom = makeQueryAtom(this.getRuntime, getAtomRt)
|
|
419
|
+
this.useQueryFamily = makeQueryFamily(this.getRuntime, getAtomRt)
|
|
420
|
+
this.useStreamQuery = makeStreamQuery(this.getRuntime, getAtomRt)
|
|
421
|
+
this.useStreamQueryFamily = makeStreamQueryFamily(this.getRuntime, getAtomRt)
|
|
422
|
+
this.useStreamQueryAtom = makeStreamQueryAtom(this.getRuntime, getAtomRt)
|
|
423
|
+
this.useStreamQueryNew = makeStreamQueryNew(this.getRuntime, getAtomRt)
|
|
338
424
|
}
|
|
339
425
|
/**
|
|
340
426
|
* Effect results are passed to the caller, including errors.
|
|
@@ -342,12 +428,24 @@ export class QueryImpl<R> {
|
|
|
342
428
|
*/
|
|
343
429
|
readonly useQuery: ReturnType<typeof makeQuery<R>>
|
|
344
430
|
|
|
431
|
+
readonly useQueryNew: ReturnType<typeof makeQueryNew<R>>
|
|
432
|
+
|
|
433
|
+
readonly useQueryAtom: ReturnType<typeof makeQueryAtom<R>>
|
|
434
|
+
|
|
435
|
+
readonly useQueryFamily: ReturnType<typeof makeQueryFamily<R>>
|
|
436
|
+
|
|
345
437
|
/**
|
|
346
438
|
* Stream results are accumulated as an array of chunks and returned as reactive state.
|
|
347
439
|
* @deprecated use client helpers instead (.query())
|
|
348
440
|
*/
|
|
349
441
|
readonly useStreamQuery: ReturnType<typeof makeStreamQuery<R>>
|
|
350
442
|
|
|
443
|
+
readonly useStreamQueryFamily: ReturnType<typeof makeStreamQueryFamily<R>>
|
|
444
|
+
|
|
445
|
+
readonly useStreamQueryAtom: ReturnType<typeof makeStreamQueryAtom<R>>
|
|
446
|
+
|
|
447
|
+
readonly useStreamQueryNew: ReturnType<typeof makeStreamQueryNew<R>>
|
|
448
|
+
|
|
351
449
|
/**
|
|
352
450
|
* The difference with useQuery is that this function will return a Promise you can await in the Setup,
|
|
353
451
|
* which ensures that either there always is a latest value, or an error occurs on load.
|
|
@@ -370,7 +468,10 @@ export class QueryImpl<R> {
|
|
|
370
468
|
>(
|
|
371
469
|
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
372
470
|
): {
|
|
373
|
-
<TData = A>(
|
|
471
|
+
<TData = A>(
|
|
472
|
+
arg: I | WatchSource<I>,
|
|
473
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
474
|
+
): Promise<
|
|
374
475
|
readonly [
|
|
375
476
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
376
477
|
ComputedRef<TData>,
|
|
@@ -385,10 +486,19 @@ export class QueryImpl<R> {
|
|
|
385
486
|
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
386
487
|
) => {
|
|
387
488
|
const runPromise = makeRunPromise(this.getRuntime())
|
|
388
|
-
const q = this.useQuery(self
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
|
|
489
|
+
const q = this.useQuery(self)
|
|
490
|
+
return <TData = A>(
|
|
491
|
+
argOrOptions: I | WatchSource<I>,
|
|
492
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
493
|
+
) => {
|
|
494
|
+
const [resultRef, latestRef, fetch, uqrt] = q<TData>(argOrOptions, options)
|
|
495
|
+
const latestDefinedRef = computed<TData>(() => {
|
|
496
|
+
const latest = latestRef.value
|
|
497
|
+
if (latest === undefined) {
|
|
498
|
+
throw new Error("Internal Error: suspense resolved without a latest value")
|
|
499
|
+
}
|
|
500
|
+
return latest
|
|
501
|
+
})
|
|
392
502
|
|
|
393
503
|
const isMounted = ref(true)
|
|
394
504
|
onBeforeUnmount(() => {
|
|
@@ -397,36 +507,89 @@ export class QueryImpl<R> {
|
|
|
397
507
|
|
|
398
508
|
// @effect-diagnostics effect/missingEffectError:off
|
|
399
509
|
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
|
-
)
|
|
510
|
+
const exit = yield* uqrt.awaitResult().pipe(Effect.exit)
|
|
409
511
|
if (!isMounted.value) {
|
|
410
512
|
return yield* Effect.interrupt
|
|
411
513
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
514
|
+
if (Exit.isFailure(exit)) {
|
|
515
|
+
return yield* Exit.failCause(exit.cause)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return [resultRef, latestDefinedRef, fetch, uqrt] as const
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
return runPromise(eff)
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
readonly useSuspenseQueryNew: {
|
|
526
|
+
<
|
|
527
|
+
I,
|
|
528
|
+
E,
|
|
529
|
+
A,
|
|
530
|
+
Request extends Req,
|
|
531
|
+
Name extends string
|
|
532
|
+
>(
|
|
533
|
+
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
534
|
+
): {
|
|
535
|
+
<TData = A>(
|
|
536
|
+
arg: I | WatchSource<I>,
|
|
537
|
+
options?: AtomQueryNewOptions<A, TData>
|
|
538
|
+
): Promise<SuspenseQueryView<TData, E>>
|
|
539
|
+
}
|
|
540
|
+
} = <I, E, A, Request extends Req, Name extends string>(
|
|
541
|
+
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
542
|
+
) => {
|
|
543
|
+
const runPromise = makeRunPromise(this.getRuntime())
|
|
544
|
+
const q = this.useQueryNew(self)
|
|
545
|
+
return <TData = A>(
|
|
546
|
+
argOrOptions: I | WatchSource<I>,
|
|
547
|
+
options?: AtomQueryNewOptions<A, TData>
|
|
548
|
+
) => {
|
|
549
|
+
const view = q<TData>(argOrOptions, options)
|
|
550
|
+
const data = computed<TData>(() => {
|
|
551
|
+
const latest = view.data.value
|
|
552
|
+
if (latest === undefined) {
|
|
553
|
+
throw new Error("Internal Error: suspenseNew resolved without a latest value")
|
|
554
|
+
}
|
|
555
|
+
return latest
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
const isMounted = ref(true)
|
|
559
|
+
onBeforeUnmount(() => {
|
|
560
|
+
isMounted.value = false
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
// @effect-diagnostics effect/missingEffectError:off
|
|
564
|
+
const eff = Effect.gen(function*() {
|
|
565
|
+
const exit = yield* view.awaitResult().pipe(Effect.exit)
|
|
566
|
+
if (!isMounted.value) {
|
|
567
|
+
return yield* Effect.interrupt
|
|
424
568
|
}
|
|
425
|
-
if (
|
|
426
|
-
return yield* Exit.failCause(
|
|
569
|
+
if (Exit.isFailure(exit)) {
|
|
570
|
+
return yield* Exit.failCause(exit.cause)
|
|
427
571
|
}
|
|
428
572
|
|
|
429
|
-
|
|
573
|
+
const fetch = (_options?: RefetchOptions) => view.refetch()
|
|
574
|
+
const handle = {
|
|
575
|
+
awaitResult: view.awaitResult,
|
|
576
|
+
refetch: view.refetch,
|
|
577
|
+
refresh: view.refresh,
|
|
578
|
+
registry: view.registry,
|
|
579
|
+
atom: view.atom
|
|
580
|
+
}
|
|
581
|
+
return Object.assign(
|
|
582
|
+
[
|
|
583
|
+
view.result,
|
|
584
|
+
data,
|
|
585
|
+
fetch,
|
|
586
|
+
handle
|
|
587
|
+
] as const,
|
|
588
|
+
{
|
|
589
|
+
...view,
|
|
590
|
+
data
|
|
591
|
+
}
|
|
592
|
+
)
|
|
430
593
|
})
|
|
431
594
|
|
|
432
595
|
return runPromise(eff)
|
|
@@ -493,11 +656,31 @@ type ClientForArgs<
|
|
|
493
656
|
>
|
|
494
657
|
]
|
|
495
658
|
|
|
659
|
+
const makeResolvedAtomQueryInvalidator = <R>(getContext: () => Context.Context<R>): QueryInvalidator => {
|
|
660
|
+
let reactivity: Context.Service.Shape<typeof Reactivity.Reactivity> | undefined
|
|
661
|
+
const getReactivity = () => {
|
|
662
|
+
if (reactivity !== undefined) return reactivity
|
|
663
|
+
const service = Context.getOrUndefined(getContext(), Reactivity.Reactivity)
|
|
664
|
+
if (service === undefined) {
|
|
665
|
+
throw new Error("Reactivity service is missing from the client runtime")
|
|
666
|
+
}
|
|
667
|
+
return reactivity = service
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return {
|
|
671
|
+
invalidateAndAwait: (keys) =>
|
|
672
|
+
invalidateAndAwait(keys).pipe(
|
|
673
|
+
Effect.provideService(Reactivity.Reactivity, getReactivity())
|
|
674
|
+
)
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
496
678
|
export const makeClient = <RT_, RTHooks>(
|
|
497
679
|
// global, but only accessible after startup has completed
|
|
498
680
|
getBaseMrt: () => ManagedRuntime.ManagedRuntime<RT_ | Mix, never>,
|
|
499
681
|
clientFor_: ReturnType<typeof ApiClientFactory["makeFor"]>,
|
|
500
|
-
rtHooks: Layer.Layer<RTHooks, never, Mix
|
|
682
|
+
rtHooks: Layer.Layer<RTHooks, never, Mix>,
|
|
683
|
+
options?: MakeClientOptions
|
|
501
684
|
) => {
|
|
502
685
|
type RT = RT_ | Mix
|
|
503
686
|
const getBaseRt = () => managedRuntimeRt(getBaseMrt())
|
|
@@ -505,16 +688,105 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
505
688
|
let cmd: Effect.Success<typeof makeCommand>
|
|
506
689
|
const useCommand = () => cmd ??= getBaseMrt().runSync(makeCommand)
|
|
507
690
|
|
|
691
|
+
// one AtomRuntime for the query engine, built lazily from the live app context;
|
|
692
|
+
// shares the ManagedRuntime memoMap so layers + Reactivity are the same instances.
|
|
693
|
+
let atomRt: AtomClientRuntime | undefined
|
|
694
|
+
const getAtomRt =
|
|
695
|
+
() => (atomRt ??= makeAtomClientRuntime(() => Layer.succeedContext(getBaseRt()), getBaseMrt().memoMap))
|
|
696
|
+
|
|
697
|
+
const legacyQueryEngine = options?.legacyQueryEngine ?? "tanstack"
|
|
698
|
+
let tanstackQueryClient: ReturnType<typeof makeTanstackQueryClient> | undefined
|
|
699
|
+
const getTanstackQueryClient = () => tanstackQueryClient ??= makeTanstackQueryClient()
|
|
700
|
+
const atomInvalidator = makeResolvedAtomQueryInvalidator(getBaseRt)
|
|
701
|
+
const queryInvalidator = legacyQueryEngine === "tanstack"
|
|
702
|
+
? combineQueryInvalidators(atomInvalidator, makeTanstackQueryInvalidator(getTanstackQueryClient()))
|
|
703
|
+
: atomInvalidator
|
|
704
|
+
setQueryCacheUpdater(
|
|
705
|
+
legacyQueryEngine === "tanstack"
|
|
706
|
+
? combineQueryCacheUpdaters(
|
|
707
|
+
makeTanstackQueryCacheUpdater(getTanstackQueryClient()),
|
|
708
|
+
optionalAtomQueryCacheUpdater
|
|
709
|
+
)
|
|
710
|
+
: atomQueryCacheUpdater
|
|
711
|
+
)
|
|
712
|
+
|
|
508
713
|
let m: ReturnType<typeof useMutationInt>
|
|
509
|
-
const useMutation = () => m ??= useMutationInt()
|
|
714
|
+
const useMutation = () => m ??= useMutationInt(queryInvalidator)
|
|
510
715
|
|
|
511
716
|
let sm2: ReturnType<typeof makeStreamMutation2>
|
|
512
|
-
const useStreamMutation2 = () => sm2 ??= makeStreamMutation2()
|
|
717
|
+
const useStreamMutation2 = () => sm2 ??= makeStreamMutation2(queryInvalidator)
|
|
513
718
|
|
|
514
|
-
const
|
|
719
|
+
const legacyUseQuery = legacyQueryEngine === "tanstack"
|
|
720
|
+
? makeTanstackQuery(getBaseRt, getTanstackQueryClient())
|
|
721
|
+
: undefined
|
|
722
|
+
const query = new QueryImpl(getBaseRt, getAtomRt, legacyUseQuery)
|
|
515
723
|
const useQuery = query.useQuery
|
|
724
|
+
const useQueryNew = query.useQueryNew
|
|
725
|
+
const useQueryAtom = query.useQueryAtom
|
|
726
|
+
const useQueryFamily = query.useQueryFamily
|
|
516
727
|
const useSuspenseQuery = query.useSuspenseQuery
|
|
728
|
+
const useSuspenseQueryNew = query.useSuspenseQueryNew
|
|
517
729
|
const useStreamQuery = query.useStreamQuery
|
|
730
|
+
const useStreamQueryFamily = query.useStreamQueryFamily
|
|
731
|
+
const useStreamQueryAtom = query.useStreamQueryAtom
|
|
732
|
+
const useStreamQueryNew = query.useStreamQueryNew
|
|
733
|
+
|
|
734
|
+
const isQueryHandler = <H extends { readonly Request: Req }>(handler: H): handler is QueryHandler<H> =>
|
|
735
|
+
handler.Request.type === "query" && !handler.Request.stream
|
|
736
|
+
|
|
737
|
+
const isStreamQueryHandler = <H extends { readonly Request: Req }>(handler: H): handler is QueryStreamHandler<H> =>
|
|
738
|
+
handler.Request.type === "query" && handler.Request.stream
|
|
739
|
+
|
|
740
|
+
const queryHelpersFor = <I, A, E, Request extends Req, Name extends string>(
|
|
741
|
+
handler: RequestHandlerWithInput<I, A, E, RT, Request, Name>
|
|
742
|
+
) => ({
|
|
743
|
+
family: useQueryFamily(handler),
|
|
744
|
+
atom: useQueryAtom(handler),
|
|
745
|
+
query: useQuery(handler),
|
|
746
|
+
queryNew: useQueryNew(handler),
|
|
747
|
+
suspense: useSuspenseQuery(handler),
|
|
748
|
+
suspenseNew: useSuspenseQueryNew(handler)
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
const streamQueryHelpersFor = <I, A, E, Request extends Req, Name extends string>(
|
|
752
|
+
handler: RequestStreamHandlerWithInput<I, A, E, RT, Request, Name>
|
|
753
|
+
) => ({
|
|
754
|
+
family: useStreamQueryFamily(handler),
|
|
755
|
+
atom: useStreamQueryAtom(handler),
|
|
756
|
+
query: useStreamQuery(handler),
|
|
757
|
+
queryNew: useStreamQueryNew(handler)
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
const projectQueryFor = <
|
|
761
|
+
I,
|
|
762
|
+
A,
|
|
763
|
+
E,
|
|
764
|
+
Request extends Req & { readonly success: S.Top & { readonly "EncodingServices": RT } },
|
|
765
|
+
Name extends string
|
|
766
|
+
>(
|
|
767
|
+
handler: RequestHandlerWithInput<I, A, E, RT, Request, Name>
|
|
768
|
+
) =>
|
|
769
|
+
<ProjSchema extends S.Top & { readonly "DecodingServices": RT }>(projectionSchema: ProjSchema) => {
|
|
770
|
+
const successSchema = handler.Request.success
|
|
771
|
+
const projectionHash = projectionSchemaHash(projectionSchema)
|
|
772
|
+
const projected = projectHandler(handler.handler, successSchema, projectionSchema)
|
|
773
|
+
const projectedHandler = {
|
|
774
|
+
handler: projected,
|
|
775
|
+
id: handler.id,
|
|
776
|
+
Request: handler.Request,
|
|
777
|
+
queryKeyProjectionHash: projectionHash
|
|
778
|
+
}
|
|
779
|
+
if (handler.options) {
|
|
780
|
+
return {
|
|
781
|
+
request: projected,
|
|
782
|
+
...queryHelpersFor({ ...projectedHandler, options: handler.options })
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
request: projected,
|
|
787
|
+
...queryHelpersFor(projectedHandler)
|
|
788
|
+
}
|
|
789
|
+
}
|
|
518
790
|
|
|
519
791
|
const mergeInvalidation = (
|
|
520
792
|
a?: MutationOptionsBase["queryInvalidation"],
|
|
@@ -555,23 +827,51 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
555
827
|
) => {
|
|
556
828
|
const queries = Struct.keys(client).reduce(
|
|
557
829
|
(acc, key) => {
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
830
|
+
const handler = client[key]
|
|
831
|
+
if (isQueryHandler(handler)) {
|
|
832
|
+
Object.assign(acc, {
|
|
833
|
+
[camelCase(key) + "QueryFamily"]: Object.assign(useQueryFamily(handler), {
|
|
834
|
+
id: client[key].id
|
|
835
|
+
})
|
|
836
|
+
})
|
|
837
|
+
;(acc as any)[camelCase(key) + "Query"] = Object.assign(useQuery(handler), {
|
|
562
838
|
id: client[key].id
|
|
563
839
|
})
|
|
564
|
-
;(acc as any)[camelCase(key) + "
|
|
840
|
+
;(acc as any)[camelCase(key) + "QueryNew"] = Object.assign(useQueryNew(handler), {
|
|
565
841
|
id: client[key].id
|
|
566
842
|
})
|
|
567
|
-
|
|
568
|
-
;(acc as any)[camelCase(key) + "Query"] = Object.assign(useStreamQuery(client[key] as any), {
|
|
843
|
+
;(acc as any)[camelCase(key) + "SuspenseQuery"] = Object.assign(useSuspenseQuery(handler), {
|
|
569
844
|
id: client[key].id
|
|
570
845
|
})
|
|
846
|
+
;(acc as any)[camelCase(key) + "SuspenseQueryNew"] = Object.assign(
|
|
847
|
+
useSuspenseQueryNew(handler),
|
|
848
|
+
{
|
|
849
|
+
id: client[key].id
|
|
850
|
+
}
|
|
851
|
+
)
|
|
852
|
+
} else if (isStreamQueryHandler(handler)) {
|
|
853
|
+
const streamHelpers = streamQueryHelpersFor(handler)
|
|
854
|
+
Object.assign(acc, {
|
|
855
|
+
[camelCase(key) + "QueryFamily"]: Object.assign(streamHelpers.family, {
|
|
856
|
+
id: client[key].id
|
|
857
|
+
}),
|
|
858
|
+
[camelCase(key) + "Query"]: Object.assign(streamHelpers.query, {
|
|
859
|
+
id: client[key].id
|
|
860
|
+
}),
|
|
861
|
+
[camelCase(key) + "QueryNew"]: Object.assign(streamHelpers.queryNew, {
|
|
862
|
+
id: client[key].id
|
|
863
|
+
})
|
|
864
|
+
})
|
|
571
865
|
}
|
|
572
866
|
return acc
|
|
573
867
|
},
|
|
574
868
|
{} as
|
|
869
|
+
& {
|
|
870
|
+
[
|
|
871
|
+
Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
|
|
872
|
+
: `${ToCamel<string & Key>}QueryFamily`
|
|
873
|
+
]: Queries<RT, QueryHandler<typeof client[Key]>>["family"]
|
|
874
|
+
}
|
|
575
875
|
& {
|
|
576
876
|
// apparently can't get JSDoc in here..
|
|
577
877
|
[
|
|
@@ -579,6 +879,12 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
579
879
|
: `${ToCamel<string & Key>}Query`
|
|
580
880
|
]: Queries<RT, QueryHandler<typeof client[Key]>>["query"]
|
|
581
881
|
}
|
|
882
|
+
& {
|
|
883
|
+
[
|
|
884
|
+
Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
|
|
885
|
+
: `${ToCamel<string & Key>}QueryNew`
|
|
886
|
+
]: Queries<RT, QueryHandler<typeof client[Key]>>["queryNew"]
|
|
887
|
+
}
|
|
582
888
|
// todo: or suspense as an Option?
|
|
583
889
|
& {
|
|
584
890
|
// apparently can't get JSDoc in here..
|
|
@@ -590,12 +896,33 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
590
896
|
QueryHandler<typeof client[Key]>
|
|
591
897
|
>["suspense"]
|
|
592
898
|
}
|
|
899
|
+
& {
|
|
900
|
+
[
|
|
901
|
+
Key in keyof typeof client as QueryHandler<typeof client[Key]> extends never ? never
|
|
902
|
+
: `${ToCamel<string & Key>}SuspenseQueryNew`
|
|
903
|
+
]: Queries<
|
|
904
|
+
RT,
|
|
905
|
+
QueryHandler<typeof client[Key]>
|
|
906
|
+
>["suspenseNew"]
|
|
907
|
+
}
|
|
908
|
+
& {
|
|
909
|
+
[
|
|
910
|
+
Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
|
|
911
|
+
: `${ToCamel<string & Key>}QueryFamily`
|
|
912
|
+
]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["family"]
|
|
913
|
+
}
|
|
593
914
|
& {
|
|
594
915
|
[
|
|
595
916
|
Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
|
|
596
917
|
: `${ToCamel<string & Key>}Query`
|
|
597
918
|
]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["query"]
|
|
598
919
|
}
|
|
920
|
+
& {
|
|
921
|
+
[
|
|
922
|
+
Key in keyof typeof client as QueryStreamHandler<typeof client[Key]> extends never ? never
|
|
923
|
+
: `${ToCamel<string & Key>}QueryNew`
|
|
924
|
+
]: StreamQueries<RT, QueryStreamHandler<typeof client[Key]>>["queryNew"]
|
|
925
|
+
}
|
|
599
926
|
)
|
|
600
927
|
return queries
|
|
601
928
|
}
|
|
@@ -705,41 +1032,25 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
705
1032
|
const queryResources = makeQueryResources(invalidationResources)
|
|
706
1033
|
const extended = Struct.keys(client).reduce(
|
|
707
1034
|
(acc, key) => {
|
|
708
|
-
const
|
|
709
|
-
const
|
|
1035
|
+
const handler = client[key]
|
|
1036
|
+
const requestType = handler.Request.type
|
|
1037
|
+
const isStream = handler.Request.stream
|
|
710
1038
|
const fn = Command.fn(client[key].id)
|
|
711
|
-
const h_ =
|
|
1039
|
+
const h_ = handler.handler
|
|
712
1040
|
const request = h_
|
|
713
1041
|
;(acc as any)[key] = Object.assign(
|
|
714
|
-
|
|
1042
|
+
isQueryHandler(handler)
|
|
715
1043
|
? {
|
|
716
|
-
...
|
|
1044
|
+
...handler,
|
|
717
1045
|
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
|
-
}
|
|
1046
|
+
...queryHelpersFor(handler),
|
|
1047
|
+
project: projectQueryFor(handler)
|
|
737
1048
|
}
|
|
738
|
-
:
|
|
1049
|
+
: isStreamQueryHandler(handler)
|
|
739
1050
|
? {
|
|
740
1051
|
...client[key],
|
|
741
1052
|
request,
|
|
742
|
-
|
|
1053
|
+
...streamQueryHelpersFor(handler)
|
|
743
1054
|
}
|
|
744
1055
|
: requestType === "command" && isStream
|
|
745
1056
|
? (() => {
|
|
@@ -911,7 +1222,8 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
911
1222
|
return {
|
|
912
1223
|
Command,
|
|
913
1224
|
useCommand,
|
|
914
|
-
clientFor
|
|
1225
|
+
clientFor,
|
|
1226
|
+
tanstackQueryClient: getTanstackQueryClient()
|
|
915
1227
|
}
|
|
916
1228
|
}
|
|
917
1229
|
|