houdini-svelte 0.17.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/package.json +57 -0
  4. package/src/plugin/codegen/adapter.ts +45 -0
  5. package/src/plugin/codegen/components/index.ts +149 -0
  6. package/src/plugin/codegen/index.ts +28 -0
  7. package/src/plugin/codegen/routes/index.ts +307 -0
  8. package/src/plugin/codegen/routes/kit.test.ts +276 -0
  9. package/src/plugin/codegen/stores/fragment.ts +83 -0
  10. package/src/plugin/codegen/stores/index.ts +55 -0
  11. package/src/plugin/codegen/stores/mutation.ts +56 -0
  12. package/src/plugin/codegen/stores/query.test.ts +504 -0
  13. package/src/plugin/codegen/stores/query.ts +97 -0
  14. package/src/plugin/codegen/stores/subscription.ts +57 -0
  15. package/src/plugin/extract.test.ts +290 -0
  16. package/src/plugin/extract.ts +127 -0
  17. package/src/plugin/extractLoadFunction.test.ts +247 -0
  18. package/src/plugin/extractLoadFunction.ts +249 -0
  19. package/src/plugin/fsPatch.ts +238 -0
  20. package/src/plugin/imports.ts +28 -0
  21. package/src/plugin/index.ts +165 -0
  22. package/src/plugin/kit.ts +382 -0
  23. package/src/plugin/transforms/index.ts +90 -0
  24. package/src/plugin/transforms/kit/index.ts +20 -0
  25. package/src/plugin/transforms/kit/init.test.ts +28 -0
  26. package/src/plugin/transforms/kit/init.ts +75 -0
  27. package/src/plugin/transforms/kit/load.test.ts +1234 -0
  28. package/src/plugin/transforms/kit/load.ts +506 -0
  29. package/src/plugin/transforms/kit/session.test.ts +268 -0
  30. package/src/plugin/transforms/kit/session.ts +161 -0
  31. package/src/plugin/transforms/query.test.ts +99 -0
  32. package/src/plugin/transforms/query.ts +263 -0
  33. package/src/plugin/transforms/reactive.ts +126 -0
  34. package/src/plugin/transforms/tags.ts +20 -0
  35. package/src/plugin/transforms/types.ts +9 -0
  36. package/src/plugin/validate.test.ts +95 -0
  37. package/src/plugin/validate.ts +50 -0
  38. package/src/preprocess/index.ts +33 -0
  39. package/src/runtime/adapter.ts +21 -0
  40. package/src/runtime/fragments.ts +86 -0
  41. package/src/runtime/index.ts +72 -0
  42. package/src/runtime/network.ts +6 -0
  43. package/src/runtime/session.ts +187 -0
  44. package/src/runtime/stores/fragment.ts +48 -0
  45. package/src/runtime/stores/index.ts +5 -0
  46. package/src/runtime/stores/mutation.ts +185 -0
  47. package/src/runtime/stores/pagination/cursor.ts +265 -0
  48. package/src/runtime/stores/pagination/fetch.ts +7 -0
  49. package/src/runtime/stores/pagination/fragment.ts +236 -0
  50. package/src/runtime/stores/pagination/index.ts +7 -0
  51. package/src/runtime/stores/pagination/offset.ts +162 -0
  52. package/src/runtime/stores/pagination/pageInfo.test.ts +39 -0
  53. package/src/runtime/stores/pagination/pageInfo.ts +67 -0
  54. package/src/runtime/stores/pagination/query.ts +132 -0
  55. package/src/runtime/stores/query.ts +524 -0
  56. package/src/runtime/stores/store.ts +13 -0
  57. package/src/runtime/stores/subscription.ts +107 -0
  58. package/src/runtime/types.ts +40 -0
  59. package/src/test/index.ts +208 -0
