Skip to content

feat(settings): add new system component #367

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 4 commits into from
Feb 4, 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
2 changes: 1 addition & 1 deletion docs/guides/configuration.md
Original file line number Diff line number Diff line change
@@ -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
39 changes: 30 additions & 9 deletions docs/references/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ schema.objectType({

#### `log`

An instance of [`RootLogger`](#rootlogger).
An instance of [`Logger`](#logger).

**Example**

Expand All @@ -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
Expand Down Expand Up @@ -102,14 +121,6 @@ TODO

#### `server.stop`

### `RootLogger`

TODO

Extends [`Logger`](#logger)

#### `rootLogger.settings`

### `Logger`

TODO
Expand All @@ -131,3 +142,13 @@ TODO
#### `logger.addToContext`

#### `logger.child`

### `Settings`

TODO

#### `change`

#### `current`

#### `original`
112 changes: 76 additions & 36 deletions src/framework/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,43 @@ 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<Server.Options, 'port' | 'playground' | 'startMessage'>
>

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 +
// plugin augmentations type
type ContextContributor<T extends {}> = (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)
*
* ### todo
*
*/
server: {
start: (config?: ServerOptions) => Promise<void>
/**
* todo
*/
start: () => Promise<void>
/**
* todo
*/
stop: () => Promise<void>
}
/**
* todo
*/
settings: Settings
/**
* [API Reference](https://nexus-future.now.sh/#/references/api?id=appschema) // [Guide](todo)
*
Expand All @@ -62,56 +65,93 @@ 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.
plugins.push(...Plugin.loadAllRuntimePluginsFromPackageJsonSync())

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

/**
* 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<void> {
async start(): Promise<void> {
// 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
Expand Down Expand Up @@ -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() {
Expand Down
15 changes: 13 additions & 2 deletions src/framework/index.ts
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/framework/schema/index.ts
Original file line number Diff line number Diff line change
@@ -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'
52 changes: 33 additions & 19 deletions src/framework/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type ConnectionPluginConfig = NonNullable<

type ConnectionConfig = Omit<ConnectionPluginConfig, 'nexusFieldName'>

type SettingsInput = {
export type SettingsInput = {
/**
* todo
*/
Expand All @@ -35,6 +35,8 @@ type SettingsInput = {
}
}

export type SettingsData = SettingsInput

export type Schema = {
// addToContext: <T extends {}>(
// contextContributor: ContextContributor<T>
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -129,6 +141,8 @@ export function create(): SchemaInternal {
extendInputType,
},
}

return api
}

/**
Expand Down
Loading