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,276 @@
1
+ import { parseJS, fs, path } from 'houdini'
2
+ import { testConfig } from 'houdini/test'
3
+ import { test, expect } from 'vitest'
4
+
5
+ import generate from '..'
6
+ import { type_route_dir } from '../../kit'
7
+
8
+ const config = testConfig()
9
+ const plugin_root = config.pluginDirectory('test-plugin')
10
+
11
+ test('generates types for inline queries', async function () {
12
+ // create the mock filesystem
13
+ await fs.mock({
14
+ [config.routesDir]: {
15
+ myProfile: {
16
+ '+page.svelte': `
17
+ <script>
18
+ import { query, graphql } from '$houdini'
19
+
20
+ const result = query(graphql\`query MyInlineQuery { viewer { id } } \`)
21
+ </script>
22
+ `,
23
+ },
24
+ },
25
+ })
26
+
27
+ // execute the generator
28
+ await generate({ config, documents: [], framework: 'kit', plugin_root })
29
+
30
+ // load the contents of the file
31
+ const queryContents = await fs.readFile(
32
+ path.join(path.join(type_route_dir(config), 'myProfile', '$houdini.d.ts'))
33
+ )
34
+ expect(queryContents).toBeTruthy()
35
+
36
+ const parsedQuery = (await parseJS(queryContents!))?.script
37
+ // verify contents
38
+ expect(parsedQuery).toMatchInlineSnapshot(`
39
+ import type * as Kit from "@sveltejs/kit";
40
+ import type { VariableFunction, AfterLoadFunction, BeforeLoadFunction } from "../../../../plugins/houdini-svelte/runtime/types";
41
+ import type { PageLoadEvent, PageData as KitPageData } from "./$types";
42
+ import { MyInlineQuery$result, MyInlineQuery$input } from "../../../../artifacts/MyInlineQuery";
43
+ import { MyInlineQueryStore } from "../../../../stores/MyInlineQuery";
44
+ type PageParams = PageLoadEvent["params"];
45
+
46
+ export type PageData = {
47
+ MyInlineQuery: MyInlineQueryStore
48
+ };
49
+ `)
50
+ })
51
+
52
+ test('generates types for page queries', async function () {
53
+ // create the mock filesystem
54
+ await fs.mock({
55
+ [config.routesDir]: {
56
+ myProfile: {
57
+ '+page.gql': `
58
+ query MyPageQuery {
59
+ viewer {
60
+ id
61
+ }
62
+ }
63
+ `,
64
+ },
65
+ },
66
+ })
67
+
68
+ // execute the generator
69
+ await generate({ config, documents: [], framework: 'kit', plugin_root })
70
+
71
+ // load the contents of the file
72
+ const queryContents = await fs.readFile(
73
+ path.join(path.join(type_route_dir(config), 'myProfile', '$houdini.d.ts'))
74
+ )
75
+ expect(queryContents).toBeTruthy()
76
+
77
+ const parsedQuery = (await parseJS(queryContents!))?.script
78
+ // verify contents
79
+ expect(parsedQuery).toMatchInlineSnapshot(`
80
+ import type * as Kit from "@sveltejs/kit";
81
+ import type { VariableFunction, AfterLoadFunction, BeforeLoadFunction } from "../../../../plugins/houdini-svelte/runtime/types";
82
+ import type { PageLoadEvent, PageData as KitPageData } from "./$types";
83
+ import { MyPageQuery$result, MyPageQuery$input } from "../../../../artifacts/MyPageQuery";
84
+ import { MyPageQueryStore } from "../../../../stores/MyPageQuery";
85
+ type PageParams = PageLoadEvent["params"];
86
+
87
+ export type PageData = {
88
+ MyPageQuery: MyPageQueryStore
89
+ };
90
+ `)
91
+ })
92
+
93
+ test('generates types for layout queries', async function () {
94
+ // create the mock filesystem
95
+ await fs.mock({
96
+ [config.routesDir]: {
97
+ myProfile: {
98
+ '+layout.gql': `
99
+ query MyLayoutQuery {
100
+ viewer {
101
+ id
102
+ }
103
+ }
104
+ `,
105
+ },
106
+ },
107
+ })
108
+
109
+ // execute the generator
110
+ await generate({ config, documents: [], framework: 'kit', plugin_root })
111
+
112
+ // load the contents of the file
113
+ const queryContents = await fs.readFile(
114
+ path.join(path.join(type_route_dir(config), 'myProfile', '$houdini.d.ts'))
115
+ )
116
+ expect(queryContents).toBeTruthy()
117
+
118
+ const parsedQuery = (await parseJS(queryContents!))?.script
119
+ // verify contents
120
+ expect(parsedQuery).toMatchInlineSnapshot(`
121
+ import type * as Kit from "@sveltejs/kit";
122
+ import type { VariableFunction, AfterLoadFunction, BeforeLoadFunction } from "../../../../plugins/houdini-svelte/runtime/types";
123
+ import type { LayoutLoadEvent, LayoutData as KitPageData } from "./$types";
124
+ import { MyLayoutQuery$result, MyLayoutQuery$input } from "../../../../artifacts/MyLayoutQuery";
125
+ import { MyLayoutQueryStore } from "../../../../stores/MyLayoutQuery";
126
+ type LayoutParams = LayoutLoadEvent["params"];
127
+
128
+ export type LayoutData = {
129
+ MyLayoutQuery: MyLayoutQueryStore
130
+ };
131
+ `)
132
+ })
133
+
134
+ test('generates types for after load', async function () {
135
+ // create the mock filesystem
136
+ await fs.mock({
137
+ [config.routesDir]: {
138
+ myProfile: {
139
+ '+page.js': `
140
+ import { graphql } from '$houdini'
141
+
142
+ const store1 = graphql\`query MyPageLoad1Query($id: ID!) {
143
+ viewer(id: $id) {
144
+ id
145
+ }
146
+ }\`
147
+
148
+ const store2 = graphql\`query MyPageLoad2Query {
149
+ viewer {
150
+ id
151
+ }
152
+ }\`
153
+
154
+ export const houdini_load = [ store1, store2 ]
155
+
156
+ export function afterLoad() {
157
+ return {
158
+ hello: 'world'
159
+ }
160
+ }
161
+ `,
162
+ },
163
+ },
164
+ })
165
+
166
+ // execute the generator
167
+ await generate({ config, documents: [], framework: 'kit', plugin_root })
168
+
169
+ // load the contents of the file
170
+ const queryContents = await fs.readFile(
171
+ path.join(type_route_dir(config), 'myProfile', '$houdini.d.ts')
172
+ )
173
+ expect(queryContents).toBeTruthy()
174
+
175
+ // verify contents
176
+ expect((await parseJS(queryContents!))?.script).toMatchInlineSnapshot(`
177
+ import type * as Kit from "@sveltejs/kit";
178
+ import type { VariableFunction, AfterLoadFunction, BeforeLoadFunction } from "../../../../plugins/houdini-svelte/runtime/types";
179
+ import type { PageLoadEvent, PageData as KitPageData } from "./$types";
180
+ import { MyPageLoad1Query$result, MyPageLoad1Query$input } from "../../../../artifacts/MyPageLoad1Query";
181
+ import { MyPageLoad1QueryStore } from "../../../../stores/MyPageLoad1Query";
182
+ import { MyPageLoad2Query$result, MyPageLoad2Query$input } from "../../../../artifacts/MyPageLoad2Query";
183
+ import { MyPageLoad2QueryStore } from "../../../../stores/MyPageLoad2Query";
184
+ type PageParams = PageLoadEvent["params"];
185
+ export type MyPageLoad1QueryVariables = VariableFunction<PageParams, MyPageLoad1Query$input>;
186
+ type AfterLoadReturn = ReturnType<typeof import("./+page").afterLoad>;
187
+
188
+ type AfterLoadData = {
189
+ MyPageLoad1Query: MyPageLoad1Query$result
190
+ MyPageLoad2Query: MyPageLoad2Query$result
191
+ };
192
+
193
+ type LoadInput = {
194
+ MyPageLoad1Query: MyPageLoad1Query$input
195
+ };
196
+
197
+ export type AfterLoadEvent = {
198
+ event: PageLoadEvent
199
+ data: AfterLoadData
200
+ input: LoadInput
201
+ };
202
+
203
+ export type PageData = {
204
+ MyPageLoad1Query: MyPageLoad1QueryStore
205
+ MyPageLoad2Query: MyPageLoad2QueryStore
206
+ } & AfterLoadReturn;
207
+ `)
208
+ })
209
+
210
+ test('generates types for onError', async function () {
211
+ // create the mock filesystem
212
+ await fs.mock({
213
+ [config.routesDir]: {
214
+ myProfile: {
215
+ '+page.js': `
216
+ import { graphql } from '$houdini'
217
+
218
+ const store1 = graphql\`query MyPageLoad1Query($id: ID!) {
219
+ viewer(id: $id) {
220
+ id
221
+ }
222
+ }\`
223
+
224
+ const store2 = graphql\`query MyPageLoad2Query {
225
+ viewer {
226
+ id
227
+ }
228
+ }\`
229
+
230
+ export const houdini_load = [ store1, store2 ]
231
+
232
+ export function onError() {
233
+ return {
234
+ hello: 'world'
235
+ }
236
+ }
237
+ `,
238
+ },
239
+ },
240
+ })
241
+
242
+ // execute the generator
243
+ await generate({ config, documents: [], framework: 'kit', plugin_root })
244
+
245
+ // load the contents of the file
246
+ const queryContents = await fs.readFile(
247
+ path.join(type_route_dir(config), 'myProfile', '$houdini.d.ts')
248
+ )
249
+ expect(queryContents).toBeTruthy()
250
+
251
+ // verify contents
252
+ expect((await parseJS(queryContents!))?.script).toMatchInlineSnapshot(`
253
+ import type * as Kit from "@sveltejs/kit";
254
+ import type { VariableFunction, AfterLoadFunction, BeforeLoadFunction } from "../../../../plugins/houdini-svelte/runtime/types";
255
+ import type { PageLoadEvent, PageData as KitPageData } from "./$types";
256
+ import { MyPageLoad1Query$result, MyPageLoad1Query$input } from "../../../../artifacts/MyPageLoad1Query";
257
+ import { MyPageLoad1QueryStore } from "../../../../stores/MyPageLoad1Query";
258
+ import { MyPageLoad2Query$result, MyPageLoad2Query$input } from "../../../../artifacts/MyPageLoad2Query";
259
+ import { MyPageLoad2QueryStore } from "../../../../stores/MyPageLoad2Query";
260
+ type PageParams = PageLoadEvent["params"];
261
+ export type MyPageLoad1QueryVariables = VariableFunction<PageParams, MyPageLoad1Query$input>;
262
+
263
+ export type OnErrorEvent = {
264
+ event: LoadEvent
265
+ input: LoadInput
266
+ error: Error | Error[]
267
+ };
268
+
269
+ type OnErrorReturn = ReturnType<typeof import("./+page").onError>;
270
+
271
+ export type PageData = {
272
+ MyPageLoad1Query: MyPageLoad1QueryStore
273
+ MyPageLoad2Query: MyPageLoad2QueryStore
274
+ } & OnErrorReturn;
275
+ `)
276
+ })
@@ -0,0 +1,83 @@
1
+ import { CollectedGraphQLDocument, fs, GenerateHookInput, path } from 'houdini'
2
+
3
+ import { global_store_name, stores_directory, store_name } from '../../kit'
4
+
5
+ export async function generateFragmentStore(
6
+ { config, plugin_root }: GenerateHookInput,
7
+ doc: CollectedGraphQLDocument
8
+ ) {
9
+ const fileName = doc.name
10
+ const artifactName = `${doc.name}`
11
+ const storeName = store_name({ config, name: doc.name })
12
+ const globalStoreName = global_store_name({ config, name: doc.name })
13
+
14
+ const paginationMethod = doc.refetch?.method
15
+
16
+ // in order to build the store, we need to know what class we're going to import from
17
+ let queryClass = 'FragmentStore'
18
+ if (paginationMethod === 'cursor') {
19
+ queryClass =
20
+ doc.refetch?.direction === 'forward'
21
+ ? 'FragmentStoreForwardCursor'
22
+ : 'FragmentStoreBackwardCursor'
23
+ } else if (paginationMethod === 'offset') {
24
+ queryClass = 'FragmentStoreOffset'
25
+ }
26
+
27
+ // store definition
28
+ const storeContent = `import { ${queryClass} } from '../runtime/stores'
29
+ import artifact from '$houdini/artifacts/${artifactName}'
30
+ ${
31
+ paginationMethod
32
+ ? `import _PaginationArtifact from '${config.artifactImportPath(
33
+ config.paginationQueryName(doc.name)
34
+ )}'`
35
+ : ''
36
+ }
37
+
38
+ // create the query store
39
+
40
+ export class ${storeName} extends ${queryClass} {
41
+ constructor() {
42
+ super({
43
+ artifact,
44
+ storeName: ${JSON.stringify(storeName)},
45
+ variables: ${JSON.stringify(true)},
46
+ ${paginationMethod ? 'paginationArtifact: _PaginationArtifact,' : ''}
47
+ })
48
+ }
49
+ }
50
+
51
+ export const ${globalStoreName} = new ${storeName}()
52
+
53
+ export default ${globalStoreName}
54
+ `
55
+
56
+ const _data = `${artifactName}$data`
57
+
58
+ // the type definitions for the store
59
+ const typeDefs = `import type { ${_data}, ${queryClass}, QueryStoreFetchParams} from '$houdini'
60
+
61
+ export declare class ${storeName} extends ${queryClass}<${_data}, {}> {
62
+ constructor() {
63
+ // @ts-ignore
64
+ super({})
65
+ }
66
+ }
67
+
68
+ export const ${globalStoreName}: ${storeName}
69
+
70
+ export declare const load_${artifactName}: (params: QueryStoreFetchParams<${_data}, {}>) => Promise<${storeName}>
71
+
72
+ export default ${storeName}
73
+ `
74
+
75
+ // write the store contents to disk
76
+ await Promise.all([
77
+ fs.writeFile(path.join(stores_directory(plugin_root), `${fileName}.d.ts`), typeDefs),
78
+ fs.writeFile(path.join(stores_directory(plugin_root), `${fileName}.js`), storeContent),
79
+ ])
80
+
81
+ // return the store name to the generator so the index file can be created
82
+ return fileName
83
+ }
@@ -0,0 +1,55 @@
1
+ import { cleanupFiles, fs, ArtifactKind, GenerateHookInput, path } from 'houdini'
2
+
3
+ import { stores_directory } from '../../kit'
4
+ import { generateFragmentStore } from './fragment'
5
+ import { generateIndividualStoreMutation } from './mutation'
6
+ import { generateIndividualStoreQuery } from './query'
7
+ import { generateSubscriptionStore } from './subscription'
8
+
9
+ export default async function storesGenerator(input: GenerateHookInput) {
10
+ const { config, documents } = input
11
+
12
+ const listOfStores: (string | null)[] = []
13
+
14
+ await Promise.all(
15
+ documents.map(async (doc) => {
16
+ // if the doc is not meant to be generated, skip it
17
+ if (!doc.generateStore) {
18
+ return
19
+ }
20
+
21
+ if (doc.kind === ArtifactKind.Query) {
22
+ listOfStores.push(await generateIndividualStoreQuery(input, doc))
23
+ } else if (doc.kind === ArtifactKind.Mutation) {
24
+ listOfStores.push(await generateIndividualStoreMutation(input, doc))
25
+ } else if (doc.kind === ArtifactKind.Subscription) {
26
+ listOfStores.push(await generateSubscriptionStore(input, doc))
27
+ } else if (doc.kind === ArtifactKind.Fragment) {
28
+ listOfStores.push(await generateFragmentStore(input, doc))
29
+ }
30
+ })
31
+ )
32
+
33
+ const listOfStoresOrdered = listOfStores
34
+ .filter((c) => c !== null)
35
+ .sort((a, b) => (a + '').localeCompare(b + '')) as string[]
36
+ const dataIndex = listOfStoresOrdered.map((c) => `export * from './${c}'`).join(`\n`)
37
+ await fs.writeFile(path.join(stores_directory(input.plugin_root), `index.js`), dataIndex)
38
+
39
+ const dataIndexDTs = `import type { DataSource } from '$houdini/runtime'
40
+
41
+ export type Result<DataType> = {
42
+ isFetching: boolean
43
+ partial: boolean
44
+ source?: DataSource | null
45
+ data?: DataType | null
46
+ error?: Error | null
47
+ }`
48
+
49
+ const storePath = stores_directory(input.plugin_root)
50
+
51
+ await fs.writeFile(path.join(storePath, `index.d.ts`), dataIndexDTs + `\n` + dataIndex)
52
+
53
+ // cleanup files that are no more necessary!
54
+ await cleanupFiles(storePath, listOfStoresOrdered)
55
+ }
@@ -0,0 +1,56 @@
1
+ import { CollectedGraphQLDocument, fs, path, GenerateHookInput } from 'houdini'
2
+
3
+ import { global_store_name, stores_directory, store_name } from '../../kit'
4
+
5
+ export async function generateIndividualStoreMutation(
6
+ { config, plugin_root }: GenerateHookInput,
7
+ doc: CollectedGraphQLDocument
8
+ ) {
9
+ const fileName = doc.name
10
+ const storeName = store_name({ config, name: doc.name })
11
+ const globalStoreName = global_store_name({ config, name: doc.name })
12
+ const artifactName = `${doc.name}`
13
+
14
+ // store content
15
+ const storeData = `import artifact from '$houdini/artifacts/${artifactName}'
16
+ import { MutationStore } from '../runtime/stores'
17
+
18
+ export class ${storeName} extends MutationStore {
19
+ constructor() {
20
+ super({
21
+ artifact,
22
+ })
23
+ }
24
+ }
25
+
26
+ export const ${globalStoreName} = new ${storeName}()
27
+
28
+ export default ${globalStoreName}
29
+ `
30
+
31
+ const _input = `${artifactName}$input`
32
+ const _data = `${artifactName}$result`
33
+ const _optimistic = `${artifactName}$optimistic`
34
+
35
+ // the type definitions for the store
36
+ const typeDefs = `import type { ${_input}, ${_data}, ${_optimistic}, MutationStore } from '$houdini'
37
+
38
+ export declare class ${storeName} extends MutationStore<${_data} | undefined, ${_input}, ${_optimistic}>{
39
+ constructor() {
40
+ // @ts-ignore
41
+ super({})
42
+ }
43
+ }
44
+
45
+ export const ${globalStoreName}: ${storeName}
46
+
47
+ export default ${storeName}
48
+ `
49
+
50
+ await Promise.all([
51
+ fs.writeFile(path.join(stores_directory(plugin_root), `${fileName}.js`), storeData),
52
+ fs.writeFile(path.join(stores_directory(plugin_root), `${fileName}.d.ts`), typeDefs),
53
+ ])
54
+
55
+ return fileName
56
+ }