@platformatic/react-router 3.29.0 → 3.30.0

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
@@ -133,6 +133,8 @@ export interface PlatformaticReactRouterConfig {
133
133
  maxMemory?: number;
134
134
  cooldown?: number;
135
135
  gracePeriod?: number;
136
+ scaleUpELU?: number;
137
+ scaleDownELU?: number;
136
138
  [k: string]: unknown;
137
139
  };
138
140
  workersRestartDelay?: number | string;
@@ -465,13 +467,7 @@ export interface PlatformaticReactRouterConfig {
465
467
  maxWorkers?: number;
466
468
  cooldownSec?: number;
467
469
  gracePeriod?: number;
468
- /**
469
- * @deprecated
470
- */
471
470
  scaleUpELU?: number;
472
- /**
473
- * @deprecated
474
- */
475
471
  scaleDownELU?: number;
476
472
  /**
477
473
  * @deprecated
@@ -535,6 +531,8 @@ export interface PlatformaticReactRouterConfig {
535
531
  static?: number;
536
532
  minimum?: number;
537
533
  maximum?: number;
534
+ scaleUpELU?: number;
535
+ scaleDownELU?: number;
538
536
  [k: string]: unknown;
539
537
  };
540
538
  health?: {
package/lib/capability.js CHANGED
@@ -1,3 +1,4 @@
1
+ import fastifyStatic from '@fastify/static'
1
2
  import {
2
3
  cleanBasePath,
3
4
  createServerListener,
@@ -8,13 +9,12 @@ import {
8
9
  resolvePackageViaCJS
9
10
  } from '@platformatic/basic'
10
11
  import { ViteCapability } from '@platformatic/vite'
11
- import { createRequestHandler } from '@react-router/express'
12
- import express from 'express'
13
- import inject from 'light-my-request'
12
+ import fastify from 'fastify'
14
13
  import { existsSync } from 'node:fs'
15
14
  import { readFile, writeFile } from 'node:fs/promises'
16
- import { dirname, resolve } from 'node:path'
17
- import { pinoHttp } from 'pino-http'
15
+ import { dirname, join, resolve } from 'node:path'
16
+ import { Readable } from 'node:stream'
17
+ import { createRequestHandler } from 'react-router'
18
18
  import { satisfies } from 'semver'
19
19
  import { packageJson } from './schema.js'
20
20
 
@@ -22,7 +22,6 @@ const supportedVersions = '^7.0.0'
22
22
 
23
23
  export class ReactRouterCapability extends ViteCapability {
24
24
  #app
25
- #server
26
25
  #reactRouter
27
26
  #basePath
28
27
 
@@ -55,6 +54,11 @@ export class ReactRouterCapability extends ViteCapability {
55
54
  }
56
55
 
57
56
  async start ({ listen }) {
57
+ // Make this idempotent
58
+ if (this.url) {
59
+ return this.url
60
+ }
61
+
58
62
  const config = this.config
59
63
  const reactRouterConfig = await this.#getReactRouterConfig()
60
64
 
@@ -69,6 +73,8 @@ export class ReactRouterCapability extends ViteCapability {
69
73
  }
70
74
 
71
75
  if (reactRouterConfig.ssr) {
76
+ await super._start({ listen })
77
+
72
78
  return this.#startSSRProduction(listen)
73
79
  }
74
80
  }
@@ -76,22 +82,6 @@ export class ReactRouterCapability extends ViteCapability {
76
82
  return super.start({ listen })
77
83
  }
78
84
 
79
- async stop () {
80
- const reactRouterConfig = await this.#getReactRouterConfig()
81
-
82
- if (reactRouterConfig.ssr) {
83
- await this._stop()
84
-
85
- if (this.#server?.listening) {
86
- await this._closeServer(this.#server)
87
- }
88
-
89
- return
90
- }
91
-
92
- return super.stop()
93
- }
94
-
95
85
  async build () {
96
86
  const config = this.config
97
87
  const command = config.application.commands.build
@@ -109,7 +99,7 @@ export class ReactRouterCapability extends ViteCapability {
109
99
 
110
100
  await writeFile(
111
101
  resolve(this.root, config.reactRouter.outputDirectory, '.platformatic-build.json'),
112
- JSON.stringify({ basePath: reactRouterConfig.basename ?? '/' }),
102
+ JSON.stringify({ basePath: basePath ?? reactRouterConfig.basename ?? '/' }),
113
103
  'utf-8'
114
104
  )
115
105
  }
@@ -119,16 +109,6 @@ export class ReactRouterCapability extends ViteCapability {
119
109
  return super.getMeta(reactRouterConfig.basename)
120
110
  }
121
111
 
122
- async inject (injectParams, onInject) {
123
- const reactRouterConfig = await this.#getReactRouterConfig()
124
-
125
- if (this.isProduction && reactRouterConfig.ssr) {
126
- return this.#inject(injectParams, onInject)
127
- }
128
-
129
- return super.inject(injectParams, onInject)
130
- }
131
-
132
112
  async #getReactRouterConfig () {
133
113
  const ext = ['ts', 'js'].find(ext => existsSync(resolve(this.root, `react-router.config.${ext}`)))
134
114
 
@@ -150,16 +130,8 @@ export class ReactRouterCapability extends ViteCapability {
150
130
  createServerListener(false, false, { backlog: serverOptions.backlog })
151
131
  }
152
132
 
153
- this.#server = await new Promise((resolve, reject) => {
154
- return this.#app
155
- .listen(listenOptions, function () {
156
- resolve(this)
157
- })
158
- .on('error', reject)
159
- })
160
-
161
- this.url = getServerUrl(this.#server)
162
-
133
+ await this.#app.listen(listenOptions)
134
+ this.url = getServerUrl(this.#app.server)
163
135
  return this.url
164
136
  }
165
137
 
@@ -171,33 +143,110 @@ export class ReactRouterCapability extends ViteCapability {
171
143
  this.verifyOutputDirectory(serverRoot)
172
144
  this.#basePath = await this._getBasePathFromBuildInfo()
173
145
 
174
- const { entrypoint } = await importFile(resolve(serverRoot, 'index.js'))
146
+ const serverModule = await importFile(resolve(serverRoot, 'index.js'))
175
147
 
176
- // Setup express app
177
- this.#app = express()
148
+ // Setup fastify
149
+ this.#app = fastify({ loggerInstance: this.logger })
178
150
  this._setApp(this.#app)
179
- this.#app.disable('x-powered-by')
180
- this.#app.use(pinoHttp({ logger: this.logger }))
181
- this.#app.use(this.#basePath, express.static(clientRoot))
182
- this.#app.all(
183
- `${ensureTrailingSlash(cleanBasePath(this.#basePath))}*`,
184
- createRequestHandler({ build: () => entrypoint })
185
- )
186
151
 
152
+ let assetsRoot = clientRoot
153
+ let publicPath = '/'
154
+ let mainHandler
155
+
156
+ // Since it uses the Fetch API, we don't need to parse the request body.
157
+ this.#app.removeAllContentTypeParsers()
158
+ this.#app.addContentTypeParser('*', function (_, payload, done) {
159
+ done(null, payload)
160
+ })
161
+
162
+ // Custom entrypoint
163
+ if (serverModule.entrypoint) {
164
+ mainHandler = createRequestHandler(() => serverModule.entrypoint, process.env.NODE_ENV)
165
+ // Adapts @react-router/serve to fastify
166
+ } else {
167
+ if (serverModule.assetsBuildDirectory) {
168
+ assetsRoot = resolve(this.root, serverModule.assetsBuildDirectory)
169
+ }
170
+
171
+ if (serverModule.publicPath) {
172
+ publicPath = serverModule.publicPath ?? '/'
173
+ }
174
+
175
+ // RSC build
176
+ if (typeof serverModule.default === 'function') {
177
+ if (serverModule.unstable_reactRouterServeConfig) {
178
+ if (serverModule.unstable_reactRouterServeConfig.assetsBuildDirectory) {
179
+ assetsRoot = resolve(this.root, serverModule.unstable_reactRouterServeConfig.assetsBuildDirectory)
180
+ }
181
+ if (serverModule.unstable_reactRouterServeConfig.publicPath) {
182
+ publicPath = serverModule.unstable_reactRouterServeConfig.publicPath
183
+ }
184
+ }
185
+
186
+ mainHandler = serverModule.default
187
+ } else {
188
+ mainHandler = createRequestHandler(serverModule, process.env.NODE_ENV)
189
+ }
190
+ }
191
+
192
+ await this.#app.register(fastifyStatic, {
193
+ root: resolve(assetsRoot, 'assets'),
194
+ prefix: join(this.#basePath, 'assets'),
195
+ prefixAvoidTrailingSlash: true,
196
+ schemaHide: true,
197
+ decorateReply: false
198
+ })
199
+
200
+ if (publicPath !== '/') {
201
+ await this.#app.register(fastifyStatic, {
202
+ root: resolve(assetsRoot, 'assets'),
203
+ prefix: join(this.#basePath, publicPath),
204
+ prefixAvoidTrailingSlash: true,
205
+ schemaHide: true,
206
+ decorateReply: false
207
+ })
208
+ }
209
+
210
+ this.#app.all(`${ensureTrailingSlash(cleanBasePath(this.#basePath))}*`, this.#handleRequest.bind(this, mainHandler))
211
+
212
+ await this.#app.ready()
187
213
  await this._collectMetrics()
188
- return this.url
189
214
  }
190
215
 
191
- async #inject (injectParams, onInject) {
192
- const res = await inject(this.#app, injectParams, onInject)
216
+ #handleRequest (handle, req) {
217
+ // Support aborting
218
+ const ac = new AbortController()
219
+ let ended = false
220
+
221
+ req.raw.on('aborted', () => ac.abort())
222
+ req.raw.on('end', () => { ended = true })
223
+ req.raw.on('close', () => {
224
+ if (!ended) {
225
+ ac.abort()
226
+ }
227
+ })
228
+
229
+ const headers = new Headers()
230
+ for (const [key, value] of Object.entries(req.headers)) {
231
+ if (value) {
232
+ headers.set(key, Array.isArray(value) ? value.join(',') : value)
233
+ }
234
+ }
235
+
236
+ let body
193
237
 
194
- /* c8 ignore next 3 */
195
- if (onInject) {
196
- return
238
+ if (!['GET', 'HEAD'].includes(req.method)) {
239
+ body = Readable.toWeb(req.raw)
197
240
  }
198
241
 
199
- // Since inject might be called from the main thread directly via ITC, let's clean it up
200
- const { statusCode, headers, body, payload, rawPayload } = res
201
- return { statusCode, headers, body, payload, rawPayload }
242
+ return handle(
243
+ new Request(`${req.protocol}://${req.hostname}${req.raw.url}`, {
244
+ method: req.method,
245
+ headers,
246
+ body,
247
+ duplex: body ? 'half' : undefined,
248
+ signal: ac.signal
249
+ })
250
+ )
202
251
  }
203
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/react-router",
3
- "version": "3.29.0",
3
+ "version": "3.30.0",
4
4
  "description": "Platformatic React Router Capability",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -15,23 +15,21 @@
15
15
  },
