Skip to content

Commit 7474d3d

Browse files
author
Jason Kuhrt
authored
feat(settings): add new system component (#367)
closes #297 BREAKING CHANGE: - Settings that used to be passed to `server.start` are now under `server` key in settings input. - `log.settings` no longer exists. Settings that used to be passed to it are now under `logger` key in settings input. - `schema.settings` no longer exists. Settings that used to be passed to it are now under `schema` key in settings input.
1 parent 2ea755e commit 7474d3d

File tree

10 files changed

+179
-78
lines changed

10 files changed

+179
-78
lines changed

docs/guides/configuration.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +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).
1+
## TODO

docs/references/api.md

+30-9
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ schema.objectType({
2323

2424
#### `log`
2525

26-
An instance of [`RootLogger`](#rootlogger).
26+
An instance of [`Logger`](#logger).
2727

2828
**Example**
2929

@@ -49,6 +49,25 @@ Framework Notes:
4949

5050
- 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!
5151

52+
#### `settings`
53+
54+
An instance of [`Settings`](#settings).
55+
56+
**Example**
57+
58+
```ts
59+
import { log, settings } from 'nexus-future'
60+
61+
settings.change({
62+
server: {
63+
startMessage: info => {
64+
settings.original.server.startMessage(info)
65+
log.warn('stowaway message! :p')
66+
},
67+
},
68+
})
69+
```
70+
5271
### `nexus-future/testing`
5372

5473
todo
@@ -102,14 +121,6 @@ TODO
102121

103122
#### `server.stop`
104123

105-
### `RootLogger`
106-
107-
TODO
108-
109-
Extends [`Logger`](#logger)
110-
111-
#### `rootLogger.settings`
112-
113124
### `Logger`
114125

115126
TODO
@@ -131,3 +142,13 @@ TODO
131142
#### `logger.addToContext`
132143

133144
#### `logger.child`
145+
146+
### `Settings`
147+
148+
TODO
149+
150+
#### `change`
151+
152+
#### `current`
153+
154+
#### `original`

src/framework/app.ts

+76-36
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,43 @@ import * as singletonChecks from './singleton-checks'
1010

1111
const log = Logger.create({ name: 'app' })
1212

13-
/**
14-
* The available server options to configure how your app runs its server.
15-
*/
16-
type ServerOptions = Partial<
17-
Pick<Server.Options, 'port' | 'playground' | 'startMessage'>
18-
>
19-
2013
type Request = HTTP.IncomingMessage & { log: Logger.Logger }
2114

22-
// TODO plugins could augment the request
15+
// todo the jsdoc below is lost on the destructured object exports later on...
16+
// todo plugins could augment the request
2317
// plugins will be able to use typegen to signal this fact
2418
// all places in the framework where the req object is referenced should be
2519
// actually referencing the typegen version, so that it reflects the req +
2620
// plugin augmentations type
2721
type ContextContributor<T extends {}> = (req: Request) => T
2822

2923
export type App = {
30-
use: (plugin: Plugin.Driver) => App
3124
/**
3225
* [API Reference](https://nexus-future.now.sh/#/references/api?id=logger) ⌁ [Guide](https://nexus-future.now.sh/#/guides/logging)
3326
*
3427
* ### todo
3528
*/
36-
log: Logger.RootLogger
29+
log: Logger.Logger
3730
/**
3831
* [API Reference](https://nexus-future.now.sh/#/references/api?id=server) ⌁ [Guide](todo)
3932
*
4033
* ### todo
4134
*
4235
*/
4336
server: {
44-
start: (config?: ServerOptions) => Promise<void>
37+
/**
38+
* todo
39+
*/
40+
start: () => Promise<void>
41+
/**
42+
* todo
43+
*/
4544
stop: () => Promise<void>
4645
}
46+
/**
47+
* todo
48+
*/
49+
settings: Settings
4750
/**
4851
* [API Reference](https://nexus-future.now.sh/#/references/api?id=appschema) // [Guide](todo)
4952
*
@@ -62,56 +65,93 @@ export type App = {
6265
}
6366
}
6467

68+
type SettingsInput = {
69+
logger?: Logger.SettingsInput
70+
schema?: Schema.SettingsInput
71+
server?: Server.ExtraSettingsInput
72+
}
73+
74+
type SettingsData = Readonly<{
75+
logger: Logger.SettingsData
76+
schema: Schema.SettingsData
77+
server: Server.ExtraSettingsData
78+
}>
79+
80+
/**
81+
* todo
82+
*/
83+
type Settings = {
84+
/**
85+
* todo
86+
*/
87+
original: SettingsData
88+
/**
89+
* todo
90+
*/
91+
current: SettingsData
92+
/**
93+
* todo
94+
*/
95+
change(newSetting: SettingsInput): void
96+
}
97+
6598
/**
6699
* Crate an app instance
67100
* TODO extract and improve config type
68101
*/
69102
export function create(appConfig?: { types?: any }): App {
70103
const plugins: Plugin.RuntimeContributions[] = []
71-
72104
// Automatically use all installed plugins
73105
// TODO during build step we should turn this into static imports, not unlike
74106
// the schema module imports system.
75107
plugins.push(...Plugin.loadAllRuntimePluginsFromPackageJsonSync())
76108

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

79-
/**
80-
* Auto-use all runtime plugins that are installed in the project
81-
*/
82-
83111
let server: Server.Server
112+
84113
const schema = Schema.create()
85-
const api: App = {
86-
log,
87-
// TODO bring this back pending future discussion
88-
// installGlobally() {
89-
// installGlobally(api)
90-
// return api
91-
// },
92-
// TODO think hard about this api... When/why would it be used with auto-use
93-
// import system? "Inproject" plugins? What is the right place to expose
94-
// this? app.plugins.use() ?
95-
use(pluginDriver) {
96-
const plugin = pluginDriver.loadRuntimePlugin()
97-
if (plugin) {
98-
plugins.push(plugin)
114+
115+
const settings: Settings = {
116+
change(newSettings) {
117+
if (newSettings.logger) {
118+
log.settings(newSettings.logger)
119+
}
120+
if (newSettings.schema) {
121+
schema.private.settings.change(newSettings.schema)
99122
}
100-
return api
123+
if (newSettings.server) {
124+
Object.assign(settings.current.server, newSettings.server)
125+
}
126+
},
127+
current: {
128+
logger: log.settings,
129+
schema: schema.private.settings.data,
130+
server: { ...Server.defaultExtraSettings },
101131
},
132+
original: Lo.cloneDeep({
133+
logger: log.settings,
134+
schema: schema.private.settings.data,
135+
server: { ...Server.defaultExtraSettings },
136+
}),
137+
}
138+
139+
const api: App = {
140+
log,
141+
settings,
102142
schema: {
103143
addToContext(contextContributor) {
104144
contextContributors.push(contextContributor)
105145
return api
106146
},
107-
...schema.external,
147+
...schema.public,
108148
},
109149
server: {
110150
/**
111151
* Start the server. If you do not call this explicitly then nexus will
112152
* for you. You should not normally need to call this function yourself.
113153
*/
114-
async start(opts: ServerOptions = {}): Promise<void> {
154+
async start(): Promise<void> {
115155
// Track the start call so that we can know in entrypoint whether to run
116156
// or not start for the user.
117157
singletonChecks.state.is_was_server_start_called = true
@@ -237,17 +277,17 @@ export function create(appConfig?: { types?: any }): App {
237277
nexusConfig.types.push(...appConfig.types)
238278
}
239279

240-
if (schema.internal.types.length === 0) {
280+
if (schema.private.types.length === 0) {
241281
log.warn(
242282
'Your GraphQL schema is empty. Make sure your GraphQL schema lives in a `schema.ts` file or some `schema/` directories'
243283
)
244284
}
245285

246286
return Server.create({
247-
schema: await schema.internal.compile(nexusConfig),
287+
schema: await schema.private.compile(nexusConfig),
248288
plugins,
249289
contextContributors,
250-
...opts,
290+
...settings.current.server,
251291
}).start()
252292
},
253293
async stop() {

src/framework/index.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import * as App from './app'
22

33
const app = App.create()
4-
const { log, schema, server } = app
54

65
export default app
7-
export { log, schema, server }
6+
7+
// Destructure app for export
8+
// Do not use destructuring syntax
9+
// Breaks jsdoc, only first destructed member annotated
10+
// todo jsdoc
11+
12+
export const log = app.log
13+
14+
export const schema = app.schema
15+
16+
export const server = app.server
17+
18+
export const settings = app.settings

src/framework/schema/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { findDirOrModules, importModules, printStaticImports } from './modules'
22
export { createInternalConfig } from './nexus'
3-
export { create, Schema } from './schema'
3+
export { create, Schema, SettingsData, SettingsInput } from './schema'

src/framework/schema/schema.ts

+33-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type ConnectionPluginConfig = NonNullable<
88

99
type ConnectionConfig = Omit<ConnectionPluginConfig, 'nexusFieldName'>
1010

11-
type SettingsInput = {
11+
export type SettingsInput = {
1212
/**
1313
* todo
1414
*/
@@ -35,6 +35,8 @@ type SettingsInput = {
3535
}
3636
}
3737

38+
export type SettingsData = SettingsInput
39+
3840
export type Schema = {
3941
// addToContext: <T extends {}>(
4042
// contextContributor: ContextContributor<T>
@@ -54,15 +56,18 @@ export type Schema = {
5456
idArg: typeof NexusSchema.idArg
5557
extendType: typeof NexusSchema.extendType
5658
extendInputType: typeof NexusSchema.extendInputType
57-
settings: (settingsInput: SettingsInput) => void
5859
}
5960

6061
type SchemaInternal = {
61-
internal: {
62+
private: {
6263
types: any[]
6364
compile: any
65+
settings: {
66+
data: SettingsData
67+
change: (newSettings: SettingsInput) => void
68+
}
6469
}
65-
external: Schema
70+
public: Schema
6671
}
6772

6873
export function create(): SchemaInternal {
@@ -86,32 +91,39 @@ export function create(): SchemaInternal {
8691
__types,
8792
} = createNexusSingleton()
8893

89-
const state: { settings: SettingsInput } = {
94+
type State = {
95+
settings: SettingsData
96+
}
97+
98+
const state: State = {
9099
settings: {},
91100
}
92101

93-
return {
94-
internal: {
102+
const api: SchemaInternal = {
103+
private: {
95104
types: __types,
96105
compile: (c: any) => {
97106
c.plugins = c.plugins ?? []
98107
c.plugins.push(...processConnectionsConfig(state.settings))
99108
return makeSchema(c)
100109
},
101-
},
102-
external: {
103-
settings(newSettings) {
104-
if (newSettings.connections) {
105-
state.settings.connections = state.settings.connections ?? {}
106-
const { types, ...connectionPluginConfig } = newSettings.connections
107-
if (types) {
108-
state.settings.connections.types =
109-
state.settings.connections.types ?? {}
110-
Object.assign(state.settings.connections.types, types)
110+
settings: {
111+
data: state.settings,
112+
change(newSettings) {
113+
if (newSettings.connections) {
114+
state.settings.connections = state.settings.connections ?? {}
115+
const { types, ...connectionPluginConfig } = newSettings.connections
116+
if (types) {
117+
state.settings.connections.types =
118+
state.settings.connections.types ?? {}
119+
Object.assign(state.settings.connections.types, types)
120+
}
121+
Object.assign(state.settings.connections, connectionPluginConfig)
111122
}
112-
Object.assign(state.settings.connections, connectionPluginConfig)
113-
}
123+
},
114124
},
125+
},
126+
public: {
115127
queryType,
116128
mutationType,
117129
objectType,
@@ -129,6 +141,8 @@ export function create(): SchemaInternal {
129141
extendInputType,
130142
},
131143
}
144+
145+
return api
132146
}
133147

134148
/**

0 commit comments

Comments
 (0)