@contember/engine-server 1.1.0-alpha.1 → 1.1.0-alpha.4

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 (93) hide show
  1. package/alpine.dockerfile +3 -1
  2. package/debian.dockerfile +3 -1
  3. package/dist/src/MasterContainer.d.ts +104 -8
  4. package/dist/src/MasterContainer.d.ts.map +1 -1
  5. package/dist/src/MasterContainer.js +65 -84
  6. package/dist/src/MasterContainer.js.map +1 -1
  7. package/dist/src/bootstrap/Initializer.d.ts +3 -12
  8. package/dist/src/bootstrap/Initializer.d.ts.map +1 -1
  9. package/dist/src/bootstrap/Initializer.js +8 -24
  10. package/dist/src/bootstrap/Initializer.js.map +1 -1
  11. package/dist/src/config/config.d.ts +7 -49
  12. package/dist/src/config/config.d.ts.map +1 -1
  13. package/dist/src/config/config.js +11 -188
  14. package/dist/src/config/config.js.map +1 -1
  15. package/dist/src/config/configSchema.d.ts +84 -0
  16. package/dist/src/config/configSchema.d.ts.map +1 -0
  17. package/dist/src/config/configSchema.js +80 -0
  18. package/dist/src/config/configSchema.js.map +1 -0
  19. package/dist/src/config/configTemplate.d.ts +2 -0
  20. package/dist/src/config/configTemplate.d.ts.map +1 -0
  21. package/dist/src/config/configTemplate.js +62 -0
  22. package/dist/src/config/configTemplate.js.map +1 -0
  23. package/dist/src/config/projectConfigResolver.d.ts +8 -0
  24. package/dist/src/config/projectConfigResolver.d.ts.map +1 -0
  25. package/dist/src/config/projectConfigResolver.js +63 -0
  26. package/dist/src/config/projectConfigResolver.js.map +1 -0
  27. package/dist/src/config/tenantConfigResolver.d.ts +8 -0
  28. package/dist/src/config/tenantConfigResolver.d.ts.map +1 -0
  29. package/dist/src/config/tenantConfigResolver.js +31 -0
  30. package/dist/src/config/tenantConfigResolver.js.map +1 -0
  31. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts +2 -2
  32. package/dist/src/http/CollectHttpMetricsMiddelware.d.ts.map +1 -1
  33. package/dist/src/http/CollectHttpMetricsMiddelware.js +6 -5
  34. package/dist/src/http/CollectHttpMetricsMiddelware.js.map +1 -1
  35. package/dist/src/project/ProjectContainer.d.ts +14 -16
  36. package/dist/src/project/ProjectContainer.d.ts.map +1 -1
  37. package/dist/src/project/ProjectContainer.js +17 -12
  38. package/dist/src/project/ProjectContainer.js.map +1 -1
  39. package/dist/src/project/ProjectContainerResolver.d.ts +12 -10
  40. package/dist/src/project/ProjectContainerResolver.d.ts.map +1 -1
  41. package/dist/src/project/ProjectContainerResolver.js +26 -33
  42. package/dist/src/project/ProjectContainerResolver.js.map +1 -1
  43. package/dist/src/project/{ProjectGroupState.d.ts → ProjectContainerStore.d.ts} +2 -2
  44. package/dist/src/project/ProjectContainerStore.d.ts.map +1 -0
  45. package/dist/src/project/{ProjectGroupState.js → ProjectContainerStore.js} +4 -4
  46. package/dist/src/project/ProjectContainerStore.js.map +1 -0
  47. package/dist/src/project/ProjectInitializer.d.ts +3 -3
  48. package/dist/src/project/ProjectInitializer.d.ts.map +1 -1
  49. package/dist/src/project/ProjectInitializer.js +4 -4
  50. package/dist/src/project/ProjectInitializer.js.map +1 -1
  51. package/dist/src/project/ProjectSchemaResolver.d.ts +3 -3
  52. package/dist/src/project/ProjectSchemaResolver.d.ts.map +1 -1
  53. package/dist/src/project/ProjectSchemaResolver.js +5 -5
  54. package/dist/src/project/ProjectSchemaResolver.js.map +1 -1
  55. package/dist/src/projectGroup/ProjectGroupContainer.d.ts +23 -0
  56. package/dist/src/projectGroup/ProjectGroupContainer.d.ts.map +1 -0
  57. package/dist/src/projectGroup/ProjectGroupContainer.js +64 -0
  58. package/dist/src/projectGroup/ProjectGroupContainer.js.map +1 -0
  59. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts +12 -0
  60. package/dist/src/projectGroup/ProjectGroupContainerResolver.d.ts.map +1 -0
  61. package/dist/src/projectGroup/ProjectGroupContainerResolver.js +36 -0
  62. package/dist/src/projectGroup/ProjectGroupContainerResolver.js.map +1 -0
  63. package/dist/src/start.js +9 -8
  64. package/dist/src/start.js.map +1 -1
  65. package/dist/src/tsconfig.tsbuildinfo +1 -1
  66. package/dist/src/utils/dbMetrics.d.ts +6 -2
  67. package/dist/src/utils/dbMetrics.d.ts.map +1 -1
  68. package/dist/src/utils/dbMetrics.js +13 -16
  69. package/dist/src/utils/dbMetrics.js.map +1 -1
  70. package/dist/tests/cases/http.js +30 -19
  71. package/dist/tests/cases/http.js.map +1 -1
  72. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  73. package/package.json +22 -21
  74. package/src/MasterContainer.ts +145 -204
  75. package/src/bootstrap/Initializer.ts +9 -34
  76. package/src/config/config.ts +23 -234
  77. package/src/config/configSchema.ts +114 -0
  78. package/src/config/configTemplate.ts +58 -0
  79. package/src/config/projectConfigResolver.ts +83 -0
  80. package/src/config/tenantConfigResolver.ts +41 -0
  81. package/src/http/CollectHttpMetricsMiddelware.ts +6 -5
  82. package/src/project/ProjectContainer.ts +25 -18
  83. package/src/project/ProjectContainerResolver.ts +29 -37
  84. package/src/project/{ProjectGroupState.ts → ProjectContainerStore.ts} +1 -1
  85. package/src/project/ProjectInitializer.ts +5 -9
  86. package/src/project/ProjectSchemaResolver.ts +6 -10
  87. package/src/projectGroup/ProjectGroupContainer.ts +120 -0
  88. package/src/projectGroup/ProjectGroupContainerResolver.ts +47 -0
  89. package/src/start.ts +9 -8
  90. package/src/utils/dbMetrics.ts +19 -20
  91. package/tests/cases/http.ts +35 -22
  92. package/dist/src/project/ProjectGroupState.d.ts.map +0 -1
  93. package/dist/src/project/ProjectGroupState.js.map +0 -1