16
16
  "homepage": "https://github.com/platformatic/platformatic#readme",
17
17
  "dependencies": {
18
- "express": "^4.19.2",
19
- "light-my-request": "^6.0.0",
20
- "pino-http": "^10.2.0",
18
+ "@fastify/static": "^8.0.0",
19
+ "fastify": "^5.7.0",
21
20
  "semver": "^7.6.3",
22
- "@platformatic/basic": "3.29.0",
23
- "@platformatic/vite": "3.29.0",
24
- "@platformatic/foundation": "3.29.0"
21
+ "@platformatic/basic": "3.30.0",
22
+ "@platformatic/foundation": "3.30.0",
23
+ "@platformatic/vite": "3.30.0"
25
24
  },
26
25
  "devDependencies": {
27
26
  "@react-router/dev": "^7.10.0",
28
- "@react-router/express": "^7.10.1",
29
27
  "@react-router/node": "^7.10.0",
30
28
  "@types/react": "^19.1.11",
31
29
  "@types/react-dom": "^19.1.7",
32
30
  "cleaner-spec-reporter": "^0.5.0",
33
31
  "eslint": "9",
34
- "fastify": "^5.0.0",
32
+ "fastify": "^5.7.0",
35
33
  "isbot": "^5.1.17",
36
34
  "json-schema-to-typescript": "^15.0.1",
37
35
  "neostandard": "^0.12.0",
@@ -42,8 +40,8 @@
42
40
  "typescript": "^5.5.4",
43
41
  "vite": "^7.1.7",
44
42
  "ws": "^8.18.0",
45
- "@platformatic/service": "3.29.0",
46
- "@platformatic/gateway": "3.29.0"
43
+ "@platformatic/gateway": "3.30.0",
44
+ "@platformatic/service": "3.30.0"
47
45
  },
