@platformatic/nest 3.68.0 → 4.0.0-new-config.1

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.
package/config.d.ts CHANGED
@@ -66,7 +66,7 @@ export interface PlatformaticNestJSConfig {
66
66
  hostname?: string;
67
67
  port?: number | string;
68
68
  /**
69
- * Configures how entrypoint server worker ports are assigned. When set to shared, all workers listen on the same port. When set to perWorkerIncrement, each worker will use its own port, starting from port (worker 0).
69
+ * Configures how the port is assigned when the application runs multiple workers. When set to shared (the default), all workers listen on the same port (which requires SO_REUSEPORT support). When set to perWorkerIncrement, each worker listens on its own port, starting from port (worker 0) and incrementing by one for each additional worker.
70
70
  */
71
71
  portAssignment?: "shared" | "perWorkerIncrement";
72
72
  /**
@@ -224,46 +224,6 @@ export interface PlatformaticNestJSConfig {
224
224
  };
225
225
  [k: string]: unknown;
226
226
  };
227
- server?: {
228
- hostname?: string;
229
- port?: number | string;
230
- /**
231
- * Configures how entrypoint server worker ports are assigned. When set to shared, all workers listen on the same port. When set to perWorkerIncrement, each worker will use its own port, starting from port (worker 0).
232
- */
233
- portAssignment?: "shared" | "perWorkerIncrement";
234
- /**
235
- * The maximum length of the queue of pending connections
236
- */
237
- backlog?: number;
238
- http2?: boolean;
239
- https?: {
240
- allowHTTP1?: boolean;
241
- key:
242
- | string
243
- | {
244
- path?: string;
245
- }
246
- | (
247
- | string
248
- | {
249
- path?: string;
250
- }
251
- )[];
252
- cert:
253
- | string
254
- | {
255
- path?: string;
256
- }
257
- | (
258
- | string
259
- | {
260
- path?: string;
261
- }
262
- )[];
263
- requestCert?: boolean;
264
- rejectUnauthorized?: boolean;
265
- };
266
- };
267
227
  reuseTcpPorts?: boolean;
268
228
  startTimeout?: number;
269
229
  restartOnError?: boolean | number;
