@platformatic/gateway 3.0.0-alpha.6

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 (49) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +13 -0
  3. package/config.d.ts +1000 -0
  4. package/eslint.config.js +12 -0
  5. package/index.d.ts +58 -0
  6. package/index.js +34 -0
  7. package/lib/application.js +186 -0
  8. package/lib/capability.js +89 -0
  9. package/lib/commands/index.js +15 -0
  10. package/lib/commands/openapi-fetch-schemas.js +48 -0
  11. package/lib/errors.js +18 -0
  12. package/lib/gateway-hook.js +66 -0
  13. package/lib/generator.js +127 -0
  14. package/lib/graphql-fetch.js +83 -0
  15. package/lib/graphql-generator.js +33 -0
  16. package/lib/graphql.js +29 -0
  17. package/lib/metrics.js +12 -0
  18. package/lib/not-host-constraints.js +31 -0
  19. package/lib/openapi-composer.js +101 -0
  20. package/lib/openapi-config-schema.js +89 -0
  21. package/lib/openapi-generator.js +215 -0
  22. package/lib/openapi-load-config.js +31 -0
  23. package/lib/openapi-modifier.js +131 -0
  24. package/lib/openapi-scalar.js +22 -0
  25. package/lib/proxy.js +266 -0
  26. package/lib/root.js +75 -0
  27. package/lib/schema.js +258 -0
  28. package/lib/upgrade.js +20 -0
  29. package/lib/utils.js +16 -0
  30. package/lib/versions/2.0.0.js +9 -0
  31. package/lib/versions/3.0.0.js +24 -0
  32. package/package.json +83 -0
  33. package/public/images/dark_mode.svg +3 -0
  34. package/public/images/ellipse.svg +21 -0
  35. package/public/images/external-link.svg +5 -0
  36. package/public/images/favicon.ico +0 -0
  37. package/public/images/graphiql.svg +10 -0
  38. package/public/images/graphql.svg +10 -0
  39. package/public/images/light_mode.svg +11 -0
  40. package/public/images/openapi.svg +13 -0
  41. package/public/images/platformatic-logo-dark.svg +30 -0
  42. package/public/images/platformatic-logo-light.svg +30 -0
  43. package/public/images/reverse-proxy.svg +8 -0
  44. package/public/images/triangle_dark.svg +3 -0
  45. package/public/images/triangle_light.svg +3 -0
  46. package/public/index.html +253 -0
  47. package/public/index.njk +101 -0
  48. package/public/main.css +244 -0
  49. package/schema.json +3779 -0
