@effect-app/vue 4.0.0-beta.163 → 4.0.0-beta.164

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
@@ -3,7 +3,9 @@ import { type InvalidateOptions, type InvalidateQueryFilters, isCancelledError,
3
3
  import { camelCase } from "change-case"
4
4
  import { type Context, Effect, Exit, Hash, type Layer, type ManagedRuntime, S, Struct } from "effect-app"
5
5
  import { type ApiClientFactory, type Req } from "effect-app/client"
6
- import type { RequestInputFromMake, ExtractModuleName, RequestHandler, RequestHandlers, RequestHandlerWithInput, RequestsAny } from "effect-app/client/clientFor"
6
+ import type { ExtractModuleName, RequestHandler, RequestHandlers, RequestHandlerWithInput, RequestInputFromMake, RequestsAny } from "effect-app/client/clientFor"
7
+ import type { InvalidationCallback } from "effect-app/client/makeClient"
8
+ import type * as ExitResult from "effect/Exit"
7
9
  import { type Fiber } from "effect/Fiber"
8
10
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
9
11
  import { type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
@@ -481,6 +483,28 @@ const managedRuntimeRt = <A, E>(mrt: ManagedRuntime.ManagedRuntime<A, E>) => mrt
481
483
 
482
484
  type Base = I18n | Toast
483
485
  type Mix = ApiClientFactory | Commander | Base
486
+
487
+ type InvalidationResources = Record<string, Record<string, { readonly type: "command" | "query" }>>
488
+ type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I
489
+ : never
490
+
491
+ type CommandInvalidationResources<Req> = Req extends {
492
+ readonly type: "command"
493
+ readonly config?: infer Config
494
+ } ? Config extends {
495
+ readonly invalidationResources?: infer Resources
496
+ } ? Resources extends InvalidationResources ? Resources : never
497
+ : never
498
+ : never
499
+
500
+ type InvalidationResourcesForUnion<M extends RequestsAny> = {
501
+ [K in keyof M]: CommandInvalidationResources<M[K]>
502
+ }[keyof M]
503
+
504
+ type InvalidationResourcesFor<M extends RequestsAny> = [InvalidationResourcesForUnion<M>] extends [never] ? never
505
+ : UnionToIntersection<InvalidationResourcesForUnion<M>> extends infer R ? R extends InvalidationResources ? R
506
+ : never
507
+ : never
484
508
  export const makeClient = <RT_, RTHooks>(
485
509
  // global, but only accessible after startup has completed
486
510
  getBaseMrt: () => ManagedRuntime.ManagedRuntime<RT_ | Mix, never>,
@@ -500,6 +524,37 @@ export const makeClient = <RT_, RTHooks>(
500
524
  const useQuery = query.useQuery
501
525
  const useSuspenseQuery = query.useSuspenseQuery
502
526
 
527
+ const mergeInvalidation = (
528
+ a?: MutationOptionsBase["queryInvalidation"],
529
+ b?: MutationOptionsBase["queryInvalidation"]
530
+ ): MutationOptionsBase["queryInvalidation"] | undefined => {
531
+ if (!a && !b) {
532
+ return undefined
533
+ }
534
+ return (defaultKey, name, input, output) => [
535
+ ...(a?.(defaultKey, name, input, output) ?? []),
536
+ ...(b?.(defaultKey, name, input, output) ?? [])
537
+ ]
538
+ }
539
+
540
+ const makeQueryResources = <Resources extends InvalidationResources>(resources: Resources | undefined) => {
541
+ if (!resources) {
542
+ return {} as Record<string, Record<string, unknown>>
543
+ }
544
+
545
+ return Struct.keys(resources).reduce((acc, resourceName) => {
546
+ const resource = resources[resourceName]!
547
+ ;(acc as any)[resourceName] = Struct.keys(resource).reduce((moduleAcc, requestName) => {
548
+ const request = resource[requestName]!
549
+ if (request.type === "query") {
550
+ ;(moduleAcc as any)[requestName] = request
551
+ }
552
+ return moduleAcc
553
+ }, {} as Record<string, unknown>)
554
+ return acc
555
+ }, {} as Record<string, Record<string, unknown>>)
556
+ }
557
+
503
558
  const mapQuery = <M extends RequestsAny>(
504
559
  client: ClientFrom<M>
505
560
  ) => {
@@ -572,17 +627,35 @@ export const makeClient = <RT_, RTHooks>(
572
627
  }
573
628
 
574
629
  const mapMutation = <M extends RequestsAny>(
575
- client: ClientFrom<M>
630
+ client: ClientFrom<M>,
631
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
632
+ invalidationResources?: InvalidationResourcesFor<M>
576
633
  ) => {
577
634
  const Command = useCommand()
578
635
  const mutation = useMutation()
636
+ const invalidation = queryInvalidation?.(client)
637
+ const queryResources = makeQueryResources(invalidationResources)
579
638
  const mutations = Struct.keys(client).reduce(
580
639
  (acc, key) => {
581
640
  if (client[key].Request.type !== "command") {
582
641
  return acc
583
642
  }
643
+ const fromRequestConfig = client[key].Request.config?.invalidatesQueries as
644
+ | InvalidationCallback<InvalidationResourcesFor<M>>
645
+ | undefined
646
+ const fromRequest = fromRequestConfig
647
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
648
+ fromRequestConfig(defaultKey, queryResources as never, input as never, output as never).map((entry) => ({
649
+ filters: entry.filters as InvalidateQueryFilters | undefined,
650
+ options: entry.options as InvalidateOptions | undefined
651
+ })))
652
+ : undefined
653
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
584
654
  const makeProjectedMutation = (handler: any): any => {
585
- const mut: any = mutation(handler)
655
+ const mut: any = mutation(
656
+ handler,
657
+ mergedInvalidation ? { queryInvalidation: mergedInvalidation } : undefined
658
+ )
586
659
  const wrap = Command.wrap({ mutate: Effect.isEffect(mut) ? () => mut : mut, id: client[key].id })
587
660
  return Object.assign(mut, {
588
661
  wrap,
@@ -614,7 +687,8 @@ export const makeClient = <RT_, RTHooks>(
614
687
  // make available .query, .suspense and .mutate for each operation
615
688
  // and a .helpers with all mutations and queries
616
689
  const mapClient = <M extends RequestsAny>(
617
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
690
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
691
+ invalidationResources?: InvalidationResourcesFor<M>
618
692
  ) =>
619
693
  (
620
694
  client: ClientFrom<M>
@@ -622,6 +696,7 @@ export const makeClient = <RT_, RTHooks>(
622
696
  const Command = useCommand()
623
697
  const mutation = useMutation()
624
698
  const invalidation = queryInvalidation?.(client)
699
+ const queryResources = makeQueryResources(invalidationResources)
625
700
  const extended = Struct.keys(client).reduce(
626
701
  (acc, key) => {
627
702
  const requestType = client[key].Request.type
@@ -658,10 +733,23 @@ export const makeClient = <RT_, RTHooks>(
658
733
  }
659
734
  : {
660
735
  mutate: ((handler: any) => {
736
+ const fromRequestConfig = client[key].Request.config?.invalidatesQueries as
737
+ | InvalidationCallback<InvalidationResourcesFor<M>>
738
+ | undefined
739
+ const fromRequest = fromRequestConfig
740
+ ? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
741
+ fromRequestConfig(defaultKey, queryResources as never, input as never, output as never).map((
742
+ entry
743
+ ) => ({
744
+ filters: entry.filters as InvalidateQueryFilters | undefined,
745
+ options: entry.options as InvalidateOptions | undefined
746
+ })))
747
+ : undefined
748
+ const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
661
749
  const makeProjectedMutation = (h: any): any => {
662
750
  const mutate = mutation(
663
751
  h,
664
- invalidation?.[key] ? { queryInvalidation: invalidation[key] } : undefined
752
+ mergedInvalidation ? { queryInvalidation: mergedInvalidation } : undefined
665
753
  ) as any
666
754
  return Object.assign(
667
755
  mutate,
@@ -706,26 +794,34 @@ export const makeClient = <RT_, RTHooks>(
706
794
  & { Input: typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I : never }
707
795
  }
708
796
  )
709
- return Object.assign(extended, { helpers: { ...mapRequest(client), ...mapMutation(client), ...mapQuery(client) } })
797
+ return Object.assign(extended, {
798
+ helpers: {
799
+ ...mapRequest(client),
800
+ ...mapMutation(client, queryInvalidation, invalidationResources),
801
+ ...mapQuery(client)
802
+ }
803
+ })
710
804
  }
711
805
 
712
806
  // TODO: Clean up this delay initialisation messs
713
807
  // TODO; invalidateQueries should perhaps be configured in the Request impl themselves?
714
808
  const clientFor__ = <M extends RequestsAny>(
715
809
  m: M,
716
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
717
- ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation))))
810
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
811
+ invalidationResources?: InvalidationResourcesFor<M>
812
+ ) => getBaseMrt().runSync(clientFor_(m).pipe(Effect.map(mapClient(queryInvalidation, invalidationResources))))
718
813
 
719
814
  // delay client creation until first access
720
815
  // the idea is that we don't need the useNuxtApp().$runtime (only available at later initialisation stage)
721
816
  // until we are at a place where it is available..
722
817
  const clientFor = <M extends RequestsAny>(
723
818
  m: M,
724
- queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>
819
+ queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
820
+ invalidationResources?: InvalidationResourcesFor<M>
725
821
  ) => {
726
822
  type Client = ReturnType<typeof clientFor__<M>>
727
823
  let client: Client | undefined = undefined
728
- const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation))
824
+ const getOrMakeClient = () => (client ??= clientFor__(m, queryInvalidation, invalidationResources))
729
825
 
730
826
  // initialize on first use..
731
827
  const proxy = Struct.keys(m).concat(["helpers"]).reduce((acc, key) => {
@@ -762,7 +858,12 @@ export const makeClient = <RT_, RTHooks>(
762
858
  }
763
859
 
764
860
  export type QueryInvalidation<M> = {
765
- [K in keyof M]?: (defaultKey: string[], name: string) => {
861
+ [K in keyof M]?: (
862
+ defaultKey: string[],
863
+ name: string,
864
+ input?: unknown,
865
+ output?: ExitResult.Exit<unknown, unknown>
866
+ ) => {
766
867
  filters?: InvalidateQueryFilters | undefined
767
868
  options?: InvalidateOptions | undefined
768
869
  }[]
package/src/mutate.ts CHANGED
@@ -72,7 +72,7 @@ export interface MutationOptionsBase {
72
72
  * By default we invalidate one level of the query key, e.g $project/$configuration.get, we invalidate $project.
73
73
  * This can be overridden by providing a function that returns an array of filters and options.
74
74
  */
75
- queryInvalidation?: (defaultKey: string[], name: string) => {
75
+ queryInvalidation?: (defaultKey: string[], name: string, input?: unknown, output?: Exit.Exit<unknown, unknown>) => {
76
76
  filters?: InvalidateQueryFilters | undefined
77
77
  options?: InvalidateOptions | undefined
78
78
  }[]
@@ -158,40 +158,49 @@ export const invalidateQueries = (
158
158
  )
159
159
  )
160
160
 
161
- const invalidateCache = Effect.suspend(() => {
162
- const queryKey = getQueryKey(self)
161
+ const invalidateCache = (input: unknown, output: Exit.Exit<unknown, unknown>) =>
162
+ Effect.suspend(() => {
163
+ const queryKey = getQueryKey(self)
163
164
 
164
- if (options) {
165
- const opts = options(queryKey, self.id)
166
- if (!opts.length) {
167
- return Effect.void
165
+ if (options) {
166
+ const opts = options(queryKey, self.id, input, output)
167
+ if (!opts.length) {
168
+ return Effect.void
169
+ }
170
+ return Effect
171
+ .andThen(
172
+ Effect.annotateCurrentSpan({ queryKey, opts }),
173
+ Effect.forEach(opts, (_) => invalidateQueries(_.filters, _.options), { concurrency: "inherit" })
174
+ )
175
+ .pipe(Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false }))
168
176
  }
177
+
178
+ if (!queryKey) return Effect.void
179
+
169
180
  return Effect
170
181
  .andThen(
171
- Effect.annotateCurrentSpan({ queryKey, opts }),
172
- Effect.forEach(opts, (_) => invalidateQueries(_.filters, _.options), { concurrency: "inherit" })
182
+ Effect.annotateCurrentSpan({ queryKey }),
183
+ invalidateQueries({ queryKey })
173
184
  )
174
- .pipe(Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false }))
175
- }
176
-
177
- if (!queryKey) return Effect.void
185
+ .pipe(
186
+ Effect.tap(
187
+ // hand over control back to the event loop so that state can be updated..
188
+ // TODO: should we do this in general on any mutation, regardless of invalidation?
189
+ Effect.sleep(0)
190
+ ),
191
+ Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false })
192
+ )
193
+ })
178
194
 
179
- return Effect
180
- .andThen(
181
- Effect.annotateCurrentSpan({ queryKey }),
182
- invalidateQueries({ queryKey })
183
- )
184
- .pipe(
185
- Effect.tap(
186
- // hand over control back to the event loop so that state can be updated..
187
- // TODO: should we do this in general on any mutation, regardless of invalidation?
188
- Effect.sleep(0)
189
- ),
190
- Effect.withSpan("client.query.invalidation", {}, { captureStackTrace: false })
195
+ const handle = <A, E, R>(self: Effect.Effect<A, E, R>, input?: unknown) =>
196
+ self.pipe(
197
+ Effect.onExit((exit) =>
198
+ invalidateCache(
199
+ input,
200
+ exit
201
+ )
191
202
  )
192
- })
193
-
194
- const handle = <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.ensuring(self, invalidateCache)
203
+ )
195
204
 
196
205
  return handle
197
206
  }
@@ -221,7 +230,7 @@ export const makeMutation = () => {
221
230
  const queryClient = useQueryClient()
222
231
  const handle = invalidateQueries(queryClient, self, options?.queryInvalidation)
223
232
  const handler = self.handler
224
- const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i))
233
+ const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i), i)
225
234
 
226
235
  return Object.assign(r, { id: self.id }) as any
227
236
  }
@@ -255,7 +264,7 @@ export const useMakeMutation = () => {
255
264
  ) => {
256
265
  const handle = invalidateQueries(queryClient, self, options?.queryInvalidation)
257
266
  const handler = self.handler
258
- const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i))
267
+ const r = Effect.isEffect(handler) ? handle(handler) : (i: I) => handle(handler(i), i)
259
268
 
260
269
  return Object.assign(r, { id: self.id }) as any
261
270
  }