48
46
  "engines": {
49
47
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/react-router/3.29.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/react-router/3.30.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic React Router Config",
5
5
  "type": "object",
@@ -456,6 +456,16 @@
456
456
  "maximum": {
457
457
  "type": "number",
458
458
  "minimum": 0
459
+ },
460
+ "scaleUpELU": {
461
+ "type": "number",
462
+ "minimum": 0,
463
+ "maximum": 1
464
+ },
465
+ "scaleDownELU": {
466
+ "type": "number",
467
+ "minimum": 0,
468
+ "maximum": 1
459
469
  }
460
470
  }
461
471
  }
@@ -735,6 +745,16 @@
735
745
  "gracePeriod": {
736
746
  "type": "number",
737
747
  "minimum": 0
748
+ },
749
+ "scaleUpELU": {
750
+ "type": "number",
751
+ "minimum": 0,
752
+ "maximum": 1
753
+ },
754
+ "scaleDownELU": {
755
+ "type": "number",
756
+ "minimum": 0,
757
+ "maximum": 1
738
758
  }
739
759
  }
740
760
  }
@@ -1767,14 +1787,12 @@
1767
1787
  "scaleUpELU": {
1768
1788
  "type": "number",
1769
1789
  "minimum": 0,
1770
- "maximum": 1,
1771
- "deprecated": true
1790
+ "maximum": 1
1772
1791
  },
1773
1792
  "scaleDownELU": {
1774
1793
  "type": "number",
1775
1794
  "minimum": 0,
1776
- "maximum": 1,
1777
- "deprecated": true
1795
+ "maximum": 1
1778
1796
  },
1779
1797
  "timeWindowSec": {
1780
1798
  "type": "number",
@@ -1975,6 +1993,16 @@
1975
1993
  "maximum": {
1976
1994
  "type": "number",
1977
1995
  "minimum": 0
1996
+ },
1997
+ "scaleUpELU": {
1998
+ "type": "number",
1999
+ "minimum": 0,
2000
+ "maximum": 1
2001
+ },
2002
+ "scaleDownELU": {
2003
+ "type": "number",
2004
+ "minimum": 0,
2005
+ "maximum": 1
1978
2006
  }
1979
2007
  }
1980
2008
  }