package/index.js CHANGED
@@ -29,3 +29,5 @@ export async function create (configOrRoot, sourceOrConfig, context) {
29
29
 
30
30
  export * from './lib/capability.js'
31
31
  export { packageJson, schema, schemaComponents, version } from './lib/schema.js'
32
+
33
+ export * from './lib/factory.js'
package/lib/capability.js CHANGED
@@ -30,6 +30,8 @@ export class NestCapability extends BaseCapability {
30
30
  #app
31
31
  #server
32
32
  #dispatcher
33
+ #managedDevelopment
34
+ #originalDevelopmentEnvironment
33
35
 
34
36
  constructor (root, config, context) {
35
37
  super('nest', version, root, config, context)
@@ -60,13 +62,8 @@ export class NestCapability extends BaseCapability {
60
62
  this.subprocessForceClose = true
61
63
  }
62
64
 
63
- async start ({ listen }) {
64
- // Make this idempotent
65
- if (this.url) {
66
- return this.url
67
- }
68
-
69
- await super._start({ listen })
65
+ async _start () {
66
+ await super._start()
70
67
 
71
68
  const config = this.config
72
69
  const command = config.application.commands[this.isProduction ? 'production' : 'development']
@@ -77,7 +74,19 @@ export class NestCapability extends BaseCapability {
77
74
  })
78
75
 
79
76
  if (command || !this.isProduction) {
80
- await this.startWithCommand(command || `node ${this.#nestjsCli} start --watch --preserveWatchOutput`)
77
+ this.#managedDevelopment = !command && !this.isProduction
78
+
79
+ if (this.#managedDevelopment && typeof this.serverConfig?.port === 'undefined') {
80
+ this.#managedDevelopment = false
81
+ return
82
+ }
83
+
84
+ try {
85
+ await this.startWithCommand(command || `node ${this.#nestjsCli} start --watch --preserveWatchOutput`)
86
+ } finally {
87
+ this.#restoreDevelopmentEnvironment()
88
+ this.#managedDevelopment = false
89
+ }
81
90
 
82
91
  // We use url changing as a way to notify restarts
83
92
  this.childManager.on('url', () => {
@@ -85,12 +94,12 @@ export class NestCapability extends BaseCapability {
85
94
  events.emitAndNotify('url', this.url)
86
95
  })
87
96
  } else {
88
- return this.#startProduction(listen)
97
+ return this.#startProduction()
89
98
  }
90
99
  }
91
100
 
92
- async stop () {
93
- await super.stop()
101
+ async _stop () {
102
+ await super._stop()
94
103
 
95
104
  if (this.childManager) {
96
105
  return this.stopCommand()
@@ -188,22 +197,42 @@ export class NestCapability extends BaseCapability {
188
197
  async getChildManagerContext (basePath) {
189
198
  const context = await super.getChildManagerContext(basePath)
190
199
 
191
- // In development mode we need to choose the random port in advance as the Nest CLI will start a new process
192
- // every time the code changes and thus we need to ensure that the port is always the same.
193
- if (!this.isProduction && (context.port === true || context.port === 0)) {
194
- context.port = await getPort()
200
+ // In managed development mode the Nest CLI starts the application in another process. Choose an ephemeral
201
+ // port once when needed and pass capability-owned listener settings through the environment inherited by
202
+ // that process. Applications started with custom commands remain responsible for their own listener.
203
+ if (this.#managedDevelopment) {
204
+ if (context.port === true || context.port === 0) {
205
+ const environmentPort = Number(process.env.PORT)
206
+ context.port = Number.isInteger(environmentPort) && environmentPort > 0 ? environmentPort : await getPort()
207
+ }
208
+
209
+ this.#originalDevelopmentEnvironment = new Map([
210
+ ['PORT', process.env.PORT],
211
+ ['HOST', process.env.HOST]
212
+ ])
213
+ process.env.PORT = context.port.toString()
214
+
215
+ if (typeof context.host === 'string') {
216
+ process.env.HOST = context.host
217
+ }
195
218
  }
196
219
 
197
220
  return context
198
221
  }
199
222
 
200
- async #startProduction (listen) {
201
- // Listen if entrypoint
202
- if (this.#app && listen) {
203
- await this.#listen()
204
- return this.url
223
+ #restoreDevelopmentEnvironment () {
224
+ for (const [key, value] of this.#originalDevelopmentEnvironment ?? []) {
225
+ if (typeof value === 'undefined') {
226
+ delete process.env[key]
227
+ } else {
228
+ process.env[key] = value
229
+ }
205
230
  }
206
231
 
232
+ this.#originalDevelopmentEnvironment = null
233
+ }
234
+
235
+ async #startProduction () {
207
236
  const outputDirectory = this.config.application.outputDirectory
208
237
  const { path, name } = this.config.nest.appModule
209
238
  this.verifyOutputDirectory(resolve(this.root, outputDirectory))
@@ -241,23 +270,22 @@ export class NestCapability extends BaseCapability {
241
270
  this.#dispatcher = this.#server.listeners('request')[0]
242
271
  }
243
272
 
244
- if (listen) {
245
- await this.#listen()
246
- }
273
+ await this.#listen()
247
274
 
248
275
  await this._collectMetrics()
249
276
  return this.url
250
277
  }
251
278
 
252
279
  async #listen () {
280
+ if (typeof this.serverConfig?.port === 'undefined') {
281
+ return
282
+ }
283
+
253
284
  const serverOptions = this.serverConfig
254
285
  const listenOptions = buildListenOptions(serverOptions)
286
+ const serverPromise = createServerListener()
255
287
 
256
- if (typeof serverOptions?.backlog === 'number') {
257
- createServerListener(false, false, { backlog: serverOptions.backlog })
258
- }
259
-
260
- await this.#app.listen(listenOptions)
288
+ await Promise.all([this.#app.listen(listenOptions), serverPromise])
261
289
  this.url = getServerUrl(this.#isFastify ? this.#server.server : this.#server)
262
290
 
263
291
  return this.url
package/lib/factory.js ADDED
@@ -0,0 +1,7 @@
1
+ import { defineCapabilityFactory } from '@platformatic/basic'
2
+ import { schema, version } from './schema.js'
3
+
4
+ export const nest = defineCapabilityFactory('@platformatic/nest', schema, {
5
+ version,
6
+ flatten: ['nest']
7
+ })
package/lib/schema.js CHANGED
@@ -1,11 +1,14 @@
1
- import { schemaComponents as basicSchemaComponents } from '@platformatic/basic'
2
- import { schemaComponents as utilsSchemaComponents } from '@platformatic/foundation'
1
+ import { schemaComponents as basicSchemaComponents } from '@platformatic/basic/lib/schema.js'
2
+ import { schemaComponents as utilsSchemaComponents } from '@platformatic/foundation/lib/schema.js'
3
3
  import { readFileSync } from 'node:fs'
4
4
  import { resolve } from 'node:path'
5
5
 
6
6
  export const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf8'))
7
7
  export const version = packageJson.version
8
8
 
9
+ // Production builds an in-thread application and awaits ready() before the port check.
10
+ export const servesWithoutPort = { development: false, production: true }
11
+
9
12
  const nest = {
10
13
  type: 'object',
11
14
  properties: {
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@platformatic/nest",
3
- "version": "3.68.0",
3
+ "version": "4.0.0-new-config.1",
4
4
  "description": "Platformatic Nest.js Capability",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./schema": "./schema.js",
10
+ "./package.json": "./package.json",
11
+ "./*": "./*"
12
+ },
7
13
  "repository": {
8
14
  "type": "git",
9
15
  "url": "git+https://github.com/platformatic/platformatic.git"
@@ -18,10 +24,10 @@
18
24
  "get-port": "^7.1.0",
19
25
  "light-my-request": "^6.0.0",
20
26
  "pino-http": "^10.2.0",
21
- "@platformatic/basic": "3.68.0",
22
- "@platformatic/foundation": "3.68.0",
23
- "@platformatic/generators": "3.68.0",
24
- "@platformatic/globals": "3.68.0"
27
+ "@platformatic/basic": "4.0.0-new-config.1",
28
+ "@platformatic/generators": "4.0.0-new-config.1",
29
+ "@platformatic/globals": "4.0.0-new-config.1",
30
+ "@platformatic/foundation": "4.0.0-new-config.1"
25
31
  },
26
32
  "devDependencies": {
27
33
  "@nestjs/cli": "^11.0.7",
@@ -35,8 +41,8 @@
35
41
  "tstyche": "^6.2.0",
36
42
  "tsx": "^4.19.0",
37
43
  "typescript": "~6.0.3",
38
- "@platformatic/gateway": "3.68.0",
39
- "@platformatic/service": "3.68.0"
44
+ "@platformatic/service": "4.0.0-new-config.1",
45
+ "@platformatic/gateway": "4.0.0-new-config.1"
40
46
  },
41
47
  "engines": {
42
48
  "node": ">=22.19.0"
package/schema.js ADDED
@@ -0,0 +1,7 @@
1
+ // The light /schema subpath, part of the v4 capability contract. It carries the schema and the
2
+ // package-level metadata main-side preparation needs, and imports nothing that would pull the
3
+ // capability's implementation in with it — boot resolves this rather than the package entry.
4
+ //
5
+ // Light is a statement about import cost, never about safety: this executes in the main process
6
+ // with full privileges, like any capability code.
7
+ export * from './lib/schema.js'
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/nest/3.68.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/nest/4.0.0-new-config.1.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic NestJS Config",
5
5
  "type": "object",
@@ -202,7 +202,7 @@
202
202
  "shared",
203
203
  "perWorkerIncrement"
204
204
  ],
205
- "description": "Configures how entrypoint server worker ports are assigned. When set to shared, all workers listen on the same port. When set to perWorkerIncrement, each worker will use its own port, starting from port (worker 0)."
205
+ "description": "Configures how the port is assigned when the application runs multiple workers. When set to shared (the default), all workers listen on the same port (which requires SO_REUSEPORT support). When set to perWorkerIncrement, each worker listens on its own port, starting from port (worker 0) and incrementing by one for each additional worker."
206
206
  },
207
207
  "backlog": {
208
208
  "type": "integer",
@@ -536,7 +536,14 @@
536
536
  "resolvePath": true
537
537
  },
538
538
  "config": {
539
- "type": "string"
539
+ "anyOf": [
540
+ {
541
+ "type": "string"
542
+ },
543
+ {
544
+ "type": "object"
545
+ }
546
+ ]
540
547
  },
541
548
  "url": {
542
549
  "type": "string"
@@ -545,12 +552,6 @@
545
552
  "type": "string",
546
553
  "default": "main"
547
554
  },
548
- "useHttp": {
549
- "type": "boolean"
550
- },
551
- "websocket": {
552
- "type": "boolean"
553
- },
554
555
  "reuseTcpPorts": {
555
556
  "type": "boolean",
556
557
  "default": true
@@ -1164,133 +1165,6 @@
1164
1165
  "default": {},
1165
1166
  "additionalProperties": true
1166
1167
  },
1167
- "server": {
1168
- "type": "object",
1169
- "properties": {
1170
- "hostname": {
1171
- "type": "string"
1172
- },
1173
- "port": {
1174
- "anyOf": [
1175
- {
1176
- "type": "integer"
1177
- },
1178
- {
1179
- "type": "string"
1180
- }
1181
- ]
1182
- },
1183
- "portAssignment": {
1184
- "type": "string",
1185
- "enum": [
1186
- "shared",
1187
- "perWorkerIncrement"
1188
- ],
1189
- "description": "Configures how entrypoint server worker ports are assigned. When set to shared, all workers listen on the same port. When set to perWorkerIncrement, each worker will use its own port, starting from port (worker 0)."
1190
- },
1191
- "backlog": {
1192
- "type": "integer",
1193
- "description": "The maximum length of the queue of pending connections"
1194
- },
1195
- "http2": {
1196
- "type": "boolean"
1197
- },
1198
- "https": {
1199
- "type": "object",
1200
- "properties": {
1201
- "allowHTTP1": {
1202
- "type": "boolean"
1203
- },
1204
- "key": {
1205
- "anyOf": [
1206
- {
1207
- "type": "string"
1208
- },
1209
- {
1210
- "type": "object",
1211
- "properties": {
1212
- "path": {
1213
- "type": "string",
1214
- "resolvePath": true
1215
- }
1216
- },
1217
- "additionalProperties": false
1218
- },
1219
- {
1220
- "type": "array",
1221
- "items": {
1222
- "anyOf": [
1223
- {
1224
- "type": "string"
1225
- },
1226
- {
1227
- "type": "object",
1228
- "properties": {
1229
- "path": {
1230
- "type": "string",
1231
- "resolvePath": true
1232
- }
1233
- },
1234
- "additionalProperties": false
1235
- }
1236
- ]
1237
- }
1238
- }
1239
- ]
1240
- },
1241
- "cert": {
1242
- "anyOf": [
1243
- {
1244
- "type": "string"
1245
- },
1246
- {
1247
- "type": "object",
1248
- "properties": {
1249
- "path": {
1250
- "type": "string",
1251
- "resolvePath": true
1252
- }
1253
- },
1254
- "additionalProperties": false
1255
- },
1256
- {
1257
- "type": "array",
1258
- "items": {
1259
- "anyOf": [
1260
- {
1261
- "type": "string"
1262
- },
1263
- {
1264
- "type": "object",
1265
- "properties": {
1266
- "path": {
1267
- "type": "string",
1268
- "resolvePath": true
1269
- }
1270
- },
1271
- "additionalProperties": false
1272
- }
1273
- ]
1274
- }
1275
- }
1276
- ]
1277
- },
1278
- "requestCert": {
1279
- "type": "boolean"
1280
- },
1281
- "rejectUnauthorized": {
1282
- "type": "boolean"
1283
- }
1284
- },
1285
- "additionalProperties": false,
1286
- "required": [
1287
- "key",
1288
- "cert"
1289
- ]
1290
- }
1291
- },
1292
- "additionalProperties": false
1293
- },
1294
1168
  "reuseTcpPorts": {
1295
1169
  "type": "boolean",
1296
1170
  "default": true