@platformatic/next 2.19.0-alpha.2 → 2.19.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.
package/config.d.ts CHANGED
@@ -93,10 +93,4 @@ export interface PlatformaticNextJsStackable {
93
93
  production?: string;
94
94
  };
95
95
  };
96
- cache?: {
97
- adapter: "redis" | "valkey";
98
- url: string;
99
- prefix?: string;
100
- maxTTL?: number | string;
101
- };
102
96
  }
package/index.js CHANGED
@@ -59,10 +59,8 @@ export class NextStackable extends BaseStackable {
59
59
  return this.stopCommand()
60
60
  }
61
61
 
62
- globalThis.platformatic.events.emit('plt:next:close')
63
-
64
62
  if (this.isProduction) {
65
- await new Promise((resolve, reject) => {
63
+ return new Promise((resolve, reject) => {
66
64
  this.#server.close(error => {
67
65
  /* c8 ignore next 3 */
68
66
  if (error) {
@@ -72,8 +70,6 @@ export class NextStackable extends BaseStackable {
72
70
  resolve()
73
71
  })
74
72
  })
75
-
76
- await this.childManager.close()
77
73
  } else {
78
74
  const exitPromise = once(this.#child, 'exit')
79
75
  await this.childManager.close()
@@ -100,8 +96,7 @@ export class NextStackable extends BaseStackable {
100
96
  /* c8 ignore next 5 */
101
97
  async getWatchConfig () {
102
98
  return {
103
- enabled: false,
104
- path: this.root
99
+ enabled: false
105
100
  }
106
101
  }
107
102
 
@@ -136,7 +131,6 @@ export class NextStackable extends BaseStackable {
136
131
  this.childManager = new ChildManager({
137
132
  loader: loaderUrl,
138
133
  context: {
139
- config: this.configManager.current,
140
134
  serviceId: this.serviceId,
141
135
  workerId: this.workerId,
142
136
  // Always use URL to avoid serialization problem in Windows
@@ -189,7 +183,6 @@ export class NextStackable extends BaseStackable {
189
183
  this.childManager = new ChildManager({
190
184
  loader: loaderUrl,
191
185
  context: {
192
- config: this.configManager.current,
193
186
  serviceId: this.serviceId,
194
187
  workerId: this.workerId,
195
188
  // Always use URL to avoid serialization problem in Windows
@@ -209,7 +202,6 @@ export class NextStackable extends BaseStackable {
209
202
 
210
203
  async #startProductionNext () {
211
204
  try {
212
- globalThis.platformatic.config = this.configManager.current
213
205
  await this.childManager.inject()
214
206
  const { nextStart } = await importFile(pathResolve(this.#next, './dist/cli/next-start.js'))
215
207
 
@@ -250,10 +242,6 @@ function transformConfig () {
250
242
  this.current.watch = { enabled: this.current.watch || false }
251
243
  }
252
244
 
253
- if (this.current.cache?.adapter === 'redis') {
254
- this.current.cache.adapter = 'valkey'
255
- }
256
-
257
245
  basicTransformConfig.call(this)
258
246
  }
259
247
 
package/lib/loader.js CHANGED
@@ -11,12 +11,10 @@ import {
11
11
  variableDeclarator
12
12
  } from '@babel/types'
13
13
  import { readFile, realpath } from 'node:fs/promises'
14
- import { sep } from 'node:path'
15
14
  import { fileURLToPath, pathToFileURL } from 'node:url'
16
15
 
17
16
  const originalId = '__pltOriginalNextConfig'
18
17
 
19
- let config
20
18
  let candidates
21
19
  let basePath
22
20
 
@@ -37,11 +35,6 @@ function parseSingleExpression (expr) {
37
35
  __pltOriginalNextConfig.basePath = basePath
38
36
  }
39
37
 
40
- if(typeof __pltOriginalNextConfig.cacheHandler === 'undefined') {
41
- __pltOriginalNextConfig.cacheHandler = $PATH
42
- __pltOriginalNextConfig.cacheMaxMemorySize = 0
43
- }
44
-
45
38
  // This is to send the configuraion when Next is executed in a child process (development)
46
39
  globalThis[Symbol.for('plt.children.itc')]?.notify('config', __pltOriginalNextConfig)
47
40
 
@@ -52,35 +45,21 @@ function parseSingleExpression (expr) {
52
45
  }
53
46
  */
54
47
  function createEvaluatorWrapperFunction (original) {
55
- const cacheHandler = config?.cache
56
- ? fileURLToPath(new URL(`./caching/${config.cache.adapter ?? 'foo'}.js`, import.meta.url)).replaceAll(sep, '/')
57
- : undefined
58
-
59
48
  return functionDeclaration(
60
49
  null,
61
50
  [restElement(identifier('args'))],
62
- blockStatement(
63
- [
64
- variableDeclaration('let', [variableDeclarator(identifier(originalId), original)]),
65
- parseSingleExpression(
66
- `if (typeof ${originalId} === 'function') { ${originalId} = await ${originalId}(...args) }`
67
- ),
68
- parseSingleExpression(
69
- `if (typeof ${originalId}.basePath === 'undefined') { ${originalId}.basePath = "${basePath}" }`
70
- ),
71
- cacheHandler
72
- ? parseSingleExpression(`
73
- if (typeof ${originalId}.cacheHandler === 'undefined') {
74
- ${originalId}.cacheHandler = '${cacheHandler}'
75
- ${originalId}.cacheMaxMemorySize = 0
76
- }
77
- `)
78
- : undefined,
79
- parseSingleExpression(`globalThis[Symbol.for('plt.children.itc')]?.notify('config', ${originalId})`),
80
- parseSingleExpression(`process.emit('plt:next:config', ${originalId})`),
81
- returnStatement(identifier(originalId))
82
- ].filter(e => e)
83
- ),
51
+ blockStatement([
52
+ variableDeclaration('let', [variableDeclarator(identifier(originalId), original)]),
53
+ parseSingleExpression(
54
+ `if (typeof ${originalId} === 'function') { ${originalId} = await ${originalId}(...args) }`
55
+ ),
56
+ parseSingleExpression(
57
+ `if (typeof ${originalId}.basePath === 'undefined') { ${originalId}.basePath = "${basePath}" }`
58
+ ),
59
+ parseSingleExpression(`globalThis[Symbol.for('plt.children.itc')]?.notify('config', ${originalId})`),
60
+ parseSingleExpression(`process.emit('plt:next:config', ${originalId})`),
61
+ returnStatement(identifier(originalId))
62
+ ]),
84
63
  false,
85
64
  true
86
65
  )
@@ -146,7 +125,6 @@ export async function initialize (data) {
146
125
  // Keep in sync with https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/constants.ts
147
126
  candidates = ['next.config.js', 'next.config.mjs'].map(c => new URL(c, realRoot).toString())
148
127
  basePath = data.basePath ?? ''
149
- config = data.config
150
128
  }
151
129
 
152
130
  export async function load (url, context, nextLoad) {
package/lib/schema.js CHANGED
@@ -4,36 +4,6 @@ import { readFileSync } from 'node:fs'
4
4
 
5
5
  export const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
6
6
 
7
- export const cache = {
8
- type: 'object',
9
- properties: {
10
- adapter: {
11
- type: 'string',
12
- enum: ['redis', 'valkey']
13
- },
14
- url: {
15
- type: 'string'
16
- },
17
- prefix: {
18
- type: 'string'
19
- },
20
- maxTTL: {
21
- default: 86400 * 7, // One week
22
- anyOf: [
23
- {
24
- type: 'number',
25
- minimum: 0
26
- },
27
- {
28
- type: 'string'
29
- }
30
- ]
31
- }
32
- },
33
- required: ['adapter', 'url'],
34
- additionalProperties: false
35
- }
36
-
37
7
  export const schema = {
38
8
  $id: `https://schemas.platformatic.dev/@platformatic/next/${packageJson.version}.json`,
39
9
  $schema: 'http://json-schema.org/draft-07/schema#',
@@ -46,8 +16,7 @@ export const schema = {
46
16
  logger: utilsSchemaComponents.logger,
47
17
  server: utilsSchemaComponents.server,
48
18
  watch: schemaComponents.watch,
49
- application: schemaComponents.application,
50
- cache
19
+ application: schemaComponents.application
51
20
  },
52
21
  additionalProperties: false
53
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/next",
3
- "version": "2.19.0-alpha.2",
3
+ "version": "2.19.0-alpha.4",
4
4
  "description": "Platformatic Next.js Stackable",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -19,12 +19,10 @@
19
19
  "@babel/parser": "^7.25.3",
20
20
  "@babel/traverse": "^7.25.3",
21
21
  "@babel/types": "^7.25.2",
22
- "iovalkey": "^0.2.1",
23
- "msgpackr": "^1.11.2",
24
22
  "semver": "^7.6.3",
25
- "@platformatic/basic": "2.19.0-alpha.2",
26
- "@platformatic/config": "2.19.0-alpha.2",
27
- "@platformatic/utils": "2.19.0-alpha.2"
23
+ "@platformatic/basic": "2.19.0-alpha.4",
24
+ "@platformatic/config": "2.19.0-alpha.4",
25
+ "@platformatic/utils": "2.19.0-alpha.4"
28
26
  },
29
27
  "devDependencies": {
30
28
  "@fastify/reply-from": "^11.0.0",
@@ -38,8 +36,8 @@
38
36
  "react-dom": "^18.3.1",
39
37
  "typescript": "^5.5.4",
40
38
  "ws": "^8.18.0",
41
- "@platformatic/composer": "2.19.0-alpha.2",
42
- "@platformatic/service": "2.19.0-alpha.2"
39
+ "@platformatic/composer": "2.19.0-alpha.4",
40
+ "@platformatic/service": "2.19.0-alpha.4"
43
41
  },
44
42
  "scripts": {
45
43
  "test": "npm run lint && borp --concurrency=1 --no-timeout",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/next/2.19.0-alpha.2.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/next/2.19.0-alpha.4.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Next.js Stackable",
5
5
  "type": "object",
@@ -296,41 +296,6 @@
296
296
  },
297
297
  "additionalProperties": false,
298
298
  "default": {}
299
- },
300
- "cache": {
301
- "type": "object",
302
- "properties": {
303
- "adapter": {
304
- "type": "string",
305
- "enum": [
306
- "redis",
307
- "valkey"
308
- ]
309
- },
310
- "url": {
311
- "type": "string"
312
- },
313
- "prefix": {
314
- "type": "string"
315
- },
316
- "maxTTL": {
317
- "default": 604800,
318
- "anyOf": [
319
- {
320
- "type": "number",
321
- "minimum": 0
322
- },
323
- {
324
- "type": "string"
325
- }
326
- ]
327
- }
328
- },
329
- "required": [
330
- "adapter",
331
- "url"
332
- ],
333
- "additionalProperties": false
334
299
  }
335
300
  },
336
301
  "additionalProperties": false
@@ -1,241 +0,0 @@
1
- import { ensureLoggableError } from '@platformatic/utils'
2
- import { Redis } from 'iovalkey'
3
- import { pack, unpack } from 'msgpackr'
4
- import { existsSync, readFileSync } from 'node:fs'
5
- import { hostname } from 'node:os'
6
- import { resolve } from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
- import { pino } from 'pino'
9
-
10
- export const MAX_BATCH_SIZE = 100
11
-
12
- const sections = {
13
- values: 'values',
14
- tags: 'tags'
15
- }
16
-
17
- const kReferences = Symbol('references')
18
- const clients = new Map()
19
-
20
- export function keyFor (prefix, subprefix, section, key) {
21
- return [prefix, 'cache:next', subprefix, section, key ? Buffer.from(key).toString('base64url') : undefined]
22
- .filter(c => c)
23
- .join(':')
24
- }
25
-
26
- export function getConnection (url) {
27
- let client = clients.get(url)
28
-
29
- if (!client) {
30
- client = new Redis(url, { enableAutoPipelining: true })
31
- client[kReferences] = 0
32
- clients.set(url, client)
33
- }
34
-
35
- client[kReferences]++
36
- return client
37
- }
38
-
39
- export function releaseConnection (url) {
40
- const client = clients.get(url)
41
-
42
- if (!client) {
43
- return
44
- }
45
-
46
- client[kReferences]--
47
-
48
- if (client[kReferences] < 1) {
49
- client.disconnect(false)
50
- clients.remove(url)
51
- }
52
- }
53
-
54
- export class CacheHandler {
55
- #config
56
- #logger
57
- #store
58
- #subprefix
59
- #maxTTL
60
-
61
- constructor () {
62
- this.#logger = this.#createLogger()
63
- this.#config = globalThis.platformatic.config.cache
64
- this.#store = getConnection(this.#config.url)
65
- this.#maxTTL = this.#config.maxTTL
66
- this.#subprefix = this.#getSubprefix()
67
-
68
- // Handle disconnection not to hang the process on exit
69
- globalThis.platformatic.events.on('plt:next:close', () => {
70
- releaseConnection(this.#config.url)
71
- })
72
- }
73
-
74
- async get (cacheKey) {
75
- this.#logger.trace({ key: cacheKey }, 'get')
76
-
77
- const key = this.#keyFor(cacheKey, sections.values)
78
-
79
- let rawValue
80
- try {
81
- rawValue = await this.#store.get(key)
82
-
83
- if (!rawValue) {
84
- return
85
- }
86
- } catch (e) {
87
- this.#logger.error({ err: ensureLoggableError(e) }, 'Cannot read cache value from Valkey')
88
- throw new Error('Cannot read cache value from Valkey', { cause: e })
89
- }
90
-
91
- let value
92
- try {
93
- value = this.#deserialize(rawValue)
94
- } catch (e) {
95
- this.#logger.error({ err: ensureLoggableError(e) }, 'Cannot deserialize cache value from Valkey')
96
-
97
- // Avoid useless reads the next time
98
- // Note that since the value was unserializable, we don't know its tags and thus
99
- // we cannot remove it from the tags sets. TTL will take care of them.
100
- await this.#store.del(key)
101
-
102
- throw new Error('Cannot deserialize cache value from Valkey', { cause: e })
103
- }
104
-
105
- if (this.#maxTTL < value.revalidate) {
106
- try {
107
- await this.#refreshKey(key, value)
108
- } catch (e) {
109
- this.#logger.error({ err: ensureLoggableError(e) }, 'Cannot refresh cache key expiration in Valkey')
110
-
111
- // We don't throw here since we want to use the cached value anyway
112
- }
113
- }
114
-
115
- return value
116
- }
117
-
118
- async set (cacheKey, value, { tags, revalidate }) {
119
- this.#logger.trace({ key: cacheKey, value, tags, revalidate }, 'set')
120
-
121
- try {
122
- // Compute the parameters to save
123
- const key = this.#keyFor(cacheKey, sections.values)
124
- const data = this.#serialize({ value, tags, lastModified: Date.now(), revalidate, maxTTL: this.#maxTTL })
125
- const expire = Math.min(revalidate, this.#maxTTL)
126
-
127
- // Enqueue all the operations to perform in Valkey
128
- const promises = []
129
- promises.push(this.#store.set(key, data, 'EX', expire))
130
-
131
- // As Next.js limits tags to 64, we don't need to manage batches here
132
- if (Array.isArray(tags)) {
133
- for (const tag of tags) {
134
- const tagsKey = this.#keyFor(tag, sections.tags)
135
- promises.push(this.#store.sadd(tagsKey, key))
136
- promises.push(this.#store.expire(tagsKey, expire))
137
- }
138
- }
139
-
140
- // Execute all the operations
141
- await Promise.all(promises)
142
- } catch (e) {
143
- this.#logger.error({ err: ensureLoggableError(e) }, 'Cannot write cache value in Valkey')
144
- throw new Error('Cannot write cache value in Valkey', { cause: e })
145
- }
146
- }
147
-
148
- async revalidateTag (tags) {
149
- this.#logger.trace({ tags }, 'revalidateTag')
150
-
151
- if (typeof tags === 'string') {
152
- tags = [tags]
153
- }
154
-
155
- try {
156
- let promises = []
157
-
158
- for (const tag of tags) {
159
- const tagsKey = this.#keyFor(tag, sections.tags)
160
-
161
- // For each key in the tag set, expire the key
162
- for await (const keys of this.#store.sscanStream(tagsKey)) {
163
- for (const key of keys) {
164
- promises.push(this.#store.del(key))
165
-
166
- // Batch full, execute it
167
- if (promises.length >= MAX_BATCH_SIZE) {
168
- await Promise.all(promises)
169
- promises = []
170
- }
171
- }
172
- }
173
-
174
- // Delete the set, this will also take care of executing pending operation for a non full batch
175
- promises.push(this.#store.del(tagsKey))
176
- await Promise.all(promises)
177
- promises = []
178
- }
179
- } catch (e) {
180
- this.#logger.error({ err: ensureLoggableError(e) }, 'Cannot expire cache tags in Valkey')
181
- throw new Error('Cannot expire cache tags in Valkey', { cause: e })
182
- }
183
- }
184
-
185
- async #refreshKey (key, value) {
186
- const life = Math.round((Date.now() - value.lastModified) / 1000)
187
- const expire = Math.min(value.revalidate - life, this.#maxTTL)
188
-
189
- if (expire > 0) {
190
- const promises = []
191
- promises.push(this.#store.expire(key, expire, 'gt'))
192
-
193
- if (Array.isArray(value.tags)) {
194
- for (const tag of value.tags) {
195
- const tagsKey = this.#keyFor(tag, sections.tags)
196
- promises.push(this.#store.expire(tagsKey, expire, 'gt'))
197
- }
198
- }
199
-
200
- await Promise.all(promises)
201
- }
202
- }
203
-
204
- #createLogger () {
205
- const pinoOptions = {
206
- level: globalThis.platformatic?.logLevel ?? 'info'
207
- }
208
-
209
- if (this.serviceId) {
210
- pinoOptions.name = `cache:${this.serviceId}`
211
- }
212
-
213
- if (typeof globalThis.platformatic.workerId !== 'undefined') {
214
- pinoOptions.base = { pid: process.pid, hostname: hostname(), worker: this.workerId }
215
- }
216
-
217
- return pino(pinoOptions)
218
- }
219
-
220
- #getSubprefix () {
221
- const root = fileURLToPath(globalThis.platformatic.root)
222
-
223
- return existsSync(resolve(root, '.next/BUILD_ID'))
224
- ? (this.#subprefix = readFileSync(resolve(root, '.next/BUILD_ID'), 'utf-8').trim())
225
- : 'development'
226
- }
227
-
228
- #keyFor (key, section) {
229
- return keyFor(this.#config.prefix, this.#subprefix, section, key)
230
- }
231
-
232
- #serialize (data) {
233
- return pack(data).toString('base64url')
234
- }
235
-
236
- #deserialize (data) {
237
- return unpack(Buffer.from(data, 'base64url'))
238
- }
239
- }
240
-
241
- export default CacheHandler