Skip to content

feat: add server.custom api #382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 27 additions & 126 deletions src/framework/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { typegenAutoConfig } from '@nexus/schema/dist/core'
import { stripIndents } from 'common-tags'
import * as HTTP from 'http'
import * as Lo from 'lodash'
import * as Plugin from '../core/plugin'
Expand All @@ -19,7 +17,7 @@ type Request = HTTP.IncomingMessage & { log: Logger.Logger }
// all places in the framework where the req object is referenced should be
// actually referencing the typegen version, so that it reflects the req +
// plugin augmentations type
type ContextContributor<T extends {}> = (req: Request) => T
type ContextContributor<Req, T extends {} = any> = (req: Req) => T

export type App = {
/**
Expand All @@ -28,7 +26,13 @@ export type App = {
* ### todo
*/
log: Logger.Logger
server: Server.Server
/**
* [API Reference](https://nexus-future.now.sh/#/references/api?id=server) ⌁ [Guide](todo)
*
* ### todo
*
*/
server: Server.ServerWithCustomizer
/**
* todo
*/
Expand All @@ -45,8 +49,8 @@ export type App = {
/**
* todo
*/
addToContext: <T extends {}>(
contextContributor: ContextContributor<T>
addToContext: <Req extends any = Request, T extends {} = any>(
contextContributor: ContextContributor<Req, T>
) => void
}
}
Expand All @@ -57,7 +61,7 @@ type SettingsInput = {
server?: Server.ExtraSettingsInput
}

type SettingsData = Readonly<{
export type SettingsData = Readonly<{
logger: Logger.SettingsData
schema: Schema.SettingsData
server: Server.ExtraSettingsData
Expand All @@ -66,7 +70,7 @@ type SettingsData = Readonly<{
/**
* todo
*/
type Settings = {
export type Settings = {
/**
* todo
*/
Expand All @@ -85,7 +89,7 @@ type Settings = {
* Crate an app instance
* TODO extract and improve config type
*/
export function create(appConfig?: { types?: any }): App {
export function create(): App {
const plugins: Plugin.RuntimeContributions[] = []
// Automatically use all installed plugins
// TODO during build step we should turn this into static imports, not unlike
Expand All @@ -94,8 +98,7 @@ export function create(appConfig?: { types?: any }): App {

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

let server: Server.Server

const server = Server.create()
const schema = Schema.create()

const settings: Settings = {
Expand Down Expand Up @@ -137,17 +140,14 @@ export function create(appConfig?: { types?: any }): App {
* Start the server. If you do not call this explicitly then nexus will
* for you. You should not normally need to call this function yourself.
*/
async start(): Promise<void> {
async start() {
// Track the start call so that we can know in entrypoint whether to run
// or not start for the user.
singletonChecks.state.is_was_server_start_called = true

// During development we dynamically import all the schema modules
//
// TODO IDEA we have concept of schema module and schema dir
// add a "refactor" command to toggle between them
// TODO put behind dev-mode guard
// TODO static imports codegen at build time
// TODO do not assume root source folder called `server`
// TODO do not assume TS
// TODO refactor and put a system behind this holy mother of...

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

// todo refactor; this is from before when nexus and framework were
// different (e.g. santa). Encapsulate component schema config
// into framework schema module.
//
// Create the NexusSchema config
const nexusConfig = Schema.createInternalConfig()

// Integrate plugin typegenAutoConfig contributions
const typegenAutoConfigFromPlugins = {}
for (const p of plugins) {
if (p.nexus?.typegenAutoConfig) {
Lo.merge(typegenAutoConfigFromPlugins, p.nexus.typegenAutoConfig)
}
}

const typegenAutoConfigObject = Lo.merge(
{},
typegenAutoConfigFromPlugins,
nexusConfig.typegenAutoConfig!
)
nexusConfig.typegenAutoConfig = undefined

function contextTypeContribSpecToCode(
ctxTypeContribSpec: Record<string, string>
): string {
return stripIndents`
interface Context {
${Object.entries(ctxTypeContribSpec)
.map(([name, type]) => {
// Quote key name to handle case of identifier-incompatible key names
return `'${name}': ${type}`
})
.join('\n')}
}
`
}

// Our use-case of multiple context sources seems to require a custom
// handling of typegenConfig. Opened an issue about maybe making our
// curreent use-case, fairly basic, integrated into the auto system, here:
// https://github.com./prisma-labs/nexus/issues/323
nexusConfig.typegenConfig = async (schema, outputPath) => {
const configurator = await typegenAutoConfig(typegenAutoConfigObject)
const config = await configurator(schema, outputPath)

// Initialize
config.imports.push('interface Context {}')
config.contextType = 'Context'

// Integrate user's app calls to app.addToContext
const addToContextCallResults: string[] = process.env
.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS
? JSON.parse(process.env.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS)
: []

const addToContextInterfaces = addToContextCallResults
.map(result => {
return stripIndents`
interface Context ${result}
`
})
.join('\n\n')

config.imports.push(addToContextInterfaces)

// Integrate plugin context contributions
for (const p of plugins) {
if (!p.context) continue

if (p.context.typeGen.imports) {
config.imports.push(
...p.context.typeGen.imports.map(
im => `import * as ${im.as} from '${im.from}'`
)
)
}

config.imports.push(
contextTypeContribSpecToCode(p.context.typeGen.fields)
)
}

config.imports.push(
"import * as Logger from 'nexus-future/dist/lib/logger'",
contextTypeContribSpecToCode({
log: 'Logger.Logger',
})
)

api.log.trace('built up Nexus typegenConfig', { config })
return config
}

log.trace('built up schema config', { nexusConfig })

// Merge the plugin nexus plugins
nexusConfig.plugins = nexusConfig.plugins ?? []
for (const plugin of plugins) {
nexusConfig.plugins.push(...(plugin.nexus?.plugins ?? []))
}

if (appConfig?.types && appConfig.types.length !== 0) {
nexusConfig.types.push(...appConfig.types)
}
const nexusConfig = Schema.createInternalConfig(plugins)
const compiledSchema = await schema.private.compile(nexusConfig)

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

return Server.create({
schema: await schema.private.compile(nexusConfig),
return server.createAndStart({
schema: compiledSchema,
plugins,
contextContributors,
...settings.current.server,
}).start()
settings,
})
},
stop() {
return server.stop()
},
async stop() {
server?.stop
custom(customizer) {
server.setCustomizer(customizer)
},
},
}
Expand Down
144 changes: 144 additions & 0 deletions src/framework/schema/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import * as Nexus from '@nexus/schema'
import { stripIndents } from 'common-tags'
import * as fs from 'fs-jetpack'
import * as Lo from 'lodash'
import * as Plugin from '../../core/plugin'
import { log } from './logger'
import {
shouldExitAfterGenerateArtifacts,
shouldGenerateArtifacts,
} from './nexus'

export type NexusConfig = Nexus.core.SchemaConfig

export function createInternalConfig(
plugins: Plugin.RuntimeContributions[]
): NexusConfig {
const defaultConfig = createDefaultNexusConfig()

// Merge the plugin nexus plugins
defaultConfig.plugins = defaultConfig.plugins ?? []

for (const plugin of plugins) {
defaultConfig.plugins.push(...(plugin.nexus?.plugins ?? []))
}

const finalConfig = withAutoTypegenConfig(defaultConfig, plugins)

log.trace('built up schema config', { nexusConfig: finalConfig })

return finalConfig
}

function createDefaultNexusConfig(): NexusConfig {
const NEXUS_DEFAULT_TYPEGEN_PATH = fs.path(
'node_modules/@types/typegen-nexus/index.d.ts'
)
return {
outputs: {
schema: false,
typegen: NEXUS_DEFAULT_TYPEGEN_PATH,
},
typegenAutoConfig: {
sources: [],
},
shouldGenerateArtifacts: shouldGenerateArtifacts(),
shouldExitAfterGenerateArtifacts: shouldExitAfterGenerateArtifacts(),
types: [],
}
}

function withAutoTypegenConfig(
nexusConfig: NexusConfig,
plugins: Plugin.RuntimeContributions[]
) {
// Integrate plugin typegenAutoConfig contributions
const typegenAutoConfigFromPlugins = {}
for (const p of plugins) {
if (p.nexus?.typegenAutoConfig) {
Lo.merge(typegenAutoConfigFromPlugins, p.nexus.typegenAutoConfig)
}
}

const typegenAutoConfigObject = Lo.merge(
{},
typegenAutoConfigFromPlugins,
nexusConfig.typegenAutoConfig!
)
nexusConfig.typegenAutoConfig = undefined

function contextTypeContribSpecToCode(
ctxTypeContribSpec: Record<string, string>
): string {
return stripIndents`
interface Context {
${Object.entries(ctxTypeContribSpec)
.map(([name, type]) => {
// Quote key name to handle case of identifier-incompatible key names
return `'${name}': ${type}`
})
.join('\n')}
}
`
}

// Our use-case of multiple context sources seems to require a custom
// handling of typegenConfig. Opened an issue about maybe making our
// curreent use-case, fairly basic, integrated into the auto system, here:
// https://github.com./prisma-labs/nexus/issues/323
nexusConfig.typegenConfig = async (schema, outputPath) => {
const configurator = await Nexus.core.typegenAutoConfig(
typegenAutoConfigObject
)
const config = await configurator(schema, outputPath)

// Initialize
config.imports.push('interface Context {}')
config.contextType = 'Context'

// Integrate user's app calls to app.addToContext
const addToContextCallResults: string[] = process.env
.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS
? JSON.parse(process.env.NEXUS_TYPEGEN_ADD_CONTEXT_RESULTS)
: []

const addToContextInterfaces = addToContextCallResults
.map(result => {
return stripIndents`
interface Context ${result}
`
})
.join('\n\n')

config.imports.push(addToContextInterfaces)

// Integrate plugin context contributions
for (const p of plugins) {
if (!p.context) continue

if (p.context.typeGen.imports) {
config.imports.push(
...p.context.typeGen.imports.map(
im => `import * as ${im.as} from '${im.from}'`
)
)
}

config.imports.push(
contextTypeContribSpecToCode(p.context.typeGen.fields)
)
}

config.imports.push(
"import * as Logger from 'nexus-future/dist/lib/logger'",
contextTypeContribSpecToCode({
log: 'Logger.Logger',
})
)

log.trace('built up Nexus typegenConfig', { config })
return config
}

return nexusConfig
}
2 changes: 1 addition & 1 deletion src/framework/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { createInternalConfig } from './nexus'
export { createInternalConfig } from './config'
export { create, Schema, SettingsData, SettingsInput } from './schema'
Loading