Skip to content

Commit f9a3095

Browse files
authored
feat: add server.custom api (#382)
1 parent 2c08928 commit f9a3095

File tree

6 files changed

+387
-202
lines changed

6 files changed

+387
-202
lines changed

src/framework/app.ts

+27-126
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { typegenAutoConfig } from '@nexus/schema/dist/core'
2-
import { stripIndents } from 'common-tags'
31
import * as HTTP from 'http'
42
import * as Lo from 'lodash'
53
import * as Plugin from '../core/plugin'
@@ -19,7 +17,7 @@ type Request = HTTP.IncomingMessage & { log: Logger.Logger }
1917
// all places in the framework where the req object is referenced should be
2018
// actually referencing the typegen version, so that it reflects the req +
2119
// plugin augmentations type
22-
type ContextContributor<T extends {}> = (req: Request) => T
20+
type ContextContributor<Req, T extends {} = any> = (req: Req) => T
2321

2422
export type App = {
2523
/**
@@ -28,7 +26,13 @@ export type App = {
2826
* ### todo
2927
*/
3028
log: Logger.Logger
31-
server: Server.Server
29+
/**
30+
* [API Reference](https://nexus-future.now.sh/#/references/api?id=server) ⌁ [Guide](todo)
31+
*
32+
* ### todo
33+
*
34+
*/
35+
server: Server.ServerWithCustomizer
3236
/**
3337
* todo
3438
*/
@@ -45,8 +49,8 @@ export type App = {
4549
/**
4650
* todo
4751
*/
48-
addToContext: <T extends {}>(
49-
contextContributor: ContextContributor<T>
52+
addToContext: <Req extends any = Request, T extends {} = any>(
53+
contextContributor: ContextContributor<Req, T>
5054
) => void
5155
}
5256
}
@@ -57,7 +61,7 @@ type SettingsInput = {
5761
server?: Server.ExtraSettingsInput
5862
}
5963

60-
type SettingsData = Readonly<{
64+
export type SettingsData = Readonly<{
6165
logger: Logger.SettingsData
6266
schema: Schema.SettingsData
6367
server: Server.ExtraSettingsData
@@ -66,7 +70,7 @@ type SettingsData = Readonly<{
6670
/**
6771
* todo
6872
*/
69-
type Settings = {
73+
export type Settings = {
7074
/**
7175
* todo
7276
*/
@@ -85,7 +89,7 @@ type Settings = {
8589
* Crate an app instance
8690
* TODO extract and improve config type
8791
*/
88-
export function create(appConfig?: { types?: any }): App {
92+
export function create(): App {
8993
const plugins: Plugin.RuntimeContributions[] = []
9094
// Automatically use all installed plugins
9195
// TODO during build step we should turn this into static imports, not unlike
@@ -94,8 +98,7 @@ export function create(appConfig?: { types?: any }): App {
9498

9599
const contextContributors: ContextContributor<any>[] = []
96100

97-
let server: Server.Server
98-
101+
const server = Server.create()
99102
const schema = Schema.create()
100103

101104
const settings: Settings = {
@@ -137,17 +140,14 @@ export function create(appConfig?: { types?: any }): App {
137140
* Start the server. If you do not call this explicitly then nexus will
138141
* for you. You should not normally need to call this function yourself.
139142
*/
140-
async start(): Promise<void> {
143+
async start() {
141144
// Track the start call so that we can know in entrypoint whether to run
142145
// or not start for the user.
143146
singletonChecks.state.is_was_server_start_called = true
147+
144148
// During development we dynamically import all the schema modules
145-
//
146149
// TODO IDEA we have concept of schema module and schema dir
147150
// add a "refactor" command to toggle between them
148-
// TODO put behind dev-mode guard
149-
// TODO static imports codegen at build time
150-
// TODO do not assume root source folder called `server`
151151
// TODO do not assume TS
152152
// TODO refactor and put a system behind this holy mother of...
153153

@@ -158,124 +158,25 @@ export function create(appConfig?: { types?: any }): App {
158158
Layout.schema.importModules()
159159
}
160160

161-
// todo refactor; this is from before when nexus and framework were
162-
// different (e.g. santa). Encapsulate component schema config
163-
// into framework schema module.
164-
//
165-
// Create the NexusSchema config
166-
const nexusConfig = Schema.createInternalConfig()
167-
168-
// Integrate plugin typegenAutoConfig contributions
169-
const typegenAutoConfigFromPlugins = {}
170-
for (const p of plugins) {
171-
if (p.nexus?.typegenAutoConfig) {
172-
Lo.merge(typegenAutoConfigFromPlugins, p.nexus.typegenAutoConfig)
173-
}
174-
}
175-
176-
const typegenAutoConfigObject = Lo.merge(
177-
{},
178-
typegenAutoConfigFromPlugins,
179-
nexusConfig.typegenAutoConfig!
180-
)
181-
nexusConfig.typegenAutoConfig = undefined
182-
183-
function contextTypeContribSpecToCode(
184-
ctxTypeContribSpec: Record<string, string>
185-
): string {
186-
return stripIndents`
187-
interface Context {
188-
${Object.entries(ctxTypeContribSpec)
189-
.map(([name, type]) => {
190-
// Quote key name to handle case of identifier-incompatible key names
191-
return `'${name}': ${type}`
192-
})
193-
.join('\n')}
194-
}
195-
`
196-
}
197-
198-
// Our use-case of multiple context sources seems to require a custom
199-
// handling of typegenConfig. Opened an issue about maybe making our
200-
// curreent use-case, fairly basic, integrated into the auto system, here:
201-
// https://github.com./prisma-labs/nexus/issues/323
202-
nexusConfig.typegenConfig = async (schema, outputPath) => {
203-
const configurator = await typegenAutoConfig(typegenAutoConfigObject)
204-
const config = await configurator(schema, outputPath)
205-
206-
// Initialize
207-
config.imports.push('interface Context {}')
208-
config.contextType = 'Context'
209-
210-
// Integrate user's app calls to app.addToContext
211-
const addToContextCallResults: string[] = process.env
212-
.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS
213-
? JSON.parse(process.env.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS)
214-
: []
215-
216-
const addToContextInterfaces = addToContextCallResults
217-
.map(result => {
218-
return stripIndents`
219-
interface Context ${result}
220-
`
221-
})
222-
.join('\n\n')
223-
224-
config.imports.push(addToContextInterfaces)
225-
226-
// Integrate plugin context contributions
227-
for (const p of plugins) {
228-
if (!p.context) continue
229-
230-
if (p.context.typeGen.imports) {
231-
config.imports.push(
232-
...p.context.typeGen.imports.map(
233-
im => `import * as ${im.as} from '${im.from}'`
234-
)
235-
)
236-
}
237-
238-
config.imports.push(
239-
contextTypeContribSpecToCode(p.context.typeGen.fields)
240-
)
241-
}
242-
243-
config.imports.push(
244-
"import * as Logger from 'nexus-future/dist/lib/logger'",
245-
contextTypeContribSpecToCode({
246-
log: 'Logger.Logger',
247-
})
248-
)
249-
250-
api.log.trace('built up Nexus typegenConfig', { config })
251-
return config
252-
}
253-
254-
log.trace('built up schema config', { nexusConfig })
255-
256-
// Merge the plugin nexus plugins
257-
nexusConfig.plugins = nexusConfig.plugins ?? []
258-
for (const plugin of plugins) {
259-
nexusConfig.plugins.push(...(plugin.nexus?.plugins ?? []))
260-
}
261-
262-
if (appConfig?.types && appConfig.types.length !== 0) {
263-
nexusConfig.types.push(...appConfig.types)
264-
}
161+
const nexusConfig = Schema.createInternalConfig(plugins)
162+
const compiledSchema = await schema.private.compile(nexusConfig)
265163

266164
if (schema.private.types.length === 0) {
267165
log.warn(Layout.schema.emptyExceptionMessage())
268166
}
269167

270-
return Server.create({
271-
schema: await schema.private.compile(nexusConfig),
168+
return server.createAndStart({
169+
schema: compiledSchema,
272170
plugins,
273171
contextContributors,
274-
...settings.current.server,
275-
}).start()
172+
settings,
173+
})
174+
},
175+
stop() {
176+
return server.stop()
276177
},
277-
async stop() {
278-
server?.stop
178+
custom(customizer) {
179+
server.setCustomizer(customizer)
279180
},
280181
},
281182
}

src/framework/schema/config.ts

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import * as Nexus from '@nexus/schema'
2+
import { stripIndents } from 'common-tags'
3+
import * as fs from 'fs-jetpack'
4+
import * as Lo from 'lodash'
5+
import * as Plugin from '../../core/plugin'
6+
import { log } from './logger'
7+
import {
8+
shouldExitAfterGenerateArtifacts,
9+
shouldGenerateArtifacts,
10+
} from './nexus'
11+
12+
export type NexusConfig = Nexus.core.SchemaConfig
13+
14+
export function createInternalConfig(
15+
plugins: Plugin.RuntimeContributions[]
16+
): NexusConfig {
17+
const defaultConfig = createDefaultNexusConfig()
18+
19+
// Merge the plugin nexus plugins
20+
defaultConfig.plugins = defaultConfig.plugins ?? []
21+
22+
for (const plugin of plugins) {
23+
defaultConfig.plugins.push(...(plugin.nexus?.plugins ?? []))
24+
}
25+
26+
const finalConfig = withAutoTypegenConfig(defaultConfig, plugins)
27+
28+
log.trace('built up schema config', { nexusConfig: finalConfig })
29+
30+
return finalConfig
31+
}
32+
33+
function createDefaultNexusConfig(): NexusConfig {
34+
const NEXUS_DEFAULT_TYPEGEN_PATH = fs.path(
35+
'node_modules/@types/typegen-nexus/index.d.ts'
36+
)
37+
return {
38+
outputs: {
39+
schema: false,
40+
typegen: NEXUS_DEFAULT_TYPEGEN_PATH,
41+
},
42+
typegenAutoConfig: {
43+
sources: [],
44+
},
45+
shouldGenerateArtifacts: shouldGenerateArtifacts(),
46+
shouldExitAfterGenerateArtifacts: shouldExitAfterGenerateArtifacts(),
47+
types: [],
48+
}
49+
}
50+
51+
function withAutoTypegenConfig(
52+
nexusConfig: NexusConfig,
53+
plugins: Plugin.RuntimeContributions[]
54+
) {
55+
// Integrate plugin typegenAutoConfig contributions
56+
const typegenAutoConfigFromPlugins = {}
57+
for (const p of plugins) {
58+
if (p.nexus?.typegenAutoConfig) {
59+
Lo.merge(typegenAutoConfigFromPlugins, p.nexus.typegenAutoConfig)
60+
}
61+
}
62+
63+
const typegenAutoConfigObject = Lo.merge(
64+
{},
65+
typegenAutoConfigFromPlugins,
66+
nexusConfig.typegenAutoConfig!
67+
)
68+
nexusConfig.typegenAutoConfig = undefined
69+
70+
function contextTypeContribSpecToCode(
71+
ctxTypeContribSpec: Record<string, string>
72+
): string {
73+
return stripIndents`
74+
interface Context {
75+
${Object.entries(ctxTypeContribSpec)
76+
.map(([name, type]) => {
77+
// Quote key name to handle case of identifier-incompatible key names
78+
return `'${name}': ${type}`
79+
})
80+
.join('\n')}
81+
}
82+
`
83+
}
84+
85+
// Our use-case of multiple context sources seems to require a custom
86+
// handling of typegenConfig. Opened an issue about maybe making our
87+
// curreent use-case, fairly basic, integrated into the auto system, here:
88+
// https://github.com./prisma-labs/nexus/issues/323
89+
nexusConfig.typegenConfig = async (schema, outputPath) => {
90+
const configurator = await Nexus.core.typegenAutoConfig(
91+
typegenAutoConfigObject
92+
)
93+
const config = await configurator(schema, outputPath)
94+
95+
// Initialize
96+
config.imports.push('interface Context {}')
97+
config.contextType = 'Context'
98+
99+
// Integrate user's app calls to app.addToContext
100+
const addToContextCallResults: string[] = process.env
101+
.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS
102+
? JSON.parse(process.env.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS)
103+
: []
104+
105+
const addToContextInterfaces = addToContextCallResults
106+
.map(result => {
107+
return stripIndents`
108+
interface Context ${result}
109+
`
110+
})
111+
.join('\n\n')
112+
113+
config.imports.push(addToContextInterfaces)
114+
115+
// Integrate plugin context contributions
116+
for (const p of plugins) {
117+
if (!p.context) continue
118+
119+
if (p.context.typeGen.imports) {
120+
config.imports.push(
121+
...p.context.typeGen.imports.map(
122+
im => `import * as ${im.as} from '${im.from}'`
123+
)
124+
)
125+
}
126+
127+
config.imports.push(
128+
contextTypeContribSpecToCode(p.context.typeGen.fields)
129+
)
130+
}
131+
132+
config.imports.push(
133+
"import * as Logger from 'nexus-future/dist/lib/logger'",
134+
contextTypeContribSpecToCode({
135+
log: 'Logger.Logger',
136+
})
137+
)
138+
139+
log.trace('built up Nexus typegenConfig', { config })
140+
return config
141+
}
142+
143+
return nexusConfig
144+
}

src/framework/schema/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { createInternalConfig } from './nexus'
1+
export { createInternalConfig } from './config'
22
export { create, Schema, SettingsData, SettingsInput } from './schema'

0 commit comments

Comments
 (0)