@@ -0,0 +1,265 @@
1
+ import { getCache } from '$houdini/runtime'
2
+ import { ConfigFile } from '$houdini/runtime/lib/config'
3
+ import { siteURL } from '$houdini/runtime/lib/constants'
4
+ import { deepEquals } from '$houdini/runtime/lib/deepEquals'
5
+ import { executeQuery } from '$houdini/runtime/lib/network'
6
+ import { GraphQLObject, QueryArtifact, QueryResult } from '$houdini/runtime/lib/types'
7
+ import { Writable, writable } from 'svelte/store'
8
+
9
+ import { getCurrentClient } from '../../network'
10
+ import { getSession } from '../../session'
11
+ import { QueryStoreFetchParams } from '../query'
12
+ import { fetchParams } from '../query'
13
+ import { FetchFn } from './fetch'
14
+ import { countPage, extractPageInfo, missingPageSizeError, PageInfo } from './pageInfo'
15
+
16
+ export function cursorHandlers<_Data extends GraphQLObject, _Input>({
17
+ artifact,
18
+ queryVariables: extraVariables,
19
+ setFetching,
20
+ fetch,
21
+ storeName,
22
+ getValue,
23
+ getConfig,
24
+ }: {
25
+ artifact: QueryArtifact
26
+ getValue: () => _Data | null
27
+ queryVariables: () => Promise<_Input | null>
28
+ setFetching: (val: boolean) => void
29
+ fetch: FetchFn<_Data, _Input>
30
+ storeName: string
31
+ getConfig: () => Promise<ConfigFile>
32
+ }): CursorHandlers<_Data, _Input> {
33
+ const pageInfo = writable<PageInfo>(extractPageInfo(getValue(), artifact.refetch!.path))
34
+
35
+ // dry up the page-loading logic
36
+ const loadPage = async ({
37
+ pageSizeVar,
38
+ input,
39
+ functionName,
40
+ metadata = {},
41
+ fetch,
42
+ }: {
43
+ pageSizeVar: string
44
+ functionName: string
45
+ input: {}
46
+ metadata?: {}
47
+ fetch?: typeof globalThis.fetch
48
+ }) => {
49
+ const config = await getConfig()
50
+ const client = await getCurrentClient()
51
+
52
+ // set the loading state to true
53
+ setFetching(true)
54
+
55
+ // build up the variables to pass to the query
56
+ const loadVariables: Record<string, any> = {
57
+ ...(await extraVariables?.()),
58
+ ...input,
59
+ }
60
+
61
+ // if we don't have a value for the page size, tell the user
62
+ if (!loadVariables[pageSizeVar] && !artifact.refetch!.pageSize) {
63
+ throw missingPageSizeError(functionName)
64
+ }
65
+
66
+ // send the query
67
+ const { result } = await executeQuery<GraphQLObject, {}>({
68
+ client,
69
+ artifact,
70
+ variables: loadVariables,
71
+ session: await getSession(),
72
+ cached: false,
73
+ config,
74
+ fetch,
75
+ metadata,
76
+ })
77
+
78
+ // if the query is embedded in a node field (paginated fragments)
79
+ // make sure we look down one more for the updated page info
80
+ const resultPath = [...artifact.refetch!.path]
81
+ if (artifact.refetch!.embedded) {
82
+ const { targetType } = artifact.refetch!
83
+ // make sure we have a type config for the pagination target type
84
+ if (!config.types?.[targetType]?.resolve) {
85
+ throw new Error(
86
+ `Missing type resolve configuration for ${targetType}. For more information, see ${siteURL}/guides/pagination#paginated-fragments`
87
+ )
88
+ }
89
+
90
+ // make sure that we pull the value out of the correct query field
91
+ resultPath.unshift(config.types[targetType].resolve!.queryField)
92
+ }
93
+
94
+ // we need to find the connection object holding the current page info
95
+ pageInfo.set(extractPageInfo(result.data, resultPath))
96
+
97
+ // update cache with the result
98
+ getCache().write({
99
+ selection: artifact.selection,
100
+ data: result.data,
101
+ variables: loadVariables,
102
+ applyUpdates: true,
103
+ })
104
+
105
+ // we're not loading any more
106
+ setFetching(false)
107
+ }
108
+
109
+ return {
110
+ loadNextPage: async ({
111
+ first,
112
+ after,
113
+ fetch,
114
+ metadata,
115
+ }: {
116
+ first?: number
117
+ after?: string
118
+ fetch?: typeof globalThis.fetch
119
+ metadata?: {}
120
+ } = {}) => {
121
+ // we need to find the connection object holding the current page info
122
+ const currentPageInfo = extractPageInfo(getValue(), artifact.refetch!.path)
123
+
124
+ // if there is no next page, we're done
125
+ if (!currentPageInfo.hasNextPage) {
126
+ return
127
+ }
128
+
129
+ // only specify the page count if we're given one
130
+ const input: Record<string, any> = {
131
+ after: after ?? currentPageInfo.endCursor,
132
+ }
133
+ if (first) {
134
+ input.first = first
135
+ }
136
+
137
+ // load the page
138
+ return await loadPage({
139
+ pageSizeVar: 'first',
140
+ functionName: 'loadNextPage',
141
+ input,
142
+ fetch,
143
+ metadata,
144
+ })
145
+ },
146
+ loadPreviousPage: async ({
147
+ last,
148
+ before,
149
+ fetch,
150
+ metadata,
151
+ }: {
152
+ last?: number
153
+ before?: string
154
+ fetch?: typeof globalThis.fetch
155
+ metadata?: {}
156
+ } = {}) => {
157
+ // we need to find the connection object holding the current page info
158
+ const currentPageInfo = extractPageInfo(getValue(), artifact.refetch!.path)
159
+
160
+ // if there is no next page, we're done
161
+ if (!currentPageInfo.hasPreviousPage) {
162
+ return
163
+ }
164
+
165
+ // only specify the page count if we're given one
166
+ const input: Record<string, any> = {
167
+ before: before ?? currentPageInfo.startCursor,
168
+ }
169
+ if (last) {
170
+ input.last = last
171
+ }
172
+
173
+ // load the page
174
+ return await loadPage({
175
+ pageSizeVar: 'last',
176
+ functionName: 'loadPreviousPage',
177
+ input,
178
+ fetch,
179
+ metadata,
180
+ })
181
+ },
182
+ pageInfo,
183
+ async fetch(
184
+ args?: QueryStoreFetchParams<_Data, _Input>
185
+ ): Promise<QueryResult<_Data, _Input>> {
186
+ // validate and prepare the request context for the current environment (client vs server)
187
+ const { params } = await fetchParams(artifact, storeName, args)
188
+
189
+ const { variables } = params ?? {}
190
+
191
+ // build up the variables to pass to the query
192
+ const extra = await extraVariables()
193
+ const queryVariables: Record<string, any> = {
194
+ ...extra,
195
+ ...variables,
196
+ }
197
+
198
+ // if the input is different than the query variables then we just do everything like normal
199
+ if (variables && !deepEquals(extra, variables)) {
200
+ const result = await fetch({
201
+ ...params,
202
+ then(data) {
203
+ pageInfo.set(extractPageInfo(data, artifact.refetch!.path))
204
+ },
205
+ })
206
+ return result
207
+ }
208
+
209
+ // we are updating the current set of items, count the number of items that currently exist
210
+ // and ask for the full data set
211
+ const count =
212
+ countPage(artifact.refetch!.path.concat('edges'), getValue()) ||
213
+ artifact.refetch!.pageSize
214
+
215
+ // if there are more records than the first page, we need fetch to load everything
216
+ if (count && count > artifact.refetch!.pageSize) {
217
+ // reverse cursors need the last entries in the list
218
+ queryVariables[artifact.refetch!.update === 'prepend' ? 'last' : 'first'] = count
219
+ }
220
+
221
+ // set the loading state to true
222
+ setFetching(true)
223
+
224
+ // send the query
225
+ const result = await fetch({
226
+ ...params,
227
+ variables: queryVariables as _Input,
228
+ })
229
+
230
+ // keep the page info store up to date
231
+ pageInfo.set(extractPageInfo(result.data, artifact.refetch!.path))
232
+
233
+ // we're not loading any more
234
+ setFetching(false)
235
+
236
+ return {
237
+ data: result.data,
238
+ variables: queryVariables as _Input,
239
+ isFetching: false,
240
+ partial: result.partial,
241
+ errors: null,
242
+ source: result.source,
243
+ }
244
+ },
245
+ }
246
+ }
247
+
248
+ export type CursorHandlers<_Data extends GraphQLObject, _Input> = {
249
+ loadNextPage: (args?: {
250
+ first?: number
251
+ after?: string
252
+ fetch?: typeof globalThis.fetch
253
+ metadata: {}
254
+ }) => Promise<void>
255
+ loadPreviousPage: (args?: {
256
+ last?: number
257
+ before?: string
258
+ fetch?: typeof globalThis.fetch
259
+ metadata?: {}
260
+ }) => Promise<void>
261
+ pageInfo: Writable<PageInfo>
262
+ fetch(
263
+ args?: QueryStoreFetchParams<_Data, _Input> | undefined
264
+ ): Promise<QueryResult<_Data, _Input>>
265
+ }
@@ -0,0 +1,7 @@
1
+ import { GraphQLObject, QueryResult } from '$houdini/runtime/lib/types'
2
+
3
+ import { QueryStoreFetchParams } from '../query'
4
+
5
+ export type FetchFn<_Data extends GraphQLObject, _Input = any> = (
6
+ params?: QueryStoreFetchParams<_Data, _Input>
7
+ ) => Promise<QueryResult<_Data, _Input>>
@@ -0,0 +1,236 @@
1
+ import { keyFieldsForType, getCurrentConfig } from '$houdini/runtime/lib/config'
2
+ import { siteURL } from '$houdini/runtime/lib/constants'
3
+ import {
4
+ GraphQLObject,
5
+ FragmentArtifact,
6
+ QueryArtifact,
7
+ HoudiniFetchContext,
8
+ CompiledFragmentKind,
9
+ } from '$houdini/runtime/lib/types'
10
+ import { derived, get, Readable, Subscriber, Writable, writable } from 'svelte/store'
11
+
12
+ import { StoreConfig } from '../query'
13
+ import { BaseStore } from '../store'
14
+ import { cursorHandlers, CursorHandlers } from './cursor'
15
+ import { offsetHandlers } from './offset'
16
+ import { nullPageInfo, PageInfo } from './pageInfo'
17
+
18
+ type FragmentStoreConfig<_Data extends GraphQLObject, _Input> = StoreConfig<
19
+ _Data,
20
+ _Input,
21
+ FragmentArtifact
22
+ > & { paginationArtifact: QueryArtifact }
23
+
24
+ class BasePaginatedFragmentStore<_Data extends GraphQLObject, _Input> extends BaseStore {
25
+ // all paginated stores need to have a flag to distinguish from other fragment stores
26
+ paginated = true
27
+
28
+ protected paginationArtifact: QueryArtifact
29
+ name: string
30
+ kind = CompiledFragmentKind
31
+
32
+ constructor(config: FragmentStoreConfig<_Data, _Input>) {
33
+ super()
34
+ this.paginationArtifact = config.paginationArtifact
35
+ this.name = config.storeName
36
+ }
37
+
38
+ protected async queryVariables(
39
+ store: Readable<FragmentPaginatedResult<_Data, unknown>>
40
+ ): Promise<_Input> {
41
+ const config = await getCurrentConfig()
42
+
43
+ const { targetType } = this.paginationArtifact.refetch || {}
44
+ const typeConfig = config.types?.[targetType || '']
45
+ if (!typeConfig) {
46
+ throw new Error(
47
+ `Missing type refetch configuration for ${targetType}. For more information, see ${siteURL}/guides/pagination#paginated-fragments`
48
+ )
49
+ }
50
+
51
+ // if we have a specific function to use when computing the variables
52
+ // then we need to collect those fields
53
+ let idVariables = {}
54
+ const value = get(store).data
55
+ if (typeConfig.resolve?.arguments) {
56
+ // @ts-ignore
57
+ idVariables = (typeConfig.resolve!.arguments?.(value) || {}) as _Input
58
+ } else {
59
+ const keys = keyFieldsForType(config, targetType || '')
60
+ // @ts-ignore
61
+ idVariables = Object.fromEntries(keys.map((key) => [key, value[key]])) as _Input
62
+ }
63
+
64
+ // add the id variables to the query variables
65
+ return {
66
+ ...idVariables,
67
+ } as _Input
68
+ }
69
+ }
70
+
71
+ // both cursor paginated stores add a page info to their subscribe
72
+ class FragmentStoreCursor<_Data extends GraphQLObject, _Input> extends BasePaginatedFragmentStore<
73
+ _Data,
74
+ _Input
75
+ > {
76
+ // we want to add the cursor-based fetch to the return value of get
77
+ get(initialValue: _Data | null) {
78
+ const store = writable<FragmentPaginatedResult<_Data, { pageInfo: PageInfo }>>({
79
+ data: initialValue,
80
+ isFetching: false,
81
+ pageInfo: nullPageInfo(),
82
+ })
83
+
84
+ // track the loading state
85
+ const loading = writable(false)
86
+
87
+ // generate the pagination handlers
88
+ const handlers = this.storeHandlers(store, loading.set)
89
+
90
+ const subscribe = (
91
+ run: Subscriber<FragmentPaginatedResult<_Data, { pageInfo: PageInfo }>>,
92
+ invalidate?:
93
+ | ((
94
+ value?: FragmentPaginatedResult<_Data, { pageInfo: PageInfo }> | undefined
95
+ ) => void)
96
+ | undefined
97
+ ): (() => void) => {
98
+ const combined = derived(
99
+ [store, handlers.pageInfo],
100
+ ([$parent, $pageInfo]) =>
101
+ ({
102
+ ...$parent,
103
+ pageInfo: $pageInfo,
104
+ } as FragmentPaginatedResult<_Data, { pageInfo: PageInfo }>)
105
+ )
106
+
107
+ return combined.subscribe(run, invalidate)
108
+ }
109
+
110
+ return {
111
+ kind: CompiledFragmentKind,
112
+ data: store,
113
+ subscribe: subscribe,
114
+ loading: loading as Readable<boolean>,
115
+ fetch: handlers.fetch,
116
+ pageInfo: handlers.pageInfo,
117
+ }
118
+ }
119
+
120
+ protected storeHandlers(
121
+ store: Readable<FragmentPaginatedResult<_Data, unknown>>,
122
+ setFetching: (val: boolean) => void
123
+ ): CursorHandlers<_Data, _Input> {
124
+ return cursorHandlers<_Data, _Input>({
125
+ artifact: this.paginationArtifact,
126
+ fetch: async () => {
127
+ return {} as any
128
+ },
129
+ getValue: () => get(store).data,
130
+ queryVariables: () => this.queryVariables(store),
131
+ setFetching,
132
+ storeName: this.name,
133
+ getConfig: () => this.getConfig(),
134
+ })
135
+ }
136
+ }
137
+
138
+ // FragmentStoreForwardCursor adds loadNextPage to FragmentStoreCursor
139
+ export class FragmentStoreForwardCursor<
140
+ _Data extends GraphQLObject,
141
+ _Input
142
+ > extends FragmentStoreCursor<_Data, _Input> {
143
+ get(initialValue: _Data | null) {
144
+ // get the base class
145
+ const parent = super.get(initialValue)
146
+
147
+ // generate the pagination handlers
148
+ const handlers = this.storeHandlers(
149
+ parent,
150
+ // it really is a writable under the hood :(
151
+ (parent.loading as unknown as Writable<boolean>).set
152
+ )
153
+
154
+ return {
155
+ ...parent,
156
+ // add the specific handlers for this situation
157
+ loadNextPage: handlers.loadNextPage,
158
+ }
159
+ }
160
+ }
161
+
162
+ // BackwardFragmentStoreCursor adds loadPreviousPage to FragmentStoreCursor
163
+ export class FragmentStoreBackwardCursor<
164
+ _Data extends GraphQLObject,
165
+ _Input
166
+ > extends FragmentStoreCursor<_Data, _Input> {
167
+ get(initialValue: _Data | null) {
168
+ const parent = super.get(initialValue)
169
+
170
+ // generate the pagination handlers
171
+ const handlers = this.storeHandlers(
172
+ parent,
173
+ // it really is a writable under the hood :(
174
+ (isFetching: boolean) => parent.data.update((p) => ({ ...p, isFetching }))
175
+ )
176
+
177
+ return {
178
+ ...parent,
179
+ // add the specific handlers for this situation
180
+ loadPreviousPage: handlers.loadPreviousPage,
181
+ }
182
+ }
183
+ }
184
+
185
+ export class FragmentStoreOffset<
186
+ _Data extends GraphQLObject,
187
+ _Input
188
+ > extends BasePaginatedFragmentStore<_Data, _Input> {
189
+ get(initialValue: _Data | null) {
190
+ const parent = writable<FragmentPaginatedResult<_Data>>({
191
+ data: initialValue,
192
+ isFetching: false,
193
+ })
194
+
195
+ // create the offset handlers we'll add to the store
196
+ const handlers = offsetHandlers<_Data, _Input>({
197
+ artifact: this.paginationArtifact,
198
+ fetch: async () => ({} as any),
199
+ getValue: () => get(parent).data,
200
+ setFetching: (isFetching: boolean) => parent.update((p) => ({ ...p, isFetching })),
201
+ queryVariables: () => this.queryVariables({ subscribe: parent.subscribe }),
202
+ storeName: this.name,
203
+ getConfig: () => this.getConfig(),
204
+ })
205
+
206
+ // add the offset handlers
207
+ return {
208
+ ...parent,
209
+ kind: CompiledFragmentKind,
210
+ fetch: handlers.fetch,
211
+ loadNextPage: handlers.loadNextPage,
212
+ }
213
+ }
214
+ }
215
+
216
+ export type FragmentStorePaginated<_Data extends GraphQLObject, _Input> = Readable<{
217
+ data: _Data
218
+ isFetching: boolean
219
+ pageInfo: PageInfo
220
+ }> & {
221
+ loadNextPage(
222
+ pageCount?: number,
223
+ after?: string | number,
224
+ houdiniContext?: HoudiniFetchContext
225
+ ): Promise<void>
226
+ loadPreviousPage(
227
+ pageCount?: number,
228
+ before?: string,
229
+ houdiniContext?: HoudiniFetchContext
230
+ ): Promise<void>
231
+ }
232
+
233
+ export type FragmentPaginatedResult<_Data, _ExtraFields = {}> = {
234
+ data: _Data | null
235
+ isFetching: boolean
236
+ } & _ExtraFields
@@ -0,0 +1,7 @@
1
+ export {
2
+ FragmentStoreBackwardCursor,
3
+ FragmentStoreForwardCursor,
4
+ FragmentStoreOffset,
5
+ } from './fragment'
6
+
7
+ export { QueryStoreBackwardCursor, QueryStoreForwardCursor, QueryStoreOffset } from './query'
@@ -0,0 +1,162 @@
1
+ import { getCache } from '$houdini/runtime'
2
+ import { ConfigFile } from '$houdini/runtime/lib/config'
3
+ import { deepEquals } from '$houdini/runtime/lib/deepEquals'
4
+ import { executeQuery } from '$houdini/runtime/lib/network'
5
+ import { GraphQLObject, QueryArtifact, QueryResult } from '$houdini/runtime/lib/types'
6
+
7
+ import { getCurrentClient } from '../../network'
8
+ import { getSession } from '../../session'
9
+ import { QueryStoreFetchParams } from '../query'
10
+ import { fetchParams } from '../query'
11
+ import { FetchFn } from './fetch'
12
+ import { countPage, missingPageSizeError } from './pageInfo'
13
+
14
+ export function offsetHandlers<_Data extends GraphQLObject, _Input>({
15
+ artifact,
16
+ queryVariables: extraVariables,
17
+ fetch,
18
+ getValue,
19
+ setFetching,
20
+ storeName,
21
+ getConfig,
22
+ }: {
23
+ artifact: QueryArtifact
24
+ queryVariables: () => Promise<_Input | null>
25
+ fetch: FetchFn<_Data, _Input>
26
+ getValue: () => _Data | null
27
+ storeName: string
28
+ setFetching: (val: boolean) => void
29
+ getConfig: () => Promise<ConfigFile>
30
+ }) {
31
+ // we need to track the most recent offset for this handler
32
+ let getOffset = () =>
33
+ (artifact.refetch?.start as number) ||
34
+ countPage(artifact.refetch!.path, getValue()) ||
35
+ artifact.refetch!.pageSize
36
+
37
+ let currentOffset = getOffset() ?? 0
38
+
39
+ return {
40
+ loadNextPage: async ({
41
+ limit,
42
+ offset,
43
+ fetch,
44
+ metadata,
45
+ }: {
46
+ limit?: number
47
+ offset?: number
48
+ fetch?: typeof globalThis.fetch
49
+ metadata?: {}
50
+ } = {}) => {
51
+ const config = await getConfig()
52
+
53
+ // if the offset is zero then we want to count it just to make sure
54
+ // hence why (|| and not ??)
55
+ offset ??= currentOffset || getOffset()
56
+
57
+ // build up the variables to pass to the query
58
+ const queryVariables: Record<string, any> = {
59
+ ...(await extraVariables()),
60
+ offset,
61
+ }
62
+ if (limit || limit === 0) {
63
+ queryVariables.limit = limit
64
+ }
65
+
66
+ // if we made it this far without a limit argument and there's no default page size,
67
+ // they made a mistake
68
+ if (!queryVariables.limit && !artifact.refetch!.pageSize) {
69
+ throw missingPageSizeError('loadNextPage')
70
+ }
71
+
72
+ // set the loading state to true
73
+ setFetching(true)
74
+
75
+ // send the query
76
+ const { result } = await executeQuery<GraphQLObject, {}>({
77
+ client: await getCurrentClient(),
78
+ artifact,
79
+ variables: queryVariables,
80
+ session: await getSession(),
81
+ cached: false,
82
+ config,
83
+ fetch,
84
+ metadata,
85
+ })
86
+
87
+ // update cache with the result
88
+ getCache().write({
89
+ selection: artifact.selection,
90
+ data: result.data,
91
+ variables: queryVariables,
92
+ applyUpdates: true,
93
+ })
94
+
95
+ // add the page size to the offset so we load the next page next time
96
+ const pageSize = queryVariables.limit || artifact.refetch!.pageSize
97
+ currentOffset = offset + pageSize
98
+
99
+ // we're not loading any more
100
+ setFetching(false)
101
+ },
102
+ async fetch(
103
+ args?: QueryStoreFetchParams<_Data, _Input>
104
+ ): Promise<QueryResult<_Data, _Input>> {
105
+ const { params } = await fetchParams(artifact, storeName, args)
106
+
107
+ const { variables } = params ?? {}
108
+
109
+ const extra = await extraVariables()
110
+
111
+ // if the input is different than the query variables then we just do everything like normal
112
+ if (variables && !deepEquals(extra, variables)) {
113
+ return fetch.call(this, params)
114
+ }
115
+
116
+ // we are updating the current set of items, count the number of items that currently exist
117
+ // and ask for the full data set
118
+ const count = currentOffset || getOffset()
119
+
120
+ // build up the variables to pass to the query
121
+ const queryVariables: Record<string, any> = {
122
+ ...extra,
123
+ }
124
+
125
+ // if there are more records than the first page, we need fetch to load everything
126
+ if (!artifact.refetch!.pageSize || count > artifact.refetch!.pageSize) {
127
+ queryVariables.limit = count
128
+ }
129
+
130
+ // set the loading state to true
131
+ setFetching(true)
132
+
133
+ // send the query
134
+ const result = await fetch.call(this, {
135
+ ...params,
136
+ variables: queryVariables as _Input,
137
+ })
138
+
139
+ // we're not loading any more
140
+ setFetching(false)
141
+
142
+ return {
143
+ data: result.data,
144
+ variables: queryVariables as _Input,
145
+ isFetching: false,
146
+ partial: result.partial,
147
+ errors: null,
148
+ source: result.source,
149
+ }
150
+ },
151
+ }
152
+ }
153
+
154
+ export type OffsetHandlers<_Data extends GraphQLObject, _Input, _ReturnType> = {
155
+ loadNextPage: (args?: {
156
+ limit?: number
157
+ offset?: number
158
+ metadata?: {}
159
+ fetch?: typeof globalThis.fetch
160
+ }) => Promise<void>
161
+ fetch(args?: QueryStoreFetchParams<_Data, _Input> | undefined): Promise<_ReturnType>
162
+ }