diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 5f7ecf24f..802ac1f6a 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -1 +1 @@ -`nexus` does not yet have a configuration system. We are working on its design in [#297](https://github.com/graphql-nexus/nexus-future/issues/297). +## TODO diff --git a/docs/references/api.md b/docs/references/api.md index ec2f9bccf..ee4807b39 100644 --- a/docs/references/api.md +++ b/docs/references/api.md @@ -23,7 +23,7 @@ schema.objectType({ #### `log` -An instance of [`RootLogger`](#rootlogger). +An instance of [`Logger`](#logger). **Example** @@ -49,6 +49,25 @@ Framework Notes: - If your app does not call `server.start` then `nexus` will. It is idiomatic to allow `nexus` to take care of this. If you deviate, we would love to learn about your use-case! +#### `settings` + +An instance of [`Settings`](#settings). + +**Example** + +```ts +import { log, settings } from 'nexus-future' + +settings.change({ + server: { + startMessage: info => { + settings.original.server.startMessage(info) + log.warn('stowaway message! :p') + }, + }, +}) +``` + ### `nexus-future/testing` todo @@ -102,14 +121,6 @@ TODO #### `server.stop` -### `RootLogger` - -TODO - -Extends [`Logger`](#logger) - -#### `rootLogger.settings` - ### `Logger` TODO @@ -131,3 +142,13 @@ TODO #### `logger.addToContext` #### `logger.child` + +### `Settings` + +TODO + +#### `change` + +#### `current` + +#### `original` diff --git a/src/framework/app.ts b/src/framework/app.ts index 7b3f03aba..55c87124d 100644 --- a/src/framework/app.ts +++ b/src/framework/app.ts @@ -10,16 +10,10 @@ import * as singletonChecks from './singleton-checks' const log = Logger.create({ name: 'app' }) -/** - * The available server options to configure how your app runs its server. - */ -type ServerOptions = Partial< - Pick -> - type Request = HTTP.IncomingMessage & { log: Logger.Logger } -// TODO plugins could augment the request +// todo the jsdoc below is lost on the destructured object exports later on... +// todo plugins could augment the request // plugins will be able to use typegen to signal this fact // all places in the framework where the req object is referenced should be // actually referencing the typegen version, so that it reflects the req + @@ -27,13 +21,12 @@ type Request = HTTP.IncomingMessage & { log: Logger.Logger } type ContextContributor = (req: Request) => T export type App = { - use: (plugin: Plugin.Driver) => App /** * [API Reference](https://nexus-future.now.sh/#/references/api?id=logger) ⌁ [Guide](https://nexus-future.now.sh/#/guides/logging) * * ### todo */ - log: Logger.RootLogger + log: Logger.Logger /** * [API Reference](https://nexus-future.now.sh/#/references/api?id=server) ⌁ [Guide](todo) * @@ -41,9 +34,19 @@ export type App = { * */ server: { - start: (config?: ServerOptions) => Promise + /** + * todo + */ + start: () => Promise + /** + * todo + */ stop: () => Promise } + /** + * todo + */ + settings: Settings /** * [API Reference](https://nexus-future.now.sh/#/references/api?id=appschema) // [Guide](todo) * @@ -62,13 +65,42 @@ export type App = { } } +type SettingsInput = { + logger?: Logger.SettingsInput + schema?: Schema.SettingsInput + server?: Server.ExtraSettingsInput +} + +type SettingsData = Readonly<{ + logger: Logger.SettingsData + schema: Schema.SettingsData + server: Server.ExtraSettingsData +}> + +/** + * todo + */ +type Settings = { + /** + * todo + */ + original: SettingsData + /** + * todo + */ + current: SettingsData + /** + * todo + */ + change(newSetting: SettingsInput): void +} + /** * Crate an app instance * TODO extract and improve config type */ export function create(appConfig?: { types?: any }): App { const plugins: Plugin.RuntimeContributions[] = [] - // Automatically use all installed plugins // TODO during build step we should turn this into static imports, not unlike // the schema module imports system. @@ -76,42 +108,50 @@ export function create(appConfig?: { types?: any }): App { const contextContributors: ContextContributor[] = [] - /** - * Auto-use all runtime plugins that are installed in the project - */ - let server: Server.Server + const schema = Schema.create() - const api: App = { - log, - // TODO bring this back pending future discussion - // installGlobally() { - // installGlobally(api) - // return api - // }, - // TODO think hard about this api... When/why would it be used with auto-use - // import system? "Inproject" plugins? What is the right place to expose - // this? app.plugins.use() ? - use(pluginDriver) { - const plugin = pluginDriver.loadRuntimePlugin() - if (plugin) { - plugins.push(plugin) + + const settings: Settings = { + change(newSettings) { + if (newSettings.logger) { + log.settings(newSettings.logger) + } + if (newSettings.schema) { + schema.private.settings.change(newSettings.schema) } - return api + if (newSettings.server) { + Object.assign(settings.current.server, newSettings.server) + } + }, + current: { + logger: log.settings, + schema: schema.private.settings.data, + server: { ...Server.defaultExtraSettings }, }, + original: Lo.cloneDeep({ + logger: log.settings, + schema: schema.private.settings.data, + server: { ...Server.defaultExtraSettings }, + }), + } + + const api: App = { + log, + settings, schema: { addToContext(contextContributor) { contextContributors.push(contextContributor) return api }, - ...schema.external, + ...schema.public, }, server: { /** * 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(opts: ServerOptions = {}): Promise { + async start(): Promise { // 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 @@ -237,17 +277,17 @@ export function create(appConfig?: { types?: any }): App { nexusConfig.types.push(...appConfig.types) } - if (schema.internal.types.length === 0) { + if (schema.private.types.length === 0) { log.warn( 'Your GraphQL schema is empty. Make sure your GraphQL schema lives in a `schema.ts` file or some `schema/` directories' ) } return Server.create({ - schema: await schema.internal.compile(nexusConfig), + schema: await schema.private.compile(nexusConfig), plugins, contextContributors, - ...opts, + ...settings.current.server, }).start() }, async stop() { diff --git a/src/framework/index.ts b/src/framework/index.ts index 43fd1de0c..3b76aa7fb 100644 --- a/src/framework/index.ts +++ b/src/framework/index.ts @@ -1,7 +1,18 @@ import * as App from './app' const app = App.create() -const { log, schema, server } = app export default app -export { log, schema, server } + +// Destructure app for export +// Do not use destructuring syntax +// Breaks jsdoc, only first destructed member annotated +// todo jsdoc + +export const log = app.log + +export const schema = app.schema + +export const server = app.server + +export const settings = app.settings diff --git a/src/framework/schema/index.ts b/src/framework/schema/index.ts index b67aef3e4..c75eb5efd 100644 --- a/src/framework/schema/index.ts +++ b/src/framework/schema/index.ts @@ -1,3 +1,3 @@ export { findDirOrModules, importModules, printStaticImports } from './modules' export { createInternalConfig } from './nexus' -export { create, Schema } from './schema' +export { create, Schema, SettingsData, SettingsInput } from './schema' diff --git a/src/framework/schema/schema.ts b/src/framework/schema/schema.ts index d846efdbe..ee53b6ce7 100644 --- a/src/framework/schema/schema.ts +++ b/src/framework/schema/schema.ts @@ -8,7 +8,7 @@ type ConnectionPluginConfig = NonNullable< type ConnectionConfig = Omit -type SettingsInput = { +export type SettingsInput = { /** * todo */ @@ -35,6 +35,8 @@ type SettingsInput = { } } +export type SettingsData = SettingsInput + export type Schema = { // addToContext: ( // contextContributor: ContextContributor @@ -54,15 +56,18 @@ export type Schema = { idArg: typeof NexusSchema.idArg extendType: typeof NexusSchema.extendType extendInputType: typeof NexusSchema.extendInputType - settings: (settingsInput: SettingsInput) => void } type SchemaInternal = { - internal: { + private: { types: any[] compile: any + settings: { + data: SettingsData + change: (newSettings: SettingsInput) => void + } } - external: Schema + public: Schema } export function create(): SchemaInternal { @@ -86,32 +91,39 @@ export function create(): SchemaInternal { __types, } = createNexusSingleton() - const state: { settings: SettingsInput } = { + type State = { + settings: SettingsData + } + + const state: State = { settings: {}, } - return { - internal: { + const api: SchemaInternal = { + private: { types: __types, compile: (c: any) => { c.plugins = c.plugins ?? [] c.plugins.push(...processConnectionsConfig(state.settings)) return makeSchema(c) }, - }, - external: { - settings(newSettings) { - if (newSettings.connections) { - state.settings.connections = state.settings.connections ?? {} - const { types, ...connectionPluginConfig } = newSettings.connections - if (types) { - state.settings.connections.types = - state.settings.connections.types ?? {} - Object.assign(state.settings.connections.types, types) + settings: { + data: state.settings, + change(newSettings) { + if (newSettings.connections) { + state.settings.connections = state.settings.connections ?? {} + const { types, ...connectionPluginConfig } = newSettings.connections + if (types) { + state.settings.connections.types = + state.settings.connections.types ?? {} + Object.assign(state.settings.connections.types, types) + } + Object.assign(state.settings.connections, connectionPluginConfig) } - Object.assign(state.settings.connections, connectionPluginConfig) - } + }, }, + }, + public: { queryType, mutationType, objectType, @@ -129,6 +141,8 @@ export function create(): SchemaInternal { extendInputType, }, } + + return api } /** diff --git a/src/framework/server.ts b/src/framework/server.ts index 4576e1150..531310c0f 100644 --- a/src/framework/server.ts +++ b/src/framework/server.ts @@ -15,7 +15,7 @@ const log = Logger.create({ name: 'server' }) * The default server options. These are merged with whatever you provide. Your * settings take precedence over these. */ -const optDefaults: Required = { +export const defaultExtraSettings: Required = { port: typeof process.env.NEXUS_PORT === 'string' ? parseInt(process.env.NEXUS_PORT, 10) @@ -33,8 +33,14 @@ const optDefaults: Required = { playground: process.env.NODE_ENV === 'production' ? false : true, } -type ExtraOptions = { +export type ExtraSettingsInput = { + /** + * todo + */ port?: number + /** + * todo + */ playground?: boolean /** * Create a message suitable for printing to the terminal about the server @@ -43,19 +49,21 @@ type ExtraOptions = { startMessage?: (address: { port: number; host: string; ip: string }) => void } +export type ExtraSettingsData = Required + /** * The available server options to configure how your app runs its server. */ -export type Options = createExpressGraphql.OptionsData & - ExtraOptions & { +export type SettingsInput = createExpressGraphql.OptionsData & + ExtraSettingsInput & { plugins: Plugin.RuntimeContributions[] contextContributors: ContextContributor[] } -export function create(optsGiven: Options) { +export function create(settingsGiven: SettingsInput) { const http = HTTP.createServer() const express = createExpress() - const opts = { ...optDefaults, ...optsGiven } + const opts = { ...defaultExtraSettings, ...settingsGiven } http.on('request', express) diff --git a/src/framework/testing.ts b/src/framework/testing.ts index 500c5ae3b..cfcd42e98 100644 --- a/src/framework/testing.ts +++ b/src/framework/testing.ts @@ -79,12 +79,16 @@ export async function createTestContext(): Promise { require(appModule) } - if (singletonChecks.state.is_was_server_start_called === false) { - await oldServerStart({ + app.settings.change({ + server: { port, playground: false, startMessage: () => '', - }) + }, + }) + + if (singletonChecks.state.is_was_server_start_called === false) { + await oldServerStart() } else { return Promise.resolve() } diff --git a/src/index.ts b/src/index.ts index 496dc3f8d..4dc163b8d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,4 @@ +import app from './framework' + export * from './framework' +export default app diff --git a/src/lib/logger/index.ts b/src/lib/logger/index.ts index dbb0c0cd6..9829866f2 100644 --- a/src/lib/logger/index.ts +++ b/src/lib/logger/index.ts @@ -1,3 +1,3 @@ export { demo } from './demo' export { Logger } from './logger' -export { create, RootLogger } from './root-logger' +export { create, RootLogger, SettingsData, SettingsInput } from './root-logger'