@@ -1,19 +1,32 @@
1
1
  import { Builder } from '@contember/dic'
2
2
  import { Connection } from '@contember/database'
3
3
  import { DatabaseContextFactory, SchemaVersionBuilder } from '@contember/engine-system-api'
4
- import { logSentryError } from '../utils'
5
- import { ModificationHandlerFactory } from '@contember/schema-migrations'
6
4
  import { GraphQlSchemaBuilderFactory, PermissionsByIdentityFactory } from '@contember/engine-content-api'
7
5
  import { GraphQLSchemaContributor, Plugin } from '@contember/engine-plugins'
8
6
  import {
9
- ProjectConfig,
10
- ContentQueryHandlerProvider,
11
7
  ContentSchemaResolver,
12
8
  GraphQlSchemaFactory,
13
- Providers,
9
+ ProjectConfig,
14
10
  ProjectContainer,
11
+ Providers,
15
12
  } from '@contember/engine-http'
16
- import { ContentQueryHandlerFactory } from '@contember/engine-http'
13
+
14
+ export class ProjectContainerFactoryFactory {
15
+ constructor(
16
+ private readonly debug: boolean,
17
+ private readonly plugins: Plugin[],
18
+ private readonly providers: Providers,
19
+ ) {
20
+ }
21
+
22
+ create(schemaVersionBuilder: SchemaVersionBuilder): ProjectContainerFactory {
23
+ return new ProjectContainerFactory(this.debug, this.plugins, schemaVersionBuilder, this.providers)
24
+ }
25
+ }
26
+
27
+ interface ProjectContainerFactoryArgs {
28
+ project: ProjectConfig
29
+ }
17
30
 
