Skip to content

Commit 2b13942

Browse files
author
Jason Kuhrt
authored
feat(context): addToContext can return type refs (#1057)
1 parent c4ecbcb commit 2b13942

File tree

3 files changed

+169
-26
lines changed

3 files changed

+169
-26
lines changed

src/lib/add-to-context-extractor/extractor.spec.ts

Lines changed: 127 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ it('extracts from returned object of primitive values from single call', () => {
3838
Object {
3939
"typeImports": Array [],
4040
"types": Array [
41-
"{ a: number; }",
41+
Object {
42+
"kind": "literal",
43+
"value": "{ a: number; }",
44+
},
4245
],
4346
}
4447
`)
@@ -54,8 +57,14 @@ it('extracts from returned object of primitive values from multiple calls', () =
5457
Object {
5558
"typeImports": Array [],
5659
"types": Array [
57-
"{ a: number; }",
58-
"{ b: number; }",
60+
Object {
61+
"kind": "literal",
62+
"value": "{ a: number; }",
63+
},
64+
Object {
65+
"kind": "literal",
66+
"value": "{ b: number; }",
67+
},
5968
],
6069
}
6170
`)
@@ -71,7 +80,10 @@ it('extracts from returned object of referenced primitive value', () => {
7180
Object {
7281
"typeImports": Array [],
7382
"types": Array [
74-
"{ a: number; }",
83+
Object {
84+
"kind": "literal",
85+
"value": "{ a: number; }",
86+
},
7587
],
7688
}
7789
`)
@@ -88,7 +100,10 @@ it('extracts from returned object of referenced object value', () => {
88100
Object {
89101
"typeImports": Array [],
90102
"types": Array [
91-
"{ foo: { bar: { baz: string; }; }; }",
103+
Object {
104+
"kind": "literal",
105+
"value": "{ foo: { bar: { baz: string; }; }; }",
106+
},
92107
],
93108
}
94109
`)
@@ -109,7 +124,10 @@ it('extracts from returned object of referenced object with inline type', () =>
109124
Object {
110125
"typeImports": Array [],
111126
"types": Array [
112-
"{ foo: { bar: { baz: string; }; }; }",
127+
Object {
128+
"kind": "literal",
129+
"value": "{ foo: { bar: { baz: string; }; }; }",
130+
},
113131
],
114132
}
115133
`)
@@ -139,7 +157,10 @@ it('captures required imports information', () => {
139157
},
140158
],
141159
"types": Array [
142-
"{ foo: Foo; }",
160+
Object {
161+
"kind": "literal",
162+
"value": "{ foo: Foo; }",
163+
},
143164
],
144165
}
145166
`)
@@ -169,7 +190,10 @@ it('detects if a referenced type is not exported', () => {
169190
},
170191
],
171192
"types": Array [
172-
"{ foo: Foo; }",
193+
Object {
194+
"kind": "literal",
195+
"value": "{ foo: Foo; }",
196+
},
173197
],
174198
}
175199
`)
@@ -186,7 +210,10 @@ it('extracts optionality from props', () => {
186210
Object {
187211
"typeImports": Array [],
188212
"types": Array [
189-
"{ foo: { bar?: { baz?: string; }; }; }",
213+
Object {
214+
"kind": "literal",
215+
"value": "{ foo: { bar?: { baz?: string; }; }; }",
216+
},
190217
],
191218
}
192219
`)
@@ -216,7 +243,10 @@ it('extracts from type alias', () => {
216243
},
217244
],
218245
"types": Array [
219-
"{ foo: Foo; }",
246+
Object {
247+
"kind": "literal",
248+
"value": "{ foo: Foo; }",
249+
},
220250
],
221251
}
222252
`)
@@ -246,7 +276,10 @@ it('prop type intersection', () => {
246276
},
247277
],
248278
"types": Array [
249-
"{ foo: Foo & Bar; }",
279+
Object {
280+
"kind": "literal",
281+
"value": "{ foo: Foo & Bar; }",
282+
},
250283
],
251284
}
252285
`)
@@ -271,7 +304,10 @@ it('prop type aliased intersection', () => {
271304
},
272305
],
273306
"types": Array [
274-
"{ foo: Qux; }",
307+
Object {
308+
"kind": "literal",
309+
"value": "{ foo: Qux; }",
310+
},
275311
],
276312
}
277313
`)
@@ -301,7 +337,10 @@ it('prop type union', () => {
301337
},
302338
],
303339
"types": Array [
304-
"{ foo: Foo | Bar; }",
340+
Object {
341+
"kind": "literal",
342+
"value": "{ foo: Foo | Bar; }",
343+
},
305344
],
306345
}
307346
`)
@@ -326,7 +365,10 @@ it('prop type aliased union', () => {
326365
},
327366
],
328367
"types": Array [
329-
"{ foo: Qux; }",
368+
Object {
369+
"kind": "literal",
370+
"value": "{ foo: Qux; }",
371+
},
330372
],
331373
}
332374
`)
@@ -357,6 +399,61 @@ it.todo('truncates import paths when detected to be an external package')
357399
it.todo('props with union types where one union member is a type reference')
358400
it.todo('props with union intersection types where one intersection member is a type reference')
359401

402+
describe('extracted type refs', () => {
403+
it('an alias', () => {
404+
expect(
405+
extract(`
406+
export type A {}
407+
const a: A
408+
schema.addToContext(req => { return null as A })
409+
`)
410+
).toMatchInlineSnapshot(`
411+
Object {
412+
"typeImports": Array [
413+
Object {
414+
"isExported": true,
415+
"isNode": false,
416+
"modulePath": "/src/a",
417+
"name": "A",
418+
},
419+
],
420+
"types": Array [
421+
Object {
422+
"kind": "ref",
423+
"name": "A",
424+
},
425+
],
426+
}
427+
`)
428+
})
429+
it('an interface', () => {
430+
expect(
431+
extract(`
432+
export interface A {}
433+
const a: A
434+
schema.addToContext(req => { return null as A })
435+
`)
436+
).toMatchInlineSnapshot(`
437+
Object {
438+
"typeImports": Array [
439+
Object {
440+
"isExported": true,
441+
"isNode": false,
442+
"modulePath": "/src/a",
443+
"name": "A",
444+
},
445+
],
446+
"types": Array [
447+
Object {
448+
"kind": "ref",
449+
"name": "A",
450+
},
451+
],
452+
}
453+
`)
454+
})
455+
})
456+
360457
it('dedupes imports', () => {
361458
expect(
362459
extract(`
@@ -375,8 +472,14 @@ it('dedupes imports', () => {
375472
},
376473
],
377474
"types": Array [
378-
"{ foo: Qux; bar: Qux; }",
379-
"{ mar: Qux; }",
475+
Object {
476+
"kind": "literal",
477+
"value": "{ foo: Qux; bar: Qux; }",
478+
},
479+
Object {
480+
"kind": "literal",
481+
"value": "{ mar: Qux; }",
482+
},
380483
],
381484
}
382485
`)
@@ -391,7 +494,10 @@ it('does not import array types', () => {
391494
Object {
392495
"typeImports": Array [],
393496
"types": Array [
394-
"{ foo: string[]; }",
497+
Object {
498+
"kind": "literal",
499+
"value": "{ foo: string[]; }",
500+
},
395501
],
396502
}
397503
`)
@@ -428,7 +534,10 @@ it('support async/await', () => {
428534
Object {
429535
"typeImports": Array [],
430536
"types": Array [
431-
"{ foo: string[]; }",
537+
Object {
538+
"kind": "literal",
539+
"value": "{ foo: string[]; }",
540+
},
432541
],
433542
}
434543
`)

src/lib/add-to-context-extractor/extractor.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,23 @@ interface TypeImportInfo {
1212
isNode: boolean
1313
}
1414

15+
type A = { a: 2 }
16+
17+
export type ContribTypeRef = { kind: 'ref'; name: string }
18+
export type ContribTypeLiteral = { kind: 'literal'; value: string }
19+
export type ContribType = ContribTypeRef | ContribTypeLiteral
20+
1521
export interface ExtractedContextTypes {
1622
typeImports: TypeImportInfo[]
17-
// types: Record<string, string>[]
18-
types: string[]
23+
types: ContribType[]
24+
}
25+
26+
function contribTypeRef(name: string): ContribTypeRef {
27+
return { kind: 'ref', name }
28+
}
29+
30+
function contribTypeLiteral(value: string): ContribTypeLiteral {
31+
return { kind: 'literal', value }
1932
}
2033

2134
/**
@@ -67,7 +80,6 @@ export function extractContextTypes(program: ts.Program): ExtractedContextTypes
6780

6881
const expText = exp.getExpression().getText()
6982
const propName = exp.getName()
70-
console.log(expText, propName)
7183

7284
if (expText !== 'schema' || propName !== 'addToContext') {
7385
n.forEachChild(visit)
@@ -115,7 +127,16 @@ export function extractContextTypes(program: ts.Program): ExtractedContextTypes
115127
tsm.ts.TypeFormatFlags.NoTruncation
116128
)
117129

118-
contextTypeContributions.types.push(contextAdderRetTypeString)
130+
if (unwrappedContextAdderRetType.isInterface() || unwrappedContextAdderRetType.getAliasSymbol()) {
131+
const info = extractTypeImportInfoFromType(unwrappedContextAdderRetType)
132+
if (info) {
133+
typeImportsIndex[info.name] = info
134+
}
135+
contextTypeContributions.types.push(contribTypeRef(contextAdderRetTypeString))
136+
return
137+
}
138+
139+
contextTypeContributions.types.push(contribTypeLiteral(contextAdderRetTypeString))
119140

120141
// search for named references, they will require importing later on
121142
const contextAdderRetProps = unwrappedContextAdderRetType.getProperties()

src/lib/add-to-context-extractor/typegen.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { hardWriteFile } from '../fs'
55
import * as Layout from '../layout'
66
import { rootLogger } from '../nexus-logger'
77
import { createTSProgram } from '../tsc'
8-
import { extractContextTypes, ExtractedContextTypes } from './extractor'
8+
import { ContribType, extractContextTypes, ExtractedContextTypes } from './extractor'
99

1010
const log = rootLogger.child('addToContextExtractor')
1111

@@ -39,10 +39,14 @@ export async function generateContextExtractionArtifacts(
3939
* Output the context types to a typegen file.
4040
*/
4141
export async function writeContextTypeGenFile(contextTypes: ExtractedContextTypes) {
42-
const addToContextInterfaces = contextTypes.types
43-
.map((result) => `interface Context ${result}`)
42+
let addToContextInterfaces = contextTypes.types
43+
.map(renderContextInterfaceForExtractedReturnType)
4444
.join('\n\n')
4545

46+
if (addToContextInterfaces.trim() === '') {
47+
addToContextInterfaces = `interface Context {} // none\n\n`
48+
}
49+
4650
const content = codeBlock`
4751
import app from 'nexus'
4852
@@ -60,7 +64,7 @@ export async function writeContextTypeGenFile(contextTypes: ExtractedContextType
6064
6165
// The context types extracted from the app.
6266
63-
${addToContextInterfaces.length > 0 ? addToContextInterfaces : `interface Context {}`}
67+
${addToContextInterfaces}
6468
`
6569

6670
await hardWriteFile(NEXUS_DEFAULT_RUNTIME_CONTEXT_TYPEGEN_PATH, content)
@@ -69,3 +73,12 @@ export async function writeContextTypeGenFile(contextTypes: ExtractedContextType
6973
function renderImport(input: { from: string; names: string[] }) {
7074
return `import { ${input.names.join(', ')} } from '${input.from}'`
7175
}
76+
77+
function renderContextInterfaceForExtractedReturnType(contribType: ContribType): string {
78+
switch (contribType.kind) {
79+
case 'literal':
80+
return `interface Context ${contribType.value}`
81+
case 'ref':
82+
return `interface Context extends ${contribType.name} {}`
83+
}
84+
}

0 commit comments

Comments
 (0)