@contember/engine-server 1.2.0-alpha.9 → 1.2.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/src/MasterContainer.d.ts +6 -6
  2. package/dist/src/MasterContainer.d.ts.map +1 -1
  3. package/dist/src/MasterContainer.js +10 -13
  4. package/dist/src/MasterContainer.js.map +1 -1
  5. package/dist/src/bootstrap/Initializer.d.ts.map +1 -1
  6. package/dist/src/bootstrap/Initializer.js +3 -15
  7. package/dist/src/bootstrap/Initializer.js.map +1 -1
  8. package/dist/src/config/configSchema.d.ts +81 -0
  9. package/dist/src/config/configSchema.d.ts.map +1 -1
  10. package/dist/src/config/configSchema.js +16 -2
  11. package/dist/src/config/configSchema.js.map +1 -1
  12. package/dist/src/config/configTemplate.d.ts.map +1 -1
  13. package/dist/src/config/configTemplate.js +29 -24
  14. package/dist/src/config/configTemplate.js.map +1 -1
  15. package/dist/src/project/ProjectContainer.d.ts +8 -2
  16. package/dist/src/project/ProjectContainer.d.ts.map +1 -1
  17. package/dist/src/project/ProjectContainer.js +20 -5
  18. package/dist/src/project/ProjectContainer.js.map +1 -1
  19. package/dist/src/project/ProjectContainerResolver.d.ts +2 -3
  20. package/dist/src/project/ProjectContainerResolver.d.ts.map +1 -1
  21. package/dist/src/project/ProjectContainerResolver.js +3 -4
  22. package/dist/src/project/ProjectContainerResolver.js.map +1 -1
  23. package/dist/src/project/ProjectInitializer.d.ts +3 -2
  24. package/dist/src/project/ProjectInitializer.d.ts.map +1 -1
  25. package/dist/src/project/ProjectInitializer.js +3 -3
  26. package/dist/src/project/ProjectInitializer.js.map +1 -1
  27. package/dist/src/project/ProjectSchemaResolver.d.ts +2 -7
  28. package/dist/src/project/ProjectSchemaResolver.d.ts.map +1 -1
  29. package/dist/src/project/ProjectSchemaResolver.js +2 -16
  30. package/dist/src/project/ProjectSchemaResolver.js.map +1 -1
  31. package/dist/src/projectGroup/ProjectGroupContainer.d.ts +3 -1
  32. package/dist/src/projectGroup/ProjectGroupContainer.d.ts.map +1 -1
  33. package/dist/src/projectGroup/ProjectGroupContainer.js +10 -8
  34. package/dist/src/projectGroup/ProjectGroupContainer.js.map +1 -1
  35. package/dist/src/start.js +16 -13
  36. package/dist/src/start.js.map +1 -1
  37. package/dist/src/tsconfig.tsbuildinfo +1 -1
  38. package/dist/src/utils/sentry.d.ts +8 -9
  39. package/dist/src/utils/sentry.d.ts.map +1 -1
  40. package/dist/src/utils/sentry.js +44 -29
  41. package/dist/src/utils/sentry.js.map +1 -1
  42. package/dist/src/utils/serverStartup.d.ts +3 -1
  43. package/dist/src/utils/serverStartup.d.ts.map +1 -1
  44. package/dist/src/utils/serverStartup.js +49 -12
  45. package/dist/src/utils/serverStartup.js.map +1 -1
  46. package/dist/src/utils/serverTermination.d.ts +2 -1
  47. package/dist/src/utils/serverTermination.d.ts.map +1 -1
  48. package/dist/src/utils/serverTermination.js +4 -6
  49. package/dist/src/utils/serverTermination.js.map +1 -1
  50. package/dist/tests/cases/configResolver.test.d.ts +2 -0
  51. package/dist/tests/cases/configResolver.test.d.ts.map +1 -0
  52. package/dist/tests/cases/configResolver.test.js +67 -0
  53. package/dist/tests/cases/configResolver.test.js.map +1 -0
  54. package/dist/tests/src/tester.js +2 -2
  55. package/dist/tests/src/tester.js.map +1 -1
  56. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +18 -21
  58. package/src/MasterContainer.ts +17 -15
  59. package/src/bootstrap/Initializer.ts +3 -16
  60. package/src/config/configSchema.ts +44 -21
  61. package/src/config/configTemplate.ts +29 -24
  62. package/src/project/ProjectContainer.ts +23 -4
  63. package/src/project/ProjectContainerResolver.ts +4 -4
  64. package/src/project/ProjectInitializer.ts +4 -3
  65. package/src/project/ProjectSchemaResolver.ts +1 -18
  66. package/src/projectGroup/ProjectGroupContainer.ts +12 -7
  67. package/src/start.ts +24 -17
  68. package/src/tsconfig.json +6 -15
  69. package/src/utils/sentry.ts +45 -35
  70. package/src/utils/serverStartup.ts +46 -10
  71. package/src/utils/serverTermination.ts +5 -6
  72. package/tests/cases/configResolver.test.ts +67 -0
  73. package/tests/src/tester.ts +1 -1