18
31
  export class ProjectContainerFactory {
19
32
  constructor(
@@ -23,19 +36,19 @@ export class ProjectContainerFactory {
23
36
  private readonly providers: Providers,
24
37
  ) {}
25
38
 
26
- public createContainer(project: ProjectConfig): ProjectContainer {
27
- return this.createBuilder(project)
39
+ public createContainer(args: ProjectContainerFactoryArgs): ProjectContainer {
40
+ return this.createBuilder(args)
28
41
  .build()
29
42
  .pick(
30
43
  'project',
31
44
  'connection',
32
- 'contentQueryHandlerProvider',
33
45
  'systemDatabaseContextFactory',
34
46
  'contentSchemaResolver',
47
+ 'graphQlSchemaFactory',
35
48
  )
36
49
  }
37
50
 
38
- protected createBuilder(project: ProjectConfig) {
51
+ protected createBuilder({ project }: ProjectContainerFactoryArgs) {
39
52
  return new Builder({})
40
53
  .addService('providers', () =>
41
54
  this.providers)
@@ -45,8 +58,6 @@ export class ProjectContainerFactory {
45
58
  project)
46
59
  .addService('connection', ({ project }) =>
47
60
  new Connection(project.db, { timing: true }))
48
- .addService('modificationHandlerFactory', () =>
49
- new ModificationHandlerFactory(ModificationHandlerFactory.defaultFactoryMap))
50
61
  .addService('graphQlSchemaBuilderFactory', () =>
51
62
  new GraphQlSchemaBuilderFactory())
52
63
  .addService('permissionsByIdentityFactory', ({}) =>
@@ -57,13 +68,9 @@ export class ProjectContainerFactory {
57
68
  .filter((it): it is GraphQLSchemaContributor => !!it)
58
69
  return new GraphQlSchemaFactory(graphQlSchemaBuilderFactory, permissionsByIdentityFactory, contributors)
59
70
  })
60
- .addService('contentQueryHandlerFactory', () =>
61
- new ContentQueryHandlerFactory(project.slug, this.debug, logSentryError))
62
71
  .addService('contentSchemaResolver', ({ schemaVersionBuilder }) =>
63
72
  new ContentSchemaResolver(schemaVersionBuilder))
64
- .addService('contentQueryHandlerProvider', ({ contentSchemaResolver, graphQlSchemaFactory, contentQueryHandlerFactory }) =>
65
- new ContentQueryHandlerProvider(contentSchemaResolver, graphQlSchemaFactory, contentQueryHandlerFactory))
66
- .addService('systemDatabaseContextFactory', ({ connection, providers }) =>
67
- new DatabaseContextFactory(connection.createClient('system', { module: 'system' }), providers))
73
+ .addService('systemDatabaseContextFactory', ({ connection, providers, project }) =>
74
+ new DatabaseContextFactory(connection.createClient(project.db.systemSchema ?? 'system', { module: 'system' }), providers))
68
75
  }
69
76
  }
@@ -1,13 +1,14 @@
1
1
  import { ProjectContainerFactory } from './ProjectContainer'
2
- import { ProjectConfigResolver, ProjectContainer } from '@contember/engine-http'
3
- import { ProjectManager, ProjectWithSecrets } from '@contember/engine-tenant-api'
4
- import { ProjectGroup } from '@contember/engine-tenant-api'
2
+ import { ProjectContainer } from '@contember/engine-http'
3
+ import { DatabaseContext, ProjectManager, ProjectWithSecrets } from '@contember/engine-tenant-api'
5
4
  import { ProjectInitializer as SystemProjectInitializer } from '@contember/engine-system-api'
6
- import { ProjectGroupState } from './ProjectGroupState'
5
+ import { ProjectContainerStore } from './ProjectContainerStore'
7
6
  import { Logger } from '@contember/engine-common'
7
+ import { ProjectConfigResolver } from '../config/projectConfigResolver'
8
+ import { TenantConfig } from '../config/config'
8
9
 
9
10
  export class ProjectContainerResolver {
10
- private projectGroupStates = new WeakMap<ProjectGroup, ProjectGroupState>()
11
+ private projectContainers = new ProjectContainerStore()
11
12
 
12
13
  public readonly onCreate: ((container: ProjectContainer) => void | (() => void))[] = []
13
14
 
@@ -16,13 +17,15 @@ export class ProjectContainerResolver {
16
17
  private readonly projectConfigResolver: ProjectConfigResolver,
17
18
  private readonly projectManager: ProjectManager,
18
19
  private readonly systemProjectInitializer: SystemProjectInitializer,
20
+ private readonly tenantDatabase: DatabaseContext,
21
+ private readonly tenantConfig: TenantConfig,
19
22
  ) {}
20
23
 
21
- public async getAllProjectContainers(projectGroup: ProjectGroup): Promise<ProjectContainer[]> {
22
- const projects = await this.projectManager.getProjects(projectGroup.database)
24
+ public async getAllProjectContainers(): Promise<ProjectContainer[]> {
25
+ const projects = await this.projectManager.getProjects(this.tenantDatabase)
23
26
  return await Promise.all(
24
27
  projects.map(async it => {
25
- const container = await this.getProjectContainer(projectGroup, it.slug)
28
+ const container = await this.getProjectContainer(it.slug)
26
29
  if (!container) {
27
30
  throw new Error('should not happen')
28
31
  }
@@ -31,41 +34,40 @@ export class ProjectContainerResolver {
31
34
  )
32
35
  }
33
36
 
34
- public async getProjectContainer(projectGroup: ProjectGroup, slug: string, alias: boolean = false): Promise<ProjectContainer | undefined> {
35
- let groupState = this.getProjectGroupState(projectGroup)
36
-
37
- const realSlug = groupState.resolveAlias(slug)
37
+ public async getProjectContainer(slug: string, alias: boolean = false): Promise<ProjectContainer | undefined> {
38
+ const realSlug = this.projectContainers.resolveAlias(slug)
38
39
  if (realSlug) {
39
40
  slug = realSlug
40
41
  }
41
- const existing = groupState.getContainer(slug)
42
+ const existing = this.projectContainers.getContainer(slug)
42
43
  if (existing) {
43
44
  const existingAwaited = await existing
44
- const state = await this.projectManager.getProjectState(projectGroup.database, slug, existingAwaited.timestamp)
45
+ const state = await this.projectManager.getProjectState(this.tenantDatabase, slug, existingAwaited.timestamp)
45
46
  if (state === 'valid') {
46
47
  return (await existingAwaited).container
47
48
  }
48
- await this.destroyContainer(projectGroup, slug)
49
+ await this.destroyContainer(slug)
49
50
  if (state === 'not_found') {
50
51
  return undefined
51
52
  }
52
53
  }
53
- const project = await this.projectManager.getProjectWithSecretsBySlug(projectGroup.database, slug, alias)
54
+ const project = await this.projectManager.getProjectWithSecretsBySlug(this.tenantDatabase, slug, alias)
54
55
  if (!project) {
55
56
  return undefined
56
57
  }
57
- const container = await this.createProjectContainer(projectGroup, project)
58
+ const container = await this.createProjectContainer(project)
58
59
  if (slug !== project.slug) {
59
- groupState.setAlias(project.slug, slug)
60
+ this.projectContainers.setAlias(project.slug, slug)
60
61
  }
61
62
  return container
62
63
  }
63
64
 
64
- public async createProjectContainer(projectGroup: ProjectGroup, project: ProjectWithSecrets): Promise<ProjectContainer> {
65
- const projectConfig = this.projectConfigResolver(project.slug, project.config, project.secrets)
66
- const groupState = this.getProjectGroupState(projectGroup)
67
- return (await groupState.fetchContainer(project.slug, async slug => {
68
- const projectContainer = this.projectContainerFactory.createContainer(projectConfig)
65
+ public async createProjectContainer(project: ProjectWithSecrets): Promise<ProjectContainer> {
66
+ const projectConfig = this.projectConfigResolver(project.slug, project.config, project.secrets, this.tenantConfig)
67
+ return (await this.projectContainers.fetchContainer(project.slug, async slug => {
68
+ const projectContainer = this.projectContainerFactory.createContainer({
69
+ project: projectConfig,
70
+ })
69
71
  await this.systemProjectInitializer.initialize(
70
72
  projectContainer.systemDatabaseContextFactory,
71
73
  projectContainer.project,
@@ -81,14 +83,13 @@ export class ProjectContainerResolver {
81
83
  })).container
82
84
  }
83
85
 
84
- public async destroyContainer(projectGroup: ProjectGroup, slug: string): Promise<void> {
85
- const groupState = this.getProjectGroupState(projectGroup)
86
- const existing = groupState.getContainer(slug)
86
+ public async destroyContainer(slug: string): Promise<void> {
87
+ const existing = this.projectContainers.getContainer(slug)
87
88
  if (existing) {
88
89
  const existingAwaited = await existing
89
90
  existingAwaited.cleanups.forEach(it => it())
90
- existingAwaited.container.project.alias?.forEach(it => groupState.removeAlias(it))
91
- groupState.removeContainer(slug)
91
+ existingAwaited.container.project.alias?.forEach(it => this.projectContainers.removeAlias(it))
92
+ this.projectContainers.removeContainer(slug)
92
93
  }
93
94
  process.nextTick(() => {
94
95
  if (global.gc) {
@@ -96,13 +97,4 @@ export class ProjectContainerResolver {
96
97
  }
97
98
  })
98
99
  }
99
-
100
- private getProjectGroupState(projectGroup: ProjectGroup): ProjectGroupState {
101
- let groupState = this.projectGroupStates.get(projectGroup)
102
- if (!groupState) {
103
- groupState = new ProjectGroupState()
104
- this.projectGroupStates.set(projectGroup, new ProjectGroupState())
105
- }
106
- return groupState
107
- }
108
100
  }
@@ -3,7 +3,7 @@ import { PromiseMap } from '@contember/engine-common'
3
3
 
4
4
  type ContainerWithMeta = { container: ProjectContainer; cleanups: (() => void)[]; timestamp: Date }
5
5
 
6
- export class ProjectGroupState {
6
+ export class ProjectContainerStore {
7
7
  private containers = new PromiseMap<string, ContainerWithMeta>()
8
8
  private aliasMapping = new Map<string, string>()
9
9
 
@@ -1,8 +1,4 @@
1
- import {
2
- ProjectGroup,
3
- ProjectInitializer as ProjectInitializerInterface,
4
- ProjectWithSecrets,
5
- } from '@contember/engine-tenant-api'
1
+ import { ProjectInitializer as ProjectInitializerInterface, ProjectWithSecrets } from '@contember/engine-tenant-api'
6
2
  import { ProjectContainerResolver } from './ProjectContainerResolver'
7
3
 
8
4
  export class ProjectInitializer implements ProjectInitializerInterface {
@@ -10,8 +6,8 @@ export class ProjectInitializer implements ProjectInitializerInterface {
10
6
  private readonly projectContainerResolver: ProjectContainerResolver,
11
7
  ) {}
12
8
 
13
- async initializeProject(projectGroup: ProjectGroup, project: ProjectWithSecrets) {
14
- await this.projectContainerResolver.createProjectContainer(projectGroup, project)
9
+ async initializeProject(project: ProjectWithSecrets) {
10
+ await this.projectContainerResolver.createProjectContainer(project)
15
11
  }
16
12
  }
17
13
 
@@ -22,10 +18,10 @@ export class ProjectInitializerProxy implements ProjectInitializerInterface {
22
18
  this.initializer = initializer
23
19
  }
24
20
 
25
- initializeProject(projectGroup: ProjectGroup, project: ProjectWithSecrets) {
21
+ initializeProject(project: ProjectWithSecrets) {
26
22
  if (!this.initializer) {
27
23
  throw new Error('Initializer is not set')
28
24
  }
29
- return this.initializer.initializeProject(projectGroup, project)
25
+ return this.initializer.initializeProject(project)
30
26
  }
31
27
  }
@@ -1,8 +1,4 @@
1
- import {
2
- DatabaseContext,
3
- ProjectGroup,
4
- ProjectSchemaResolver as ProjectSchemaResolverInterface,
5
- } from '@contember/engine-tenant-api'
1
+ import { ProjectSchemaResolver as ProjectSchemaResolverInterface } from '@contember/engine-tenant-api'
6
2
  import { SchemaVersionBuilder } from '@contember/engine-system-api'
7
3
  import { ProjectContainerResolver } from '@contember/engine-http'
8
4
  import { Schema } from '@contember/schema'
@@ -13,12 +9,12 @@ export class ProjectSchemaResolver implements ProjectSchemaResolverInterface {
13
9
  private readonly schemaVersionBuilder: SchemaVersionBuilder,
14
10
  ) {}
15
11
 
16
- async getSchema(projectGroup: ProjectGroup, slug: string) {
17
- const container = await this.projectContainerResolver.getProjectContainer(projectGroup, slug)
12
+ async getSchema(slug: string) {
13
+ const container = await this.projectContainerResolver.getProjectContainer(slug)
18
14
  if (!container) {
19
15
  return undefined
20
16
  }
21
- const db = container.systemDatabaseContextFactory.create(undefined)
17
+ const db = container.systemDatabaseContextFactory.create()
22
18
  return await this.schemaVersionBuilder.buildSchema(db)
23
19
  }
24
20
  }
@@ -30,10 +26,10 @@ export class ProjectSchemaResolverProxy implements ProjectSchemaResolverInterfac
30
26
  this.resolver = resolver
31
27
  }
32
28
 
33
- getSchema(projectGroup: ProjectGroup, projectSlug: string): Promise<Schema | undefined> {
29
+ getSchema(projectSlug: string): Promise<Schema | undefined> {
34
30
  if (!this.resolver) {
35
31
  throw new Error('Resolved is not set')
36
32
  }
37
- return this.resolver.getSchema(projectGroup, projectSlug)
33
+ return this.resolver.getSchema(projectSlug)
38
34
  }
39
35
  }
@@ -0,0 +1,120 @@
1
+ import { Builder } from '@contember/dic'
2
+ import { Connection } from '@contember/database'
3
+ import { TenantContainerFactory } from '@contember/engine-tenant-api'
4
+ import { TenantConfig } from '../config/config'
5
+ import {
6
+ Authenticator,
7
+ CryptoWrapper,
8
+ ProjectGroupContainer,
9
+ Providers,
10
+ SystemGraphQLHandlerFactory,
11
+ TenantGraphQLHandlerFactory,
12
+ } from '@contember/engine-http'
13
+ import {
14
+ ProjectContainerFactoryFactory,
15
+ ProjectContainerResolver,
16
+ ProjectInitializer,
17
+ ProjectInitializerProxy,
18
+ ProjectSchemaResolver,
19
+ ProjectSchemaResolverProxy,
20
+ } from '../project'
21
+ import { SystemContainerFactory } from '@contember/engine-system-api'
22
+ import { ProjectConfigResolver } from '../config/projectConfigResolver'
23
+ import { createSecretKey } from 'crypto'
24
+
25
+ interface ProjectGroupContainerFactoryArgs
26
+ {
27
+ config: TenantConfig
28
+ slug: string | undefined
29
+ }
30
+
31
+ export class ProjectGroupContainerFactory {
32
+ constructor(
33
+ private readonly providers: Providers,
34
+ private readonly systemContainerFactory: SystemContainerFactory,
35
+ private readonly tenantContainerFactory: TenantContainerFactory,
36
+ private readonly projectContainerFactoryFactory: ProjectContainerFactoryFactory,
37
+ private readonly projectConfigResolver: ProjectConfigResolver,
38
+ private readonly tenantGraphQLHandlerFactory: TenantGraphQLHandlerFactory,
39
+ private readonly systemGraphQLHandlerFactory: SystemGraphQLHandlerFactory,
40
+ ) {}
41
+
42
+ public create({ config, slug }: ProjectGroupContainerFactoryArgs): ProjectGroupContainer {
43
+ return new Builder({})
44
+ .addService('slug', () =>
45
+ slug)
46
+ .addService('providers', () =>
47
+ this.providers)
48
+ .addService('tenantConnection', (): Connection.ConnectionType =>
49
+ new Connection(config.db, {}))
50
+ .addService('projectSchemaResolver', () =>
51
+ new ProjectSchemaResolverProxy())
52
+ .addService('projectInitializer', () =>
53
+ new ProjectInitializerProxy())
54
+ .addService('tenantContainer', ({ tenantConnection, projectSchemaResolver, projectInitializer }) => {
55
+ const encryptionKey = config.secrets.encryptionKey
56
+ ? createSecretKey(Buffer.from(config.secrets.encryptionKey, 'hex'))
57
+ : undefined
58
+
59
+ const cryptoWrapper = new CryptoWrapper(encryptionKey)
60
+ return this.tenantContainerFactory.create({
61
+ connection: tenantConnection,
62
+ mailOptions: config.mailer,
63
+ tenantCredentials: config.credentials,
64
+ projectInitializer,
65
+ projectSchemaResolver,
66
+ cryptoProviders: {
67
+ decrypt: cryptoWrapper.decrypt.bind(cryptoWrapper),
68
+ encrypt: cryptoWrapper.encrypt.bind(cryptoWrapper),
69
+ },
70
+ })
71
+ })
72
+ .addService('tenantDatabase', ({ tenantContainer }) =>
73
+ tenantContainer.databaseContext)
74
+ .addService('identityFetcher', ({ tenantContainer: { identityFetcher } }) =>
75
+ identityFetcher)
76
+ .addService('systemContainer', ({ identityFetcher }) =>
77
+ this.systemContainerFactory.create({ identityFetcher }))
78
+ .addService('schemaVersionBuilder', ({ systemContainer }) =>
79
+ systemContainer.schemaVersionBuilder)
80
+ .addService('projectContainerFactory', ({ schemaVersionBuilder }) =>
81
+ this.projectContainerFactoryFactory.create(schemaVersionBuilder))
82
+ .addService('tenantProjectManager', ({ tenantContainer }) =>
83
+ tenantContainer.projectManager)
84
+ .addService('projectContainerResolver', ({ projectContainerFactory, tenantProjectManager, systemContainer, tenantContainer }) =>
85
+ new ProjectContainerResolver(
86
+ projectContainerFactory,
87
+ this.projectConfigResolver,
88
+ tenantProjectManager,
89
+ systemContainer.projectInitializer,
90
+ tenantContainer.databaseContext,
91
+ config,
92
+ ))
93
+
94
+ .setupService('projectSchemaResolver', (it, { projectContainerResolver, schemaVersionBuilder }) => {
95
+ it.setResolver(new ProjectSchemaResolver(projectContainerResolver, schemaVersionBuilder))
96
+ })
97
+ .setupService('projectInitializer', (it, { projectContainerResolver }) => {
98
+ it.setInitializer(new ProjectInitializer(projectContainerResolver))
99
+ })
100
+ .addService('tenantGraphQLHandler', ({ tenantContainer }) =>
101
+ this.tenantGraphQLHandlerFactory.create(tenantContainer.resolvers))
102
+ .addService('systemGraphQLHandler', ({ systemContainer }) =>
103
+ this.systemGraphQLHandlerFactory.create(systemContainer.systemResolversFactory))
104
+ .addService('authenticator', ({ tenantDatabase, tenantContainer }) =>
105
+ new Authenticator(tenantDatabase, tenantContainer.apiKeyManager))
106
+ .build()
107
+ .pick(
108
+ 'projectContainerResolver',
109
+ 'projectSchemaResolver',
110
+ 'projectInitializer',
111
+ 'systemContainer',
112
+ 'systemGraphQLHandler',
113
+ 'tenantContainer',
114
+ 'tenantDatabase',
115
+ 'authenticator',
116
+ 'tenantGraphQLHandler',
117
+ 'slug',
118
+ )
119
+ }
120
+ }
@@ -0,0 +1,47 @@
1
+ import { TenantConfigResolver } from '../config/tenantConfigResolver'
2
+ import {
3
+ ProjectGroupContainer,
4
+ ProjectGroupContainerResolver as ProjectGroupContainerResolverInterface,
5
+ } from '@contember/engine-http'
6
+ import { ProjectGroupContainerFactory } from './ProjectGroupContainer'
7
+ import { PromiseMap } from '@contember/engine-common'
8
+ import { isDeepStrictEqual } from 'util'
9
+
10
+ interface ContainerWithMeta {
11
+ container: ProjectGroupContainer
12
+ cleanups: (() => void)[]
13
+ inputConfig: any
14
+ }
15
+
16
+ export class ProjectGroupContainerResolver implements ProjectGroupContainerResolverInterface {
17
+ private containers = new PromiseMap<string | undefined, ContainerWithMeta>()
18
+
19
+ public readonly onCreate: ((container: ProjectGroupContainer, slug: string | undefined) => void | (() => void))[] = []
20
+
21
+ constructor(
22
+ private readonly configResolver: TenantConfigResolver,
23
+ private readonly containerFactory: ProjectGroupContainerFactory,
24
+ ) {}
25
+
26
+ public async getProjectGroupContainer(slug: string | undefined, config: any = {}): Promise<ProjectGroupContainer> {
27
+ const existing = this.containers.get(slug)
28
+ if (existing) {
29
+ const existingAwaited = await existing
30
+ if (isDeepStrictEqual(config, existingAwaited.inputConfig)) {
31
+ return existingAwaited.container
32
+ }
33
+ existingAwaited.cleanups.forEach(it => it())
34
+ this.containers.delete(slug)
35
+ }
36
+ return (await this.containers.fetch(slug, async slug => {
37
+ const container = this.containerFactory.create({
38
+ config: this.configResolver(slug, config),
39
+ slug,
40
+ })
41
+ const cleanups = this.onCreate.map(it => it(container, slug) || (() => null))
42
+ // eslint-disable-next-line no-console
43
+ await container.tenantContainer.migrationsRunner.run(console.log)
44
+ return { container, inputConfig: config, cleanups }
45
+ })).container
46
+ }
47
+ }
package/src/start.ts CHANGED
@@ -68,13 +68,13 @@ const createServerTerminator = (): Server[] => {
68
68
  }
69
69
  }
70
70
 
71
- const { config, projectConfigResolver } = await readConfig(configSources, configProcessors)
71
+ const { serverConfig, projectConfigResolver, tenantConfigResolver } = await readConfig(configSources, configProcessors)
72
72
 
73
73
  if (process.argv[2] === 'validate') {
74
74
  process.exit(0)
75
75
  }
76
- 'sentry' in config.server.logging && initSentry(config.server.logging.sentry?.dsn)
77
- const workerConfig = config.server.workerCount || 1
76
+ 'sentry' in serverConfig.logging && initSentry(serverConfig.logging.sentry?.dsn)
77
+ const workerConfig = serverConfig.workerCount || 1
78
78
 
79
79
  const workerCount = workerConfig === 'auto' ? os.cpus().length : Number(workerConfig)
80
80
  const isClusterMode = workerCount > 1
@@ -83,8 +83,9 @@ const createServerTerminator = (): Server[] => {
83
83
 
84
84
  const container = createContainer({
85
85
  debugMode: isDebug,
86
- config,
86
+ serverConfig,
87
87
  projectConfigResolver,
88
+ tenantConfigResolver,
88
89
  plugins,
89
90
  processType,
90
91
  version,
@@ -92,7 +93,7 @@ const createServerTerminator = (): Server[] => {
92
93
 
93
94
  let initializedProjects: string[] = []
94
95
  if (cluster.isMaster) {
95
- const monitoringPort = config.server.monitoringPort
96
+ const monitoringPort = serverConfig.monitoringPort
96
97
  const terminator = createServerTerminator()
97
98
  terminator.push(
98
99
  container.monitoringKoa.listen(monitoringPort, () => {
@@ -100,14 +101,14 @@ const createServerTerminator = (): Server[] => {
100
101
  console.log(`Monitoring running on http://localhost:${monitoringPort}`)
101
102
  }),
102
103
  )
103
- if (!config.server.projectGroup) {
104
+ if (!serverConfig.projectGroup) {
104
105
  initializedProjects = await container.initializer.initialize()
105
106
  }
106
107
  }
107
108
 
108
- const port = config.server.port
109
+ const port = serverConfig.port
109
110
  const printStarted = () => {
110
- if (config.server.projectGroup) {
111
+ if (serverConfig.projectGroup) {
111
112
  // eslint-disable-next-line no-console
112
113
  console.log('Contember Cloud running')
113
114
  } else {
@@ -1,10 +1,12 @@
1
1
  import prom from 'prom-client'
2
2
  import { Connection, EventManager } from '@contember/database'
3
3
 
4
+ const labelNames = ['contember_project' as const, 'contember_module' as const, 'contember_project_group' as const]
5
+ type LabelNames = typeof labelNames[number]
6
+ type Labels = { [K in LabelNames]: string }
4
7
  type DatabaseMetricsEntry = {
5
8
  connection: Connection.PoolStatusProvider & Connection.Queryable
6
- module: string
7
- project: string
9
+ labels: Labels
8
10
  }
9
11
  type Unregistrar = () => void
10
12
  type DatabaseMetricsRegistrar = (entry: DatabaseMetricsEntry) => Unregistrar
@@ -14,17 +16,14 @@ export const createDbMetricsRegistrar = (registry: prom.Registry): DatabaseMetri
14
16
  const entries = new Set<DatabaseMetricsEntry>()
15
17
  const collector = () => {
16
18
  for (const entry of entries) {
17
- dbPoolCollectorInner(entry.connection, { contember_module: entry.module, contember_project: entry.project })
19
+ dbPoolCollectorInner(entry.connection, entry.labels)
18
20
  }
19
21
  }
20
22
  registry.registerCollector(collector)
21
23
  const sqlMetricsRegistrar = createSqlMetricsRegistrar(registry)
22
24
  return entry => {
23
25
  entries.add(entry)
24
- const sqlMetricsUnregistrar = sqlMetricsRegistrar(entry.connection, {
25
- contember_module: entry.module,
26
- contember_project: entry.project,
27
- })
26
+ const sqlMetricsUnregistrar = sqlMetricsRegistrar(entry.connection, entry.labels)
28
27
  return () => {
29
28
  entries.delete(entry)
30
29
  sqlMetricsUnregistrar()
@@ -33,33 +32,33 @@ export const createDbMetricsRegistrar = (registry: prom.Registry): DatabaseMetri
33
32
  }
34
33
 
35
34
  const createSingleDbPoolMetricsCollector = (registry: prom.Registry) => {
36
- const labelNames = ['contember_project', 'contember_module']
35
+
37
36
  const totalCount = new prom.Gauge({
38
37
  registers: [registry],
39
38
  name: `contember_db_pool_total_count`,
40
39
  help: 'The total number of clients existing within the pool by contember_module (tenant or content; system is not used since it uses the same connection) and contember_project for contember_module=content.',
41
- labelNames: labelNames,
40
+ labelNames,
42
41
  })
43
42
  const idleCount = new prom.Gauge({
44
43
  registers: [registry],
45
44
  name: `contember_db_pool_idle_count`,
46
45
  help: 'The number of clients which are not checked out but are currently idle in the pool by contember_module (tenant or content; system is not used since it uses the same connection) and contember_project for contember_module=content.',
47
- labelNames: labelNames,
46
+ labelNames,
48
47
  })
49
48
  const waitingCount = new prom.Gauge({
50
49
  registers: [registry],
51
50
  name: `contember_db_pool_waiting_count`,
52
51
  help: 'The number of queued requests waiting on a client when all clients are checked out. It can be helpful to monitor this number to see if you need to adjust the size of the pool by contember_module (tenant or content; system is not used since it uses the same connection) and contember_project for contember_module=content.',
53
- labelNames: labelNames,
52
+ labelNames,
54
53
  })
55
54
  const maxCount = new prom.Gauge({
56
55
  registers: [registry],
57
56
  name: `contember_db_pool_max_count`,
58
57
  help: 'Maximum number of clients the pool should contain by contember_module (tenant or content; system is not used since it uses the same connection) and contember_project for contember_module=content.',
59
- labelNames: labelNames,
58
+ labelNames,
60
59
  })
61
60
 
62
- return (db: Connection.PoolStatusProvider, labels: { contember_module: string; contember_project: string }) => {
61
+ return (db: Connection.PoolStatusProvider, labels: Labels) => {
63
62
  const poolInfo = db.getPoolStatus()
64
63
  totalCount.set(labels, poolInfo.totalCount)
65
64
  idleCount.set(labels, poolInfo.idleCount)
@@ -69,20 +68,20 @@ const createSingleDbPoolMetricsCollector = (registry: prom.Registry) => {
69
68
  }
70
69
  type SqlMetricsRegistrar = (
71
70
  connection: Connection.Queryable,
72
- labels: { contember_module?: string; contember_project?: string },
71
+ labels: Labels,
73
72
  ) => Unregistrar
74
73
  const createSqlMetricsRegistrar = (registry: prom.Registry): SqlMetricsRegistrar => {
75
74
  const sqlDuration = new prom.Histogram({
76
75
  name: 'contember_sql_duration_ms',
77
76
  help: 'Executed SQL queries by contember_module (system, tenant, content or unknown) and contember_project (or "unknown" for unknown project)',
78
77
  registers: [registry],
79
- labelNames: ['contember_project', 'contember_module'],
78
+ labelNames,
80
79
  buckets: [1, 2, 5, 10, 25, 50, 100, 250, 500, 1000],
81
80
  })
82
81
  const sqlErrorRate = new prom.Counter({
83
82
  name: 'contember_sql_error_count',
84
83
  help: 'Failed SQL queries by contember_module (system, tenant, content or unknown) and contember_project (or "unknown" for unknown project)',
85
- labelNames: ['contember_project', 'contember_module'],
84
+ labelNames,
86
85
  registers: [registry],
87
86
  })
88
87
 
@@ -90,16 +89,16 @@ const createSqlMetricsRegistrar = (registry: prom.Registry): SqlMetricsRegistrar
90
89
  const queryEndCallback: EventManager.ListenerTypes[EventManager.Event.queryEnd] = ({ meta }, { timing }) => {
91
90
  sqlDuration.observe(
92
91
  {
93
- contember_module: labels.contember_module || meta.module || 'unknown',
94
- contember_project: labels.contember_project || 'unknown',
92
+ ...labels,
93
+ contember_module: meta.module || labels.contember_module,
95
94
  },
96
95
  timing ? timing.selfDuration / 1000 : 0,
97
96
  )
98
97
  }
99
98
  const queryErrorCallback: EventManager.ListenerTypes[EventManager.Event.queryError] = ({ meta }) => {
100
99
  sqlErrorRate.inc({
101
- contember_module: labels.contember_module || meta.module || 'unknown',
102
- contember_project: labels.contember_project || 'unknown',
100
+ ...labels,
101
+ contember_module: meta.module || labels.contember_module,
103
102
  })
104
103
  }
105
104
  connection.eventManager.on(EventManager.Event.queryEnd, queryEndCallback)