houdini 0.17.3 → 0.17.5

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 (45) hide show
  1. package/.turbo/turbo-compile.log +2 -2
  2. package/.turbo/turbo-typedefs.log +2 -2
  3. package/CHANGELOG.md +11 -3
  4. package/build/cmd-cjs/index.js +9126 -10254
  5. package/build/cmd-esm/index.js +9126 -10254
  6. package/build/codegen/transforms/paginate.d.ts +10 -11
  7. package/build/codegen-cjs/index.js +9092 -10230
  8. package/build/codegen-esm/index.js +9092 -10230
  9. package/build/lib/config.d.ts +3 -1
  10. package/build/lib/parse.d.ts +1 -0
  11. package/build/lib-cjs/index.js +9236 -10362
  12. package/build/lib-esm/index.js +9235 -10362
  13. package/build/runtime/lib/network.d.ts +1 -0
  14. package/build/runtime/lib/networkUtils.d.ts +8 -0
  15. package/build/runtime-cjs/lib/network.d.ts +1 -0
  16. package/build/runtime-cjs/lib/network.js +33 -1
  17. package/build/runtime-cjs/lib/networkUtils.d.ts +8 -0
  18. package/build/runtime-cjs/lib/networkUtils.js +85 -0
  19. package/build/runtime-esm/lib/network.d.ts +1 -0
  20. package/build/runtime-esm/lib/network.js +33 -1
  21. package/build/runtime-esm/lib/networkUtils.d.ts +8 -0
  22. package/build/runtime-esm/lib/networkUtils.js +60 -0
  23. package/build/test-cjs/index.js +9091 -10228
  24. package/build/test-esm/index.js +9091 -10228
  25. package/build/vite-cjs/index.js +9140 -10273
  26. package/build/vite-esm/index.js +9140 -10273
  27. package/package.json +2 -2
  28. package/src/cmd/generate.ts +6 -15
  29. package/src/cmd/index.ts +9 -1
  30. package/src/cmd/init.ts +4 -4
  31. package/src/cmd/pullSchema.ts +1 -1
  32. package/src/codegen/generators/artifacts/artifacts.test.ts +99 -66
  33. package/src/codegen/generators/artifacts/pagination.test.ts +12 -8
  34. package/src/codegen/generators/artifacts/policy.test.ts +12 -8
  35. package/src/codegen/generators/definitions/schema.test.ts +12 -36
  36. package/src/codegen/generators/persistedQueries/persistedQuery.test.ts +2 -2
  37. package/src/codegen/index.ts +0 -1
  38. package/src/codegen/transforms/fragmentVariables.test.ts +24 -16
  39. package/src/codegen/transforms/paginate.test.ts +9 -6
  40. package/src/codegen/transforms/paginate.ts +2 -2
  41. package/src/lib/config.ts +10 -5
  42. package/src/lib/parse.test.ts +75 -0
  43. package/src/lib/parse.ts +6 -0
  44. package/src/runtime/lib/network.ts +58 -1
  45. package/src/runtime/lib/networkUtils.ts +151 -0
@@ -2,6 +2,7 @@
2
2
  import cache from '../cache'
3
3
  import type { ConfigFile } from './config'
4
4
  import * as log from './log'
