@platformatic/next 3.49.1 → 3.50.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
@@ -122,6 +122,7 @@ export interface PlatformaticNextJsConfig {
122
122
  entrypointPort?: number;
123
123
  changeDirectoryBeforeExecution?: boolean;
124
124
  preferLocalCommands?: boolean;
125
+ processSpawner?: string;
125
126
  };
126
127
  runtime?: {
127
128
  preload?: string | string[];
@@ -703,5 +704,10 @@ export interface PlatformaticNextJsConfig {
703
704
  cacheComponents?: boolean;
704
705
  maxTTL?: number | string;
705
706
  ignoreNextConfig?: boolean | string;
707
+ remote?: {
708
+ url?: string;
709
+ prefix?: string;
710
+ maxTTL?: number | string;
711
+ };
706
712
  };
707
713
  }
package/index.js CHANGED
@@ -48,8 +48,17 @@ function enhanceNextCacheConfig (nextConfig, modifications) {
48
48
  nextConfig.cacheComponents = true
49
49
  nextConfig.cacheHandler = getCacheHandlerPath('null-isr')
50
50
  nextConfig.cacheHandlers = { default: getCacheHandlerPath(`${config.cache.adapter}-components`) }
51
+
52
+ if (config.cache.remote) {
53
+ nextConfig.cacheHandlers.remote = getCacheHandlerPath(`${config.cache.adapter}-components-remote`)
54
+ }
55
+
51
56
  nextConfig.cacheMaxMemorySize = 0
52
57
  modifications.push(['componentsCache', config.cache.adapter])
58
+
59
+ if (config.cache.remote) {
60
+ modifications.push(['remoteComponentsCache', config.cache.adapter])
61
+ }
53
62
  } else {
54
63
  delete nextConfig.cacheHandlers
55
64
  nextConfig.cacheHandler = getCacheHandlerPath(`${config.cache.adapter}-isr`)
@@ -0,0 +1,22 @@
1
+ import { CacheHandler } from './valkey-components.js'
2
+
3
+ export const CACHE_HIT_METRIC = {
4
+ name: 'next_remote_components_cache_valkey_hit_count',
5
+ help: 'Next.js Remote Components Cache (Valkey) Hit Count'
6
+ }
7
+ export const CACHE_MISS_METRIC = {
8
+ name: 'next_remote_components_cache_valkey_miss_count',
9
+ help: 'Next.js Remote Components Cache (Valkey) Miss Count'
10
+ }
11
+
12
+ export const sections = {
13
+ values: 'remote:components:values',
14
+ tags: 'remote:components:tags'
15
+ }
16
+
17
+ export default new CacheHandler({
18
+ configKey: 'remote',
19
+ sections,
20
+ cacheHitMetric: CACHE_HIT_METRIC,
21
+ cacheMissMetric: CACHE_MISS_METRIC
22
+ })
@@ -35,20 +35,30 @@ export class CacheHandler {
35
35
  #subprefix
36
36
  #meta
37
37
  #maxTTL
38
+ #sections
38
39
  #cacheHitMetric
39
40
  #cacheMissMetric
41
+ #cacheHitMetricDef
42
+ #cacheMissMetricDef
40
43
 
41
- constructor () {
44
+ constructor (options) {
45
+ options ??= {}
42
46
  ensureRedis()
43
47
  ensureMsgpackr()
44
48
 
45
- this.#config ??= globalThis.platformatic.config.cache
49
+ const baseConfig = globalThis.platformatic.config.cache
50
+ const resolvedConfig = options.configKey ? { ...baseConfig, ...baseConfig[options.configKey] } : baseConfig
51
+
52
+ this.#config ??= resolvedConfig
46
53
  this.#logger ??= createPlatformaticLogger()
47
54
  this.#store ??= getConnection(this.#config.url)
48
55
  this.#maxTTL ??= this.#config.maxTTL
49
56
  this.#prefix ??= this.#config.prefix
50
57
  this.#subprefix ??= getPlatformaticSubprefix()
51
58
  this.#meta ??= getPlatformaticMeta()
59
+ this.#sections = options.sections ?? sections
60
+ this.#cacheHitMetricDef = options.cacheHitMetric ?? CACHE_HIT_METRIC
61
+ this.#cacheMissMetricDef = options.cacheMissMetric ?? CACHE_MISS_METRIC
52
62
 
53
63
  if (!this.#config) {
54
64
  throw new Error('Please provide a the "config" option.')
@@ -70,7 +80,7 @@ export class CacheHandler {
70
80
  async get (cacheKey, _, isRedisKey) {
71
81
  this.#logger.trace({ key: cacheKey }, 'cache get')
72
82
 
73
- const key = isRedisKey ? cacheKey : this.#keyFor(cacheKey, sections.values)
83
+ const key = isRedisKey ? cacheKey : this.#keyFor(cacheKey, this.#sections.values)
74
84
 
75
85
  let rawValue
76
86
  try {
@@ -131,7 +141,7 @@ export class CacheHandler {
131
141
 
132
142
  this.#logger.trace({ key: cacheKey, value, tags, revalidate }, 'cache set')
133
143
 
134
- const key = isRedisKey ? cacheKey : this.#keyFor(cacheKey, sections.values)
144
+ const key = isRedisKey ? cacheKey : this.#keyFor(cacheKey, this.#sections.values)
135
145
 
136
146
  try {
137
147
  // Gather the value
@@ -147,7 +157,11 @@ export class CacheHandler {
147
157
  value: Buffer.concat(chunks),
148
158
  ...data
149
159
  })
150
- const expire = Math.min(revalidate, expireSec, this.#maxTTL)
160
+ // revalidate === false means "cache forever" in Next.js (SSG/force-static pages).
161
+ // Use maxTTL as the expiration in that case.
162
+ const effectiveRevalidate = revalidate === false ? this.#maxTTL : revalidate
163
+ const effectiveExpire = expireSec === false ? this.#maxTTL : expireSec
164
+ const expire = Math.min(effectiveRevalidate, effectiveExpire, this.#maxTTL)
151
165
 
152
166
  if (expire < 1) {
153
167
  return
@@ -160,7 +174,7 @@ export class CacheHandler {
160
174
  // As Next.js limits tags to 64, we don't need to manage batches here
161
175
  if (Array.isArray(tags)) {
162
176
  for (const tag of tags) {
163
- const tagsKey = this.#keyFor(tag, sections.tags)
177
+ const tagsKey = this.#keyFor(tag, this.#sections.tags)
164
178
  promises.push(this.#store.sadd(tagsKey, key))
165
179
  promises.push(this.#store.expire(tagsKey, expire))
166
180
  }
@@ -191,7 +205,7 @@ export class CacheHandler {
191
205
  const toDelete = new Set()
192
206
 
193
207
  for (const tag of tags) {
194
- const tagsKey = this.#keyFor(tag, sections.tags)
208
+ const tagsKey = this.#keyFor(tag, this.#sections.tags)
195
209
 
196
210
  // For each key in the tag set, expire the key
197
211
  for await (const keys of this.#store.sscanStream(tagsKey)) {
@@ -228,7 +242,7 @@ export class CacheHandler {
228
242
 
229
243
  if (Array.isArray(value.tags)) {
230
244
  for (const tag of value.tags) {
231
- const tagsKey = this.#keyFor(tag, sections.tags)
245
+ const tagsKey = this.#keyFor(tag, this.#sections.tags)
232
246
  promises.push(this.#store.expire(tagsKey, expire, 'gt'))
233
247
  }
234
248
  }
@@ -244,18 +258,18 @@ export class CacheHandler {
244
258
  const { client, registry } = globalThis.platformatic.prometheus
245
259
 
246
260
  this.#cacheHitMetric =
247
- registry.getSingleMetric(CACHE_HIT_METRIC.name) ??
261
+ registry.getSingleMetric(this.#cacheHitMetricDef.name) ??
248
262
  new client.Counter({
249
- name: CACHE_HIT_METRIC.name,
250
- help: CACHE_HIT_METRIC.help,
263
+ name: this.#cacheHitMetricDef.name,
264
+ help: this.#cacheHitMetricDef.help,
251
265
  registers: [registry]
252
266
  })
253
267
 
254
268
  this.#cacheMissMetric =
255
- registry.getSingleMetric(CACHE_MISS_METRIC.name) ??
269
+ registry.getSingleMetric(this.#cacheMissMetricDef.name) ??
256
270
  new client.Counter({
257
- name: CACHE_MISS_METRIC.name,
258
- help: CACHE_MISS_METRIC.help,
271
+ name: this.#cacheMissMetricDef.name,
272
+ help: this.#cacheMissMetricDef.help,
259
273
  registers: [registry]
260
274
  })
261
275
  }
@@ -146,7 +146,9 @@ export class CacheHandler {
146
146
  maxTTL: this.#maxTTL,
147
147
  ...this.#meta
148
148
  })
149
- const expire = Math.min(revalidate, this.#maxTTL)
149
+ // revalidate === false means "cache forever" in Next.js (SSG/force-static pages).
150
+ // Use maxTTL as the expiration in that case.
151
+ const expire = revalidate === false ? this.#maxTTL : Math.min(revalidate, this.#maxTTL)
150
152
 
151
153
  if (expire < 1) {
152
154
  return
package/lib/capability.js CHANGED
@@ -400,6 +400,10 @@ export class NextCapability extends BaseCapability {
400
400
  } else if (pltNextModifications.componentsCache) {
401
401
  nextConfig.cacheHandler = getCacheHandlerPath('null-isr')
402
402
  nextConfig.cacheHandlers.default = getCacheHandlerPath(`${pltNextModifications.componentsCache}-components`)
403
+
404
+ if (pltNextModifications.remoteComponentsCache) {
405
+ nextConfig.cacheHandlers.remote = getCacheHandlerPath(`${pltNextModifications.remoteComponentsCache}-components-remote`)
406
+ }
403
407
  }
404
408
  }
405
409
 
@@ -501,8 +505,24 @@ export class NextCapability extends BaseCapability {
501
505
  const requiredServerFilesPath = resolvePath(distDir, 'required-server-files.json')
502
506
  const requiredServerFiles = JSON.parse(await readFile(requiredServerFilesPath, 'utf-8'))
503
507
 
508
+ let modified = false
509
+
504
510
  if (requiredServerFiles.config.cacheHandler) {
505
511
  requiredServerFiles.config.cacheHandler = resolvePath(distDir, requiredServerFiles.config.cacheHandler)
512
+ modified = true
513
+ }
514
+
515
+ if (requiredServerFiles.config.cacheHandlers?.default) {
516
+ requiredServerFiles.config.cacheHandlers.default = resolvePath(distDir, requiredServerFiles.config.cacheHandlers.default)
517
+ modified = true
518
+ }
519
+
520
+ if (requiredServerFiles.config.cacheHandlers?.remote) {
521
+ requiredServerFiles.config.cacheHandlers.remote = resolvePath(distDir, requiredServerFiles.config.cacheHandlers.remote)
522
+ modified = true
523
+ }
524
+
525
+ if (modified) {
506
526
  await writeFile(requiredServerFilesPath, JSON.stringify(requiredServerFiles, null, 2))
507
527
  }
508
528
  }
package/lib/schema.js CHANGED
@@ -53,6 +53,29 @@ export const cache = {
53
53
  type: 'string'
54
54
  }
55
55
  ]
56
+ },
57
+ remote: {
58
+ type: 'object',
59
+ properties: {
60
+ url: {
61
+ type: 'string'
62
+ },
63
+ prefix: {
64
+ type: 'string'
65
+ },
66
+ maxTTL: {
67
+ anyOf: [
68
+ {
69
+ type: 'number',
70
+ minimum: 0
71
+ },
72
+ {
73
+ type: 'string'
74
+ }
75
+ ]
76
+ }
77
+ },
78
+ additionalProperties: false
56
79
  }
57
80
  },
58
81
  required: ['adapter', 'url'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/next",
3
- "version": "3.49.1",
3
+ "version": "3.50.0",
4
4
  "description": "Platformatic Next.js Capability",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -25,8 +25,8 @@
25
25
  "light-my-request": "^6.0.0",
26
26
  "msgpackr": "^1.11.2",
27
27
  "semver": "^7.6.3",
28
- "@platformatic/basic": "3.49.1",
29
- "@platformatic/foundation": "3.49.1"
28
+ "@platformatic/foundation": "3.50.0",
29
+ "@platformatic/basic": "3.50.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@fastify/reply-from": "^12.0.0",
@@ -43,8 +43,8 @@
43
43
  "next": "^16.1.0",
44
44
  "typescript": "^5.5.4",
45
45
  "ws": "^8.18.0",
46
- "@platformatic/gateway": "3.49.1",
47
- "@platformatic/service": "3.49.1"
46
+ "@platformatic/gateway": "3.50.0",
47
+ "@platformatic/service": "3.50.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/next/3.49.1.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/next/3.50.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Next.js Config",
5
5
  "type": "object",
@@ -393,6 +393,9 @@
393
393
  "preferLocalCommands": {
394
394
  "type": "boolean",
395
395
  "default": true
396
+ },
397
+ "processSpawner": {
398
+ "type": "string"
396
399
  }
397
400
  },
398
401
  "additionalProperties": false,
@@ -2682,6 +2685,29 @@
2682
2685
  "type": "string"
2683
2686
  }
2684
2687
  ]
2688
+ },
2689
+ "remote": {
2690
+ "type": "object",
2691
+ "properties": {
2692
+ "url": {
2693
+ "type": "string"
2694
+ },
2695
+ "prefix": {
2696
+ "type": "string"
2697
+ },
2698
+ "maxTTL": {
2699
+ "anyOf": [
2700
+ {
2701
+ "type": "number",
2702
+ "minimum": 0
2703
+ },
2704
+ {
2705
+ "type": "string"
2706
+ }
2707
+ ]
2708
+ }
2709
+ },
2710
+ "additionalProperties": false
2685
2711
  }
2686
2712
  },
2687
2713
  "required": [