@@ -0,0 +1,12 @@
1
+ import neostandard from 'neostandard'
2
+
3
+ export default neostandard({
4
+ ts: true,
5
+ ignores: [
6
+ ...neostandard.resolveIgnoresFromGitignore(),
7
+ 'test/tmp/**/*',
8
+ 'test/fixtures/*/dist/**/*',
9
+ '**/dist/*',
10
+ 'tmp/**/*'
11
+ ]
12
+ })
package/index.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { BaseCapability } from '@platformatic/basic'
2
+ import { BaseGenerator } from '@platformatic/generators'
3
+ import {
4
+ ApplicationCapability,
5
+ ServerInstance as ApplicationInstance,
6
+ Configuration,
7
+ ConfigurationOptions
8
+ } from '@platformatic/service'
9
+ import { JSONSchemaType } from 'ajv'
10
+ import { FastifyError, FastifyInstance } from 'fastify'
11
+ import { PlatformaticGatewayConfig } from './config'
12
+
13
+ export { PlatformaticService } from '@platformatic/service'
14
+ export { PlatformaticGatewayConfig } from './config'
15
+
16
+ export type GatewayCapability = ApplicationCapability<PlatformaticGatewayConfig>
17
+
18
+ export type ServerInstance = ApplicationInstance<PlatformaticGatewayConfig>
19
+
20
+ type GatewayConfiguration = Configuration<PlatformaticGatewayConfig>
21
+
22
+ export declare function loadConfiguration (
23
+ root: string | PlatformaticServiceConfig,
24
+ source?: string | PlatformaticServiceConfig,
25
+ context?: ConfigurationOptions
26
+ ): Promise<GatewayConfiguration>
27
+
28
+ export function create (
29
+ root: string,
30
+ source?: string | PlatformaticGatewayConfig,
31
+ context?: ConfigurationOptions
32
+ ): Promise<GatewayCapability>
33
+
34
+ export declare function platformaticGateway (app: FastifyInstance, capability: BaseCapability): Promise<void>
35
+
36
+ export class Generator extends BaseGenerator.BaseGenerator {}
37
+
38
+ export declare const packageJson: Record<string, unknown>
39
+
40
+ export declare const schema: JSONSchemaType<PlatformaticGatewayConfig>
41
+
42
+ export declare const schemaComponents: {
43
+ openApiApplication: JSONSchemaType<object>
44
+ entityResolver: JSONSchemaType<object>
45
+ entities: JSONSchemaType<object>
46
+ graphqlApplication: JSONSchemaType<object>
47
+ graphqlGatewayOptions: JSONSchemaType<object>
48
+ gateway: JSONSchemaType<object>
49
+ types: JSONSchemaType<object>
50
+ }
51
+
52
+ export declare const version: string
53
+
54
+ export function FastifyInstanceIsAlreadyListeningError (): FastifyError
55
+ export function FailedToFetchOpenAPISchemaError (): FastifyError
56
+ export function ValidationErrors (): FastifyError
57
+ export function PathAlreadyExistsError (): FastifyError
58
+ export function CouldNotReadOpenAPIConfigError (): FastifyError
package/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import { resolve, validationOptions } from '@platformatic/basic'
2
+ import { kMetadata, loadConfiguration as utilsLoadConfiguration } from '@platformatic/foundation'
3
+ import { transform } from '@platformatic/service'
4
+ import { GatewayCapability } from './lib/capability.js'
5
+ import { schema } from './lib/schema.js'
6
+ import { upgrade } from './lib/upgrade.js'
7
+
8
+ export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
9
+ const { root, source } = await resolve(configOrRoot, sourceOrConfig, 'gateway')
10
+
11
+ return utilsLoadConfiguration(source, context?.schema ?? schema, {
12
+ validationOptions,
13
+ transform,
14
+ upgrade,
15
+ replaceEnv: true,
16
+ root,
17
+ ...context
18
+ })
19
+ }
20
+
21
+ export async function create (configOrRoot, sourceOrConfig, context) {
22
+ const config = await loadConfiguration(configOrRoot, sourceOrConfig, context)
23
+ return new GatewayCapability(config[kMetadata].root, config, context)
24
+ }
25
+
26
+ export const skipTelemetryHooks = true
27
+
28
+ export { platformaticGateway } from './lib/application.js'
29
+ export { GatewayCapability } from './lib/capability.js'
30
+ export * from './lib/commands/index.js'
31
+ export * from './lib/errors.js'
32
+ export * as errors from './lib/errors.js'
33
+ export { Generator } from './lib/generator.js'
34
+ export { packageJson, schema, schemaComponents, version } from './lib/schema.js'
@@ -0,0 +1,186 @@
1
+ import { isKeyEnabled } from '@platformatic/foundation'
2
+ import { platformaticService } from '@platformatic/service'
3
+ import deepEqual from 'fast-deep-equal'
4
+ import { fetchOpenApiSchema } from './commands/openapi-fetch-schemas.js'
5
+ import { gatewayHook } from './gateway-hook.js'
6
+ import { fetchGraphqlSubgraphs, isSameGraphqlSchema } from './graphql-fetch.js'
7
+ import { graphqlGenerator } from './graphql-generator.js'
8
+ import { graphql } from './graphql.js'
9
+ import { openApiGateway, openApiGenerator } from './openapi-generator.js'
10
+ import { proxy } from './proxy.js'
11
+ import { isFetchable } from './utils.js'
12
+
13
+ const kITC = Symbol.for('plt.runtime.itc')
14
+ const EXPERIMENTAL_GRAPHQL_GATEWAY_FEATURE_MESSAGE = 'graphql composer is an experimental feature'
15
+
16
+ async function detectApplicationsUpdate ({ app, applications, fetchOpenApiSchema, fetchGraphqlSubgraphs }) {
17
+ let changed
18
+
19
+ const graphqlApplications = []
20
+ // assumes applications here are fetchable
21
+ for (const application of applications) {
22
+ const { id, origin, openapi, graphql } = application
23
+
24
+ if (openapi) {
25
+ const currentSchema = app.openApiSchemas.find(schema => schema.id === id)?.originSchema || null
26
+
27
+ let fetchedSchema = null
28
+ try {
29
+ fetchedSchema = await fetchOpenApiSchema({ origin, openapi })
30
+ } catch (err) {
31
+ app.log.error({ err }, 'failed to fetch schema (watch) for application ' + id)
32
+ }
33
+
34
+ if (!changed && !deepEqual(fetchedSchema, currentSchema)) {
35
+ changed = true
36
+ // it stops at first schema difference since all the schemas will be updated on reload
37
+ break
38
+ }
39
+ }
40
+
41
+ if (graphql) {
42
+ graphqlApplications.push(application)
43
+ }
44
+ }
45
+
46
+ if (!changed && graphqlApplications.length > 0) {
47
+ const graphqlSupergraph = await fetchGraphqlSubgraphs(graphqlApplications, app.graphqlGatewayOptions, app)
48
+ if (!isSameGraphqlSchema(graphqlSupergraph, app.graphqlSupergraph)) {
49
+ changed = true
50
+ app.graphqlSupergraph = graphqlSupergraph
51
+ }
52
+ }
53
+
54
+ return changed
55
+ }
56
+
57
+ /**
58
+ * poll applications to detect changes, every `opts.gateway.refreshTimeout`
59
+ * polling is disabled on refreshTimeout = 0
60
+ * or there are no network openapi nor graphql remote applications (the applications are from file or they don't have a schema/graph to fetch)
61
+ */
62
+ async function watchApplications (app, { config, capability }) {
63
+ const { applications, refreshTimeout } = config.gateway
64
+ if (refreshTimeout < 1) {
65
+ return
66
+ }
67
+
68
+ const watching = applications.filter(isFetchable)
69
+ if (watching.length < 1) {
70
+ return
71
+ }
72
+
73
+ if (!globalThis[Symbol.for('plt.runtime.id')]) {
74
+ app.log.warn('Watching applications is only supported when running within a Platformatic Runtime.')
75
+ return
76
+ }
77
+
78
+ capability.emit('watch:start')
79
+ app.log.info({ applications: watching }, 'start watching applications')
80
+
81
+ const timer = setInterval(async () => {
82
+ try {
83
+ if (await detectApplicationsUpdate({ app, applications: watching, fetchOpenApiSchema, fetchGraphqlSubgraphs })) {
84
+ clearInterval(timer)
85
+ app.log.info('detected applications changes, restarting ...')
86
+
87
+ globalThis[Symbol.for('plt.runtime.itc')].notify('changed')
88
+ }
89
+ } catch (error) {
90
+ app.log.error(
91
+ {
92
+ err: {
93
+ message: error.message,
94
+ stack: error.stack
95
+ }
96
+ },
97
+ 'failed to get applications info'
98
+ )
99
+ }
100
+ }, refreshTimeout).unref()
101
+
102
+ app.addHook('onClose', async () => {
103
+ clearInterval(timer)
104
+ })
105
+ }
106
+
107
+ export async function ensureApplications (gatewayId, config) {
108
+ if (config.gateway?.applications?.length) {
109
+ return
110
+ }
111
+
112
+ gatewayId ??= globalThis.platformatic?.applicationId
113
+ config.gateway ??= {}
114
+ config.gateway.applications ??= []
115
+
116
+ // When no applications are defined, all applications are exposed in the gateway
117
+ const applications = await globalThis[kITC]?.send('listApplications')
118
+
119
+ if (applications) {
120
+ config.gateway.applications = applications
121
+ .filter(id => id !== gatewayId) // Remove ourself
122
+ .map(id => ({ id, proxy: { prefix: `/${id}` } }))
123
+ }
124
+ }
125
+
126
+ export async function platformaticGateway (app, capability) {
127
+ const config = await capability.getConfig()
128
+ let hasGraphqlApplications, hasOpenapiApplications
129
+
130
+ // When no applications are specified, get the list from the runtime.
131
+ await ensureApplications(capability.applicationId, config)
132
+
133
+ const { applications } = config.gateway
134
+
135
+ for (const application of applications) {
136
+ if (!application.origin) {
137
+ application.origin = `http://${application.id}.plt.local`
138
+ }
139
+ if (application.openapi && !hasOpenapiApplications) {
140
+ hasOpenapiApplications = true
141
+ }
142
+ if (application.graphql && !hasGraphqlApplications) {
143
+ hasGraphqlApplications = true
144
+ }
145
+ }
146
+
147
+ await app.register(gatewayHook)
148
+
149
+ let generatedComposedOpenAPI = null
150
+ if (hasOpenapiApplications) {
151
+ generatedComposedOpenAPI = await openApiGenerator(app, config.gateway)
152
+ }
153
+
154
+ if (isKeyEnabled('healthCheck', config.server)) {
155
+ if (typeof config.server.healthCheck !== 'object') {
156
+ config.server.healthCheck = {}
157
+ }
158
+
159
+ config.server.healthCheck.fn = capability.isHealthy.bind(capability)
160
+ }
161
+
162
+ await app.register(proxy, { ...config.gateway, capability, context: capability.context })
163
+
164
+ await platformaticService(app, capability)
165
+
166
+ if (generatedComposedOpenAPI) {
167
+ await app.register(openApiGateway, { opts: config.gateway, generated: generatedComposedOpenAPI })
168
+ }
169
+
170
+ if (hasGraphqlApplications) {
171
+ app.log.warn(EXPERIMENTAL_GRAPHQL_GATEWAY_FEATURE_MESSAGE)
172
+ app.register(graphql, config.gateway)
173
+ await app.register(graphqlGenerator, config.gateway)
174
+ }
175
+
176
+ if (!app.hasRoute({ url: '/', method: 'GET' }) && !app.hasRoute({ url: '/*', method: 'GET' })) {
177
+ const rootHandler = await import('./root.js')
178
+ await app.register(rootHandler.default, config)
179
+ }
180
+
181
+ if (!capability.context?.isProduction) {
182
+ await watchApplications(app, { config, capability, context: capability.context })
183
+ }
184
+ }
185
+
186
+ platformaticGateway[Symbol.for('skip-override')] = true
@@ -0,0 +1,89 @@
1
+ import { kMetadata, replaceEnv } from '@platformatic/foundation'
2
+ import { ServiceCapability } from '@platformatic/service'
3
+ import { ensureApplications, platformaticGateway } from './application.js'
4
+ import { notHostConstraints } from './not-host-constraints.js'
5
+ import { packageJson } from './schema.js'
6
+
7
+ const kITC = Symbol.for('plt.runtime.itc')
8
+
9
+ export class GatewayCapability extends ServiceCapability {
10
+ #meta
11
+ #dependencies
12
+
13
+ constructor (root, config, context) {
14
+ super(root, config, context)
15
+ this.type = 'gateway'
16
+ this.version = packageJson.version
17
+
18
+ this.applicationFactory = this.context.applicationFactory ?? platformaticGateway
19
+ this.fastifyOptions ??= {}
20
+ this.fastifyOptions.routerOptions ??= {}
21
+ this.fastifyOptions.routerOptions.constraints = { notHost: notHostConstraints }
22
+ }
23
+
24
+ async getBootstrapDependencies () {
25
+ await ensureApplications(this.applicationId, this.config)
26
+
27
+ const composedApplications = this.config.gateway?.applications
28
+ const dependencies = []
29
+
30
+ if (Array.isArray(composedApplications)) {
31
+ dependencies.push(
32
+ ...(await Promise.all(
33
+ composedApplications.map(async application => {
34
+ return this.#parseDependency(application.id, application.origin)
35
+ })
36
+ ))
37
+ )
38
+ }
39
+
40
+ this.#dependencies = dependencies
41
+ return this.#dependencies
42
+ }
43
+
44
+ registerMeta (meta) {
45
+ this.#meta = Object.assign(this.#meta ?? {}, meta)
46
+ }
47
+
48
+ async getMeta () {
49
+ const applicationMeta = super.getMeta()
50
+ const gatewayMeta = this.#meta ? { gateway: this.#meta } : undefined
51
+
52
+ return {
53
+ ...applicationMeta,
54
+ ...gatewayMeta
55
+ }
56
+ }
57
+
58
+ async isHealthy () {
59
+ // If no dependencies (still booting), assume healthy
60
+ if (this.#dependencies) {
61
+ const composedApplications = this.#dependencies.map(dep => dep.id)
62
+ const workers = await globalThis[kITC].send('getWorkers')
63
+
64
+ for (const worker of Object.values(workers)) {
65
+ if (composedApplications.includes(worker.application) && !worker.status.startsWith('start')) {
66
+ globalThis[kITC].notify('event', { event: 'unhealthy' })
67
+ return false
68
+ }
69
+ }
70
+ }
71
+
72
+ globalThis[kITC].notify('event', { event: 'healthy' })
73
+ return true
74
+ }
75
+
76
+ async #parseDependency (id, urlString) {
77
+ let url = `http://${id}.plt.local`
78
+
79
+ if (urlString) {
80
+ const remoteUrl = await replaceEnv(urlString, this.config[kMetadata].env)
81
+
82
+ if (remoteUrl) {
83
+ url = remoteUrl
84
+ }
85
+ }
86
+
87
+ return { id, url, local: url.endsWith('.plt.local') }
88
+ }
89
+ }
@@ -0,0 +1,15 @@
1
+ import { fetchOpenApiSchemas } from './openapi-fetch-schemas.js'
2
+
3
+ export function createCommands (id) {
4
+ return {
5
+ commands: {
6
+ [`${id}:fetch-openapi-schemas`]: fetchOpenApiSchemas
7
+ },
8
+ help: {
9
+ [`${id}:fetch-openapi-schemas`]: {
10
+ usage: `${id}:fetch-openapi-schemas`,
11
+ description: 'Fetch OpenAPI schemas from remote applications'
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,48 @@
1
+ import { loadConfiguration } from '@platformatic/foundation'
2
+ import { writeFile } from 'node:fs/promises'
3
+ import { request } from 'undici'
4
+ import { FailedToFetchOpenAPISchemaError } from '../errors.js'
5
+ import { schema } from '../schema.js'
6
+ import { upgrade } from '../upgrade.js'
7
+ import { prefixWithSlash } from '../utils.js'
8
+
9
+ export async function fetchOpenApiSchema (application) {
10
+ const { origin, openapi } = application
11
+
12
+ const openApiUrl = origin + prefixWithSlash(openapi.url)
13
+ const { statusCode, body } = await request(openApiUrl)
14
+
15
+ if (statusCode !== 200 && statusCode !== 201) {
16
+ throw new FailedToFetchOpenAPISchemaError(openApiUrl)
17
+ }
18
+ const schema = await body.json()
19
+
20
+ if (openapi.file !== undefined) {
21
+ await writeFile(openapi.file, JSON.stringify(schema, null, 2))
22
+ }
23
+
24
+ return schema
25
+ }
26
+
27
+ export async function fetchOpenApiSchemas (logger, configFile, _args, { colorette }) {
28
+ const { bold } = colorette
29
+ const config = await loadConfiguration(configFile, schema, { upgrade })
30
+ const { applications } = config.gateway
31
+
32
+ const applicationsWithValidOpenApi = applications.filter(({ openapi }) => openapi && openapi.url && openapi.file)
33
+
34
+ const fetchOpenApiRequests = applicationsWithValidOpenApi.map(application => fetchOpenApiSchema(application))
35
+
36
+ const fetchOpenApiResults = await Promise.allSettled(fetchOpenApiRequests)
37
+
38
+ logger.info('Fetching schemas for all applications.')
39
+
40
+ fetchOpenApiResults.forEach((result, index) => {
41
+ const applicationId = applicationsWithValidOpenApi[index].id
42
+ if (result.status === 'rejected') {
43
+ logger.error(`Failed to fetch OpenAPI schema for application with id ${bold(applicationId)}: ${result.reason}`)
44
+ } else {
45
+ logger.info(`Successfully fetched OpenAPI schema for application with id ${bold(applicationId)}`)
46
+ }
47
+ })
48
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,18 @@
1
+ import createError from '@fastify/error'
2
+
3
+ export const ERROR_PREFIX = 'PLT_GATEWAY'
4
+
5
+ export const FastifyInstanceIsAlreadyListeningError = createError(
6
+ `${ERROR_PREFIX}_FASTIFY_INSTANCE_IS_ALREADY_LISTENING`,
7
+ 'Fastify instance is already listening. Cannot call "addGatewayOnRouteHook"!'
8
+ )
9
+ export const FailedToFetchOpenAPISchemaError = createError(
10
+ `${ERROR_PREFIX}_FAILED_TO_FETCH_OPENAPI_SCHEMA`,
11
+ 'Failed to fetch OpenAPI schema from %s'
12
+ )
13
+ export const ValidationErrors = createError(`${ERROR_PREFIX}_VALIDATION_ERRORS`, 'Validation errors: %s')
14
+ export const PathAlreadyExistsError = createError(`${ERROR_PREFIX}_PATH_ALREADY_EXISTS`, 'Path "%s" already exists')
15
+ export const CouldNotReadOpenAPIConfigError = createError(
16
+ `${ERROR_PREFIX}_COULD_NOT_READ_OPENAPI_CONFIG`,
17
+ 'Could not read openapi config for "%s" application'
18
+ )
@@ -0,0 +1,66 @@
1
+ import fp from 'fastify-plugin'
2
+ import rfdc from 'rfdc'
3
+ import { FastifyInstanceIsAlreadyListeningError } from './errors.js'
4
+
5
+ const deepClone = rfdc()
6
+
7
+ async function gatewayHookPlugin (app) {
8
+ const onRoutesHooks = {}
9
+
10
+ app.addHook('onRoute', routeOptions => {
11
+ if (routeOptions.schema) {
12
+ routeOptions.schema = deepClone(routeOptions.schema)
13
+ }
14
+
15
+ const method = routeOptions.method
16
+ const openApiPath = routeOptions.config?.openApiPath
17
+
18
+ const onRouteHooks = onRoutesHooks[openApiPath]?.[method]
19
+ if (Array.isArray(onRouteHooks)) {
20
+ for (const onRouteHook of onRouteHooks) {
21
+ onRouteHook(routeOptions)
22
+ }
23
+ }
24
+ })
25
+
26
+ let isApplicationReady = false
27
+ app.addHook('onReady', () => {
28
+ isApplicationReady = true
29
+ })
30
+
31
+ function addGatewayOnRouteHook (openApiPath, methods, hook) {
32
+ /* c8 ignore next 5 */
33
+ if (isApplicationReady) {
34
+ throw new FastifyInstanceIsAlreadyListeningError()
35
+ }
36
+
37
+ if (onRoutesHooks[openApiPath] === undefined) {
38
+ onRoutesHooks[openApiPath] = {}
39
+ }
40
+
41
+ const routeHooks = onRoutesHooks[openApiPath]
42
+
43
+ for (let method of methods) {
44
+ method = method.toUpperCase()
45
+
46
+ if (routeHooks[method] === undefined) {
47
+ routeHooks[method] = []
48
+ }
49
+ routeHooks[method].push(hook)
50
+ }
51
+ }
52
+
53
+ Object.defineProperty(app.platformatic, 'addGatewayOnRouteHook', {
54
+ value: addGatewayOnRouteHook,
55
+ writable: false,
56
+ configurable: false
57
+ })
58
+
59
+ Object.defineProperty(app.platformatic, 'addComposerOnRouteHook', {
60
+ value: addGatewayOnRouteHook,
61
+ writable: false,
62
+ configurable: false
63
+ })
64
+ }
65
+
66
+ export const gatewayHook = fp(gatewayHookPlugin)
@@ -0,0 +1,127 @@
1
+ import { Generator as ServiceGenerator } from '@platformatic/service'
2
+
3
+ export class Generator extends ServiceGenerator {
4
+ constructor (opts) {
5
+ super({
6
+ ...opts,
7
+ module: '@platformatic/gateway'
8
+ })
9
+ }
10
+
11
+ getDefaultConfig () {
12
+ const defaultBaseConfig = super.getDefaultConfig()
13
+
14
+ return {
15
+ ...defaultBaseConfig,
16
+ plugin: false,
17
+ routes: false,
18
+ tests: false
19
+ }
20
+ }
21
+
22
+ async _beforePrepare () {
23
+ if (this.config.isUpdating) {
24
+ return
25
+ }
26
+
27
+ await super._beforePrepare()
28
+
29
+ this.addEnvVars(
30
+ {
31
+ PLT_EXAMPLE_ORIGIN: 'http://127.0.0.1:3043'
32
+ },
33
+ { overwrite: false, default: true }
34
+ )
35
+
36
+ this.config.dependencies = {
37
+ '@platformatic/gateway': `^${this.platformaticVersion}`
38
+ }
39
+ }
40
+
41
+ async _afterPrepare () {
42
+ if (this.config.isUpdating) {
43
+ return
44
+ }
45
+
46
+ await super._afterPrepare()
47
+ const PLT_ENVIRONMENT_TEMPLATE = `
48
+ import { type FastifyInstance } from 'fastify'
49
+ import { PlatformaticApplication, PlatformaticGatewayConfig } from '@platformatic/gateway'
50
+
51
+ declare module 'fastify' {
52
+ interface FastifyInstance {
53
+ platformatic: PlatformaticApplication<PlatformaticGatewayConfig>
54
+ }
55
+ }
56
+ `
57
+
58
+ const README = `
59
+ # Platformatic Gateway API
60
+
61
+ This is a generated [Platformatic Gateway](https://docs.platformatic.dev/docs/gateway/overview) application.
62
+
63
+ ## Requirements
64
+
65
+ Platformatic supports macOS, Linux and Windows ([WSL](https://docs.microsoft.com/windows/wsl/) recommended).
66
+ You'll need to have [Node.js](https://nodejs.org/) >= v18.8.0 or >= v20.6.0
67
+
68
+ ## Setup
69
+
70
+ 1. Install dependencies:
71
+
72
+ \`\`\`bash
73
+ npm install
74
+ \`\`\`
75
+
76
+ ## Usage
77
+
78
+ Run the API with:
79
+
80
+ \`\`\`bash
81
+ npm start
82
+ \`\`\`
83
+
84
+ ### Explore
85
+ - ⚡ The Platformatic Gateway server is running at http://localhost:3042/
86
+ - 📔 View the REST API's Swagger documentation at http://localhost:3042/documentation/
87
+ `
88
+
89
+ this.addFile({ path: '', file: 'plt-env.d.ts', contents: PLT_ENVIRONMENT_TEMPLATE })
90
+ this.addFile({ path: '', file: 'README.md', contents: README })
91
+ }
92
+
93
+ async _getConfigFileContents () {
94
+ const config = await super._getConfigFileContents()
95
+ delete config.service
96
+ config.$schema = `https://schemas.platformatic.dev/@platformatic/gateway/${this.platformaticVersion}.json`
97
+
98
+ config.gateway = {
99
+ applications: [
100
+ {
101
+ id: 'example',
102
+ origin: `{${this.getEnvVarName('PLT_EXAMPLE_ORIGIN')}}`,
103
+ openapi: {
104
+ url: '/documentation/json'
105
+ }
106
+ }
107
+ ],
108
+ refreshTimeout: 1000
109
+ }
110
+
111
+ if (this.runtime !== null) {
112
+ config.gateway.applications = this.runtime.applications
113
+ .filter(applicationMeta => applicationMeta.application.module !== '@platformatic/gateway')
114
+ .map(applicationMeta => {
115
+ return {
116
+ id: applicationMeta.name,
117
+ openapi: {
118
+ url: '/documentation/json',
119
+ prefix: `/${applicationMeta.name}`
120
+ }
121
+ }
122
+ })
123
+ }
124
+
125
+ return config
126
+ }
127
+ }