5
+ import { extractFiles } from './networkUtils'
5
6
  import {
6
7
  CachePolicy,
7
8
  DataSource,
@@ -22,6 +23,58 @@ export class HoudiniClient {
22
23
  this.socket = subscriptionHandler
23
24
  }
24
25
 
26
+ handleMultipart(
27
+ params: FetchParams,
28
+ args: Parameters<FetchContext['fetch']>
29
+ ): Parameters<FetchContext['fetch']> | undefined {
30
+ const [url, req] = args
31
+
32
+ // process any files that could be included
33
+ const { clone, files } = extractFiles({
34
+ query: params.text,
35
+ variables: params.variables,
36
+ })
37
+
38
+ const operationJSON = JSON.stringify(clone)
39
+
40
+ // if there are files in the request
41
+ if (files.size) {
42
+ let headers: Record<string, string> = {}
43
+
44
+ // filters `content-type: application/json` if received by client.ts
45
+ if (req?.headers) {
46
+ const filtered = Object.entries(req?.headers).filter(([key, value]) => {
47
+ return !(
48
+ key.toLowerCase() == 'content-type' &&
49
+ value.toLowerCase() == 'application/json'
50
+ )
51
+ })
52
+ headers = Object.fromEntries(filtered)
53
+ }
54
+
55
+ // See the GraphQL multipart request spec:
56
+ // https://github.com/jaydenseric/graphql-multipart-request-spec
57
+ const form = new FormData()
58
+
59
+ form.set('operations', operationJSON)
60
+
61
+ const map: Record<string, Array<string>> = {}
62
+
63
+ let i = 0
64
+ files.forEach((paths) => {
65
+ map[++i] = paths
66
+ })
67
+ form.set('map', JSON.stringify(map))
68
+
69
+ i = 0
70
+ files.forEach((paths, file) => {
71
+ form.set(`${++i}`, file as Blob, (file as File).name)
72
+ })
73
+
74
+ return [url, { ...req, headers, body: form as any }]
75
+ }
76
+ }
77
+
25
78
  async sendRequest<_Data>(
26
79
  ctx: FetchContext,
27
80
  params: FetchParams
@@ -33,7 +86,11 @@ export class HoudiniClient {
33
86
  // wrap the user's fetch function so we can identify SSR by checking
34
87
  // the response.url
35
88
  fetch: async (...args: Parameters<FetchContext['fetch']>) => {
36
- const response = await ctx.fetch(...args)
89
+ // figure out if we need to do something special for multipart uploads
90
+ const newArgs = this.handleMultipart(params, args)
91
+
92
+ // use the new args if they exist, otherwise the old ones are good
93
+ const response = await ctx.fetch(...(newArgs || args))
37
94
  if (response.url) {
38
95
  url = response.url
39
96
  }
@@ -0,0 +1,151 @@
1
+ /// This file contains a modified version, made by AlecAivazis, of the functions found here: https://github.com/jaydenseric/extract-files/blob/master/extractFiles.mjs
2
+ /// The associated license is at the end of the file (per the project's license agreement)
3
+
4
+ export function isExtractableFile(value: any): value is ExtractableFile {
5
+ return (
6
+ (typeof File !== 'undefined' && value instanceof File) ||
7
+ (typeof Blob !== 'undefined' && value instanceof Blob)
8
+ )
9
+ }
10
+
11
+ type ExtractableFile = File | Blob
12
+
13
+ /** @typedef {import("./isExtractableFile.mjs").default} isExtractableFile */
14
+
15
+ export function extractFiles(value: any) {
16
+ if (!arguments.length) throw new TypeError('Argument 1 `value` is required.')
17
+
18
+ /**
19
+ * Deeply clonable value.
20
+ * @typedef {Array<unknown> | FileList | Record<PropertyKey, unknown>} Cloneable
21
+ */
22
+
23
+ /**
24
+ * Clone of a {@link Cloneable deeply cloneable value}.
25
+ * @typedef {Exclude<Cloneable, FileList>} Clone
26
+ */
27
+
28
+ /**
29
+ * Map of values recursed within the input value and their clones, for reusing
30
+ * clones of values that are referenced multiple times within the input value.
31
+ * @type {Map<Cloneable, Clone>}
32
+ */
33
+ const clones = new Map()
34
+
35
+ /**
36
+ * Extracted files and their object paths within the input value.
37
+ * @type {Extraction<Extractable>["files"]}
38
+ */
39
+ const files = new Map()
40
+
41
+ /**
42
+ * Recursively clones the value, extracting files.
43
+ */
44
+ function recurse(value: any, path: string | string[], recursed: Set<any>) {
45
+ if (isExtractableFile(value)) {
46
+ const filePaths = files.get(value)
47
+
48
+ filePaths ? filePaths.push(path) : files.set(value, [path])
49
+
50
+ return null
51
+ }
52
+
53
+ const valueIsList =
54
+ Array.isArray(value) || (typeof FileList !== 'undefined' && value instanceof FileList)
55
+ const valueIsPlainObject = isPlainObject(value)
56
+
57
+ if (valueIsList || valueIsPlainObject) {
58
+ let clone = clones.get(value)
59
+
60
+ const uncloned = !clone
61
+
62
+ if (uncloned) {
63
+ clone = valueIsList
64
+ ? []
65
+ : // Replicate if the plain object is an `Object` instance.
66
+ value instanceof /** @type {any} */ Object
67
+ ? {}
68
+ : Object.create(null)
69
+
70
+ clones.set(value, /** @type {Clone} */ clone)
71
+ }
72
+
73
+ if (!recursed.has(value)) {
74
+ const pathPrefix = path ? `${path}.` : ''
75
+ const recursedDeeper = new Set(recursed).add(value)
76
+
77
+ if (valueIsList) {
78
+ let index = 0
79
+
80
+ // @ts-ignore
81
+ for (const item of value) {
82
+ const itemClone = recurse(item, pathPrefix + index++, recursedDeeper)
83
+
84
+ if (uncloned) /** @type {Array<unknown>} */ clone.push(itemClone)
85
+ }
86
+ } else
87
+ for (const key in value) {
88
+ const propertyClone = recurse(value[key], pathPrefix + key, recursedDeeper)
89
+
90
+ if (uncloned)
91
+ /** @type {Record<PropertyKey, unknown>} */ clone[key] = propertyClone
92
+ }
93
+ }
94
+
95
+ return clone
96
+ }
97
+
98
+ return value
99
+ }
100
+
101
+ return {
102
+ clone: recurse(value, '', new Set()),
103
+ files,
104
+ }
105
+ }
106
+
107
+ /**
108
+ * An extraction result.
109
+ * @template [Extractable=unknown] Extractable file type.
110
+ * @typedef {object} Extraction
111
+ * @prop {unknown} clone Clone of the original value with extracted files
112
+ * recursively replaced with `null`.
113
+ * @prop {Map<Extractable, Array<ObjectPath>>} files Extracted files and their
114
+ * object paths within the original value.
115
+ */
116
+
117
+ /**
118
+ * String notation for the path to a node in an object tree.
119
+ * @typedef {string} ObjectPath
120
+ * @see [`object-path` on npm](https://npm.im/object-path).
121
+ * @example
122
+ * An object path for object property `a`, array index `0`, object property `b`:
123
+ *
124
+ * ```
125
+ * a.0.b
126
+ * ```
127
+ */
128
+
129
+ function isPlainObject(value: any) {
130
+ if (typeof value !== 'object' || value === null) {
131
+ return false
132
+ }
133
+
134
+ const prototype = Object.getPrototypeOf(value)
135
+ return (
136
+ (prototype === null ||
137
+ prototype === Object.prototype ||
138
+ Object.getPrototypeOf(prototype) === null) &&
139
+ !(Symbol.toStringTag in value) &&
140
+ !(Symbol.iterator in value)
141
+ )
142
+ }
143
+
144
+ // MIT License
145
+ // Copyright Jayden Seric
146
+
147
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
148
+
149
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
150
+
151
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.