package/src/start.ts CHANGED
@@ -1,28 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createContainer, listenOnProcessTermination, serverConfigSchema, TerminationJob } from './index'
3
+ import {
4
+ createContainer,
5
+ createDefaultLogger, createSentryLoggerHandler,
6
+ listenOnProcessTermination,
7
+ serverConfigSchema,
8
+ TerminationJob,
9
+ } from './index'
4
10
  import loadPlugins from './loadPlugins'
5
- import { initSentry } from './utils'
6
-
7
-
8
11
  import { getServerVersion, isDebugMode, printStartInfo, resolveServerConfig } from './utils/serverStartup'
9
12
 
10
- (async () => {
13
+ const logger = createDefaultLogger()
14
+ process.on('warning', message => {
15
+ logger.warn(message)
16
+ })
17
+ ;(async () => {
18
+ const isDebug = isDebugMode()
11
19
  if (process.env.NODE_HEAPDUMP === 'true') {
12
- // eslint-disable-next-line no-console
13
- console.log('Initializing heapdump')
20
+ logger.info('Initializing heapdump')
14
21
  await import('heapdump')
15
22
  }
16
23
  const terminationJobs: TerminationJob[] = []
17
- listenOnProcessTermination(terminationJobs)
24
+ listenOnProcessTermination(terminationJobs, logger)
18
25
 
19
- const isDebug = isDebugMode()
20
26
  const version = getServerVersion()
21
- printStartInfo({ version, isDebug })
27
+ printStartInfo({ version, isDebug }, logger)
22
28
  const plugins = await loadPlugins()
23
29
  const { serverConfig, projectConfigResolver, tenantConfigResolver } = await resolveServerConfig({ plugins, serverConfigSchema })
24
30
 
25
- initSentry(serverConfig.logging.sentry?.dsn)
31
+ const sentryTransport = createSentryLoggerHandler(serverConfig.logging.sentry?.dsn)
32
+ if (sentryTransport !== null) {
33
+ logger.addHandler(sentryTransport)
34
+ }
26
35
 
27
36
  const container = createContainer({
28
37
  debugMode: isDebug,
@@ -31,19 +40,17 @@ import { getServerVersion, isDebugMode, printStartInfo, resolveServerConfig } fr
31
40
  tenantConfigResolver,
32
41
  plugins,
33
42
  version,
43
+ logger,
34
44
  })
35
45
 
36
46
  const initializedProjects: string[] = await container.initializer.initialize()
37
47
 
38
48
  const httpServer = container.koa.listen(serverConfig.port, () => {
39
- // eslint-disable-next-line no-console
40
- console.log(`Contember API running on http://localhost:${(serverConfig.port)}`)
41
- // eslint-disable-next-line no-console
42
- console.log(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
49
+ logger.info(`Contember API running on http://localhost:${(serverConfig.port)}`)
50
+ logger.info(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
43
51
  })
44
52
  terminationJobs.push(() => new Promise<any>(resolve => httpServer.close(resolve)))
45
53
  })().catch(e => {
46
- // eslint-disable-next-line no-console
47
- console.log(e)
54
+ logger.crit(e)
48
55
  process.exit(1)
49
56
  })
package/src/tsconfig.json CHANGED
@@ -4,9 +4,6 @@
4
4
  "outDir": "../dist/src"
5
5
  },
6
6
  "references": [
7
- {
8
- "path": "../../database-migrations/src"
9
- },
10
7
  {
11
8
  "path": "../../dic/src"
12
9
  },
@@ -16,21 +13,9 @@
16
13
  {
17
14
  "path": "../../schema/src"
18
15
  },
19
- {
20
- "path": "../../schema-utils/src"
21
- },
22
- {
23
- "path": "../../schema-definition/src"
24
- },
25
- {
26
- "path": "../../authorization/src"
27
- },
28
16
  {
29
17
  "path": "../../database/src"
30
18
  },
31
- {
32
- "path": "../../queryable/src"
33
- },
34
19
  {
35
20
  "path": "../../engine-common/src"
36
21
  },
@@ -55,8 +40,14 @@
55
40
  {
56
41
  "path": "../../engine-vimeo-plugin/src"
57
42
  },
43
+ {
44
+ "path": "../../logger/src"
45
+ },
58
46
  {
59
47
  "path": "../../typesafe/src"
48
+ },
49
+ {
50
+ "path": "../../schema-migrations/src"
60
51
  }
61
52
  ]
62
53
  }
@@ -1,46 +1,56 @@
1
1
  import * as Sentry from '@sentry/node'
2
+ import { LoggerRequestSymbol } from '@contember/engine-http'
3
+ import { LogEntry, LoggerHandler, LogLevel, LogLevels } from '@contember/logger'
2
4
 
3
- export const initSentry = (dsn?: string) => {
5
+ export class SentryLoggerHandler implements LoggerHandler {
6
+ constructor(
7
+ private readonly logLevel: LogLevel,
8
+ ) {
9
+ }
10
+
11
+ getMinLevel(): number {
12
+ return this.logLevel.value
13
+ }
14
+
15
+ handle(entry: LogEntry) {
16
+ if (!entry.error || entry.level.value < this.logLevel.value) {
17
+ return
18
+ }
19
+ Sentry.captureException(entry.error, scope => {
20
+ scope.setTag('project', entry.loggerAttributes.project ?? entry.ownAttributes.project)
21
+ scope.setTag('module', entry.loggerAttributes.module ?? entry.ownAttributes.module)
22
+ scope.setLevel('error')
23
+ scope.setUser({
24
+ id: entry.loggerAttributes.user ?? entry.ownAttributes.user,
25
+ })
26
+ scope.setExtra('requestId', entry.loggerAttributes.requestId)
27
+
28
+ scope.addEventProcessor(event => {
29
+ return {
30
+ ...event,
31
+ request: {
32
+ url: entry.loggerAttributes.url ?? entry.ownAttributes.url,
33
+ data: entry.loggerAttributes[LoggerRequestSymbol]?.body,
34
+ },
35
+ }
36
+ })
37
+ return scope
38
+ })
39
+ }
40
+
41
+ close(): void {
42
+ }
43
+ }
44
+
45
+ export const createSentryLoggerHandler = (dsn?: string): null | LoggerHandler => {
4
46
  if (!dsn) {
5
- return
47
+ return null
6
48
  }
7
- // eslint-disable-next-line no-console
8
- console.log(`Intializing sentry with dsn: ${dsn}`)
9
49
  Sentry.init({
10
50
  dsn: dsn,
11
51
  integrations: integrations => {
12
52
  return integrations.filter(integration => integration.name !== 'Console' && integration.name !== 'Http')
13
53
  },
14
54
  })
15
- }
16
-
17
- interface SentryErrorContext {
18
- user: string
19
- body: string
20
- project?: string
21
- url: string
22
- module: string
23
- }
24
-
25
- export const logSentryError = (error: any, context: SentryErrorContext) => {
26
- Sentry.withScope(scope => {
27
- if (context.project) {
28
- scope.setTag('project', context.project)
29
- }
30
- scope.setTag('module', context.module)
31
- scope.setLevel('error')
32
- scope.setUser({
33
- id: context.user,
34
- })
35
- scope.addEventProcessor(event => {
36
- return {
37
- ...event,
38
- request: {
39
- url: context.url,
40
- data: context.body,
41
- },
42
- }
43
- })
44
- Sentry.captureException(error)
45
- })
55
+ return new SentryLoggerHandler(LogLevels.warn)
46
56
  }
@@ -1,7 +1,8 @@
1
- import { join } from 'path'
1
+ import { join } from 'node:path'
2
2
  import { ConfigProcessor, Plugin } from '@contember/engine-plugins'
3
3
  import { ConfigSource, readConfig, ServerConfig } from '../config/config'
4
4
  import { Type } from '@contember/typesafe'
5
+ import { createLogger, JsonStreamLoggerHandler, Logger, LogLevels, PrettyPrintLoggerHandler } from '@contember/logger'
5
6
 
6
7
  export const getServerVersion = (): string => {
7
8
  const packageJsonFile = process.env.CONTEMBER_PACKAGE_JSON || join(__dirname, '../../../package.json')
@@ -12,16 +13,12 @@ export const getServerVersion = (): string => {
12
13
 
13
14
  export const isDebugMode = (): boolean => process.env.NODE_ENV === 'development'
14
15
 
15
- export const printStartInfo = ({ version, isDebug }: { version: string; isDebug: boolean }) => {
16
- // eslint-disable-next-line no-console
17
- console.log(`Starting Contember ${version}`)
18
- // eslint-disable-next-line no-console
19
- console.log(`NODE_ENV is set to ${process.env.NODE_ENV}`)
16
+ export const printStartInfo = ({ version, isDebug }: { version: string; isDebug: boolean }, logger: Logger) => {
17
+ logger.info(`Starting Contember ${version}`)
18
+ logger.info(`NODE_ENV is set to ${process.env.NODE_ENV}`)
20
19
  if (isDebug) {
21
- // eslint-disable-next-line no-console
22
- console.log('Starting Contember in debug mode')
23
- // eslint-disable-next-line no-console
24
- console.log('NEVER USE debug mode in production environment')
20
+ logger.warn('Starting Contember in debug mode')
21
+ logger.warn('NEVER USE debug mode in production environment')
25
22
  }
26
23
  }
27
24
 
@@ -40,3 +37,42 @@ export const resolveServerConfig = async <T extends ServerConfig>({ plugins, ser
40
37
 
41
38
  return await readConfig(configSources, configProcessors, serverConfigSchema)
42
39
  }
40
+
41
+ export const createDefaultLogger = (): Logger => {
42
+ const isDebug = isDebugMode()
43
+ const loggerType = process.env.CONTEMBER_LOGGER_FORMAT ?? (isDebug ? 'pretty' : 'json')
44
+ const stream = process.stderr
45
+ const handler = loggerType === 'pretty'
46
+ ? new PrettyPrintLoggerHandler(stream, {
47
+ formatters: {
48
+ pid: () => undefined,
49
+ loggerId: () => undefined,
50
+ uri: ({ attributes, chalk }) => {
51
+ const color = attributes.status === undefined
52
+ ? chalk.cyan
53
+ : attributes.status < 300
54
+ ? chalk.green
55
+ : chalk.yellowBright
56
+ const timeInfo = attributes.totalTimeMs !== undefined ? ` ${attributes.totalTimeMs}ms` : ''
57
+ const statusInfo = attributes.status !== undefined ? ` [${attributes.status}]` : ''
58
+ return { line: color(`${attributes.method}${statusInfo} ${attributes.uri}${timeInfo} ${chalk.white.dim('#' + attributes.requestId)}`) }
59
+ },
60
+ method: () => undefined,
61
+ requestId: () => undefined,
62
+ timeLabel: () => undefined,
63
+ status: () => undefined,
64
+ totalTimeMs: () => undefined,
65
+ events: ({ formattedValue }) => formattedValue === '[]' ? undefined : formattedValue,
66
+ },
67
+ })
68
+ : new JsonStreamLoggerHandler(stream)
69
+ const logLevel = (process.env.CONTEMBER_LOGGER_LEVEL as keyof typeof LogLevels) ?? 'info'
70
+ return createLogger(handler, {
71
+ pid: process.pid,
72
+ loggerId: Math.random().toString().substring(2),
73
+ }, {
74
+ fingerCrossedOptions: {
75
+ logAlwaysLevel: LogLevels[logLevel],
76
+ },
77
+ })
78
+ }
@@ -1,4 +1,5 @@
1
- import cluster from 'cluster'
1
+ import cluster from 'node:cluster'
2
+ import { Logger } from '@contember/logger'
2
3
 
3
4
  const signals = {
4
5
  SIGHUP: 1,
@@ -6,14 +7,12 @@ const signals = {
6
7
  SIGTERM: 15,
7
8
  }
8
9
  export type TerminationJob = (args: { signal: keyof typeof signals; code: number }) => Promise<void>
9
- export const listenOnProcessTermination = (jobs: TerminationJob[]) => {
10
+ export const listenOnProcessTermination = (jobs: TerminationJob[], logger: Logger) => {
10
11
  for (const [signal, code] of Object.entries(signals)) {
11
12
  process.on(signal, async () => {
12
- // eslint-disable-next-line no-console
13
- console.log(`Process ${process.pid} received a ${signal} signal, executing ${jobs.length} termination jobs`)
13
+ logger.info(`Process ${process.pid} received a ${signal} signal, executing ${jobs.length} termination jobs`)
14
14
  await Promise.allSettled(jobs.map(it => it({ signal: signal as keyof typeof signals, code })))
15
- // eslint-disable-next-line no-console
16
- console.log(cluster.isMaster ? `All terminated, exiting` : 'All terminated, exiting a worker')
15
+ logger.info(cluster.isMaster ? `All terminated, exiting` : 'All terminated, exiting a worker')
17
16
  process.exit(128 + code)
18
17
  })
19
18
  }
@@ -0,0 +1,67 @@
1
+ import { assert, it } from 'vitest'
2
+ import { createTenantConfigResolver } from '../../src/config/tenantConfigResolver'
3
+ import { configTemplate } from '../../src/config/configTemplate'
4
+ import { createProjectConfigResolver } from '../../src/config/projectConfigResolver'
5
+
6
+ const env = {
7
+ DEFAULT_DB_POOL_IDLE_TIMEOUT_MS: '60000',
8
+ DEFAULT_DB_POOL_MAX_CONNECTIONS: '1000',
9
+ DEFAULT_DB_POOL_MAX_CONNECTING: '10',
10
+ DEFAULT_DB_CONNECTION_TIMEOUT_MS: '5000',
11
+ }
12
+ const tenantConfig = {
13
+ db: {
14
+ host: 'c0.cluster-abc.eu-west-1.rds.amazonaws.com',
15
+ port: 5432,
16
+ user: 'u_test_e',
17
+ password: 'PASSWD',
18
+ database: 'p_test_e',
19
+ read: { host: 'c0.cluster-ro-abc.eu-west-1.rds.amazonaws.com' },
20
+ },
21
+ }
22
+ it('resolves tenant config config', () => {
23
+ const tenantResolver = createTenantConfigResolver(env, configTemplate.tenant)
24
+ const resolvedTenantConfig = tenantResolver('test', tenantConfig)
25
+ assert.deepEqual(resolvedTenantConfig, {
26
+ db: {
27
+ host: 'c0.cluster-abc.eu-west-1.rds.amazonaws.com',
28
+ port: 5432,
29
+ user: 'u_test_e',
30
+ password: 'PASSWD',
31
+ database: 'p_test_e',
32
+ connectionTimeoutMs: 5000,
33
+ pool: { maxConnections: 1000, maxConnecting: 10, idleTimeoutMs: 60000 },
34
+ read: { host: 'c0.cluster-ro-abc.eu-west-1.rds.amazonaws.com' },
35
+ }, mailer: {}, credentials: {}, secrets: {},
36
+ })
37
+ })
38
+ it('resolves project config config', () => {
39
+ const tenantResolver = createTenantConfigResolver(env, configTemplate.tenant)
40
+ const resolvedTenantConfig = tenantResolver('test', tenantConfig)
41
+
42
+ const projectConfigResolver = createProjectConfigResolver(env, configTemplate, [])
43
+ const resolvedProjectConfig = projectConfigResolver('test-p', {
44
+ db: {
45
+ useTenantDb: true,
46
+ systemSchema: 'system_test_e',
47
+ }, s3: { prefix: 'test-e/test-e' }, stages: { live: { schema: 'content_test_e_live' } },
48
+ }, {}, resolvedTenantConfig)
49
+
50
+ assert.deepEqual(resolvedProjectConfig as any, {
51
+ name: 'Test p',
52
+ slug: 'test-p',
53
+ stages: [{ name: 'Live', slug: 'live', schema: 'content_test_e_live' }],
54
+ db: {
55
+ host: 'c0.cluster-abc.eu-west-1.rds.amazonaws.com',
56
+ port: 5432,
57
+ user: 'u_test_e',
58
+ password: 'PASSWD',
59
+ database: 'p_test_e',
60
+ connectionTimeoutMs: 5000,
61
+ pool: { maxConnections: 1000, maxConnecting: 10, idleTimeoutMs: 60000 },
62
+ read: { host: 'c0.cluster-ro-abc.eu-west-1.rds.amazonaws.com' },
63
+ useTenantDb: true,
64
+ systemSchema: 'system_test_e',
65
+ },
66
+ })
67
+ })
@@ -1,4 +1,4 @@
1
- import nodeAssert from 'assert'
1
+ import nodeAssert from 'node:assert'
2
2
  import supertest from 'supertest'
3
3
  import { Schema } from '@contember/schema'
4
4
  import {