@platformatic/next 3.39.0 → 3.40.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 +26 -0
- package/index.js +5 -1
- package/lib/caching/valkey-common.js +15 -14
- package/lib/caching/valkey-components.js +5 -0
- package/lib/caching/valkey-isr.js +5 -0
- package/lib/capability.js +1 -1
- package/lib/image-optimizer.js +283 -0
- package/lib/schema.js +101 -0
- package/package.json +8 -6
- package/schema.json +161 -1
package/config.d.ts
CHANGED
|
@@ -332,6 +332,12 @@ export interface PlatformaticNextJsConfig {
|
|
|
332
332
|
*/
|
|
333
333
|
socket?: string;
|
|
334
334
|
};
|
|
335
|
+
management?:
|
|
336
|
+
| boolean
|
|
337
|
+
| {
|
|
338
|
+
enabled?: boolean;
|
|
339
|
+
operations?: string[];
|
|
340
|
+
};
|
|
335
341
|
metrics?:
|
|
336
342
|
| boolean
|
|
337
343
|
| {
|
|
@@ -662,6 +668,26 @@ export interface PlatformaticNextJsConfig {
|
|
|
662
668
|
cert?: string;
|
|
663
669
|
ca?: string;
|
|
664
670
|
};
|
|
671
|
+
imageOptimizer?: {
|
|
672
|
+
enabled: boolean;
|
|
673
|
+
fallback: string;
|
|
674
|
+
storage?:
|
|
675
|
+
| {
|
|
676
|
+
type: "memory";
|
|
677
|
+
}
|
|
678
|
+
| {
|
|
679
|
+
type: "filesystem";
|
|
680
|
+
path?: string;
|
|
681
|
+
}
|
|
682
|
+
| {
|
|
683
|
+
type: "redis" | "valkey";
|
|
684
|
+
url: string;
|
|
685
|
+
prefix?: string;
|
|
686
|
+
db?: number | string;
|
|
687
|
+
};
|
|
688
|
+
timeout?: number | string;
|
|
689
|
+
maxAttempts?: number | string;
|
|
690
|
+
};
|
|
665
691
|
};
|
|
666
692
|
cache?: {
|
|
667
693
|
enabled?: boolean | string;
|
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { transform as basicTransform, resolve, validationOptions } from '@platfo
|
|
|
2
2
|
import { kMetadata, loadConfiguration as utilsLoadConfiguration } from '@platformatic/foundation'
|
|
3
3
|
import { resolve as resolvePath } from 'node:path'
|
|
4
4
|
import { getCacheHandlerPath, NextCapability } from './lib/capability.js'
|
|
5
|
+
import { NextImageOptimizerCapability } from './lib/image-optimizer.js'
|
|
5
6
|
import { schema } from './lib/schema.js'
|
|
6
7
|
|
|
7
8
|
/* c8 ignore next 9 */
|
|
@@ -103,9 +104,12 @@ export async function loadConfiguration (configOrRoot, sourceOrConfig, context)
|
|
|
103
104
|
|
|
104
105
|
export async function create (configOrRoot, sourceOrConfig, context) {
|
|
105
106
|
const config = await loadConfiguration(configOrRoot, sourceOrConfig, context)
|
|
106
|
-
|
|
107
|
+
|
|
108
|
+
const Capability = config.next?.imageOptimizer?.enabled ? NextImageOptimizerCapability : NextCapability
|
|
109
|
+
return new Capability(config[kMetadata].root, config, context)
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
export * from './lib/capability.js'
|
|
110
113
|
export * as errors from './lib/errors.js'
|
|
114
|
+
export * from './lib/image-optimizer.js'
|
|
111
115
|
export { packageJson, schema, schemaComponents, version } from './lib/schema.js'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildPinoFormatters, buildPinoTimestamp } from '@platformatic/foundation'
|
|
2
|
-
import { createRequire } from 'node:module'
|
|
3
2
|
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
4
|
import { hostname } from 'node:os'
|
|
5
5
|
import { resolve } from 'node:path'
|
|
6
6
|
import { fileURLToPath } from 'node:url'
|
|
@@ -12,13 +12,6 @@ const require = createRequire(import.meta.url)
|
|
|
12
12
|
let Redis
|
|
13
13
|
let msgpackr
|
|
14
14
|
|
|
15
|
-
function loadMsgpackr () {
|
|
16
|
-
if (!msgpackr) {
|
|
17
|
-
msgpackr = require('msgpackr')
|
|
18
|
-
}
|
|
19
|
-
return msgpackr
|
|
20
|
-
}
|
|
21
|
-
|
|
22
15
|
globalThis.platformatic ??= {}
|
|
23
16
|
globalThis.platformatic.valkeyClients = new Map()
|
|
24
17
|
|
|
@@ -42,14 +35,22 @@ export function keyFor (prefix, subprefix, section, key) {
|
|
|
42
35
|
return result
|
|
43
36
|
}
|
|
44
37
|
|
|
38
|
+
export function ensureRedis () {
|
|
39
|
+
if (!Redis) {
|
|
40
|
+
Redis = require('iovalkey').Redis
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ensureMsgpackr () {
|
|
45
|
+
if (!msgpackr) {
|
|
46
|
+
msgpackr = require('msgpackr')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
export function getConnection (url) {
|
|
46
51
|
let client = globalThis.platformatic.valkeyClients.get(url)
|
|
47
52
|
|
|
48
53
|
if (!client) {
|
|
49
|
-
if (!Redis) {
|
|
50
|
-
Redis = require('iovalkey').Redis
|
|
51
|
-
loadMsgpackr()
|
|
52
|
-
}
|
|
53
54
|
client = new Redis(url, { enableAutoPipelining: true })
|
|
54
55
|
globalThis.platformatic.valkeyClients.set(url, client)
|
|
55
56
|
|
|
@@ -109,9 +110,9 @@ export function getPlatformaticMeta () {
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
export function serialize (data) {
|
|
112
|
-
return
|
|
113
|
+
return msgpackr.pack(data).toString('base64url')
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
export function deserialize (data) {
|
|
116
|
-
return
|
|
117
|
+
return msgpackr.unpack(Buffer.from(data, 'base64url'))
|
|
117
118
|
}
|
|
@@ -3,6 +3,8 @@ import { ReadableStream } from 'node:stream/web'
|
|
|
3
3
|
import {
|
|
4
4
|
createPlatformaticLogger,
|
|
5
5
|
deserialize,
|
|
6
|
+
ensureMsgpackr,
|
|
7
|
+
ensureRedis,
|
|
6
8
|
getConnection,
|
|
7
9
|
getPlatformaticMeta,
|
|
8
10
|
getPlatformaticSubprefix,
|
|
@@ -37,6 +39,9 @@ export class CacheHandler {
|
|
|
37
39
|
#cacheMissMetric
|
|
38
40
|
|
|
39
41
|
constructor () {
|
|
42
|
+
ensureRedis()
|
|
43
|
+
ensureMsgpackr()
|
|
44
|
+
|
|
40
45
|
this.#config ??= globalThis.platformatic.config.cache
|
|
41
46
|
this.#logger ??= createPlatformaticLogger()
|
|
42
47
|
this.#store ??= getConnection(this.#config.url)
|
|
@@ -2,6 +2,8 @@ import { ensureLoggableError } from '@platformatic/foundation'
|
|
|
2
2
|
import {
|
|
3
3
|
createPlatformaticLogger,
|
|
4
4
|
deserialize,
|
|
5
|
+
ensureMsgpackr,
|
|
6
|
+
ensureRedis,
|
|
5
7
|
getConnection,
|
|
6
8
|
getPlatformaticMeta,
|
|
7
9
|
getPlatformaticSubprefix,
|
|
@@ -33,6 +35,9 @@ export class CacheHandler {
|
|
|
33
35
|
constructor (options) {
|
|
34
36
|
options ??= {}
|
|
35
37
|
|
|
38
|
+
ensureRedis()
|
|
39
|
+
ensureMsgpackr()
|
|
40
|
+
|
|
36
41
|
this.#standalone = options.standalone
|
|
37
42
|
this.#config = options.config
|
|
38
43
|
this.#logger = options.logger
|
package/lib/capability.js
CHANGED
|
@@ -20,7 +20,7 @@ import { parse, satisfies } from 'semver'
|
|
|
20
20
|
import * as errors from './errors.js'
|
|
21
21
|
import { version } from './schema.js'
|
|
22
22
|
|
|
23
|
-
const supportedVersions = ['^14.0.0', '^15.0.0', '^16.0.0']
|
|
23
|
+
export const supportedVersions = ['^14.0.0', '^15.0.0', '^16.0.0']
|
|
24
24
|
|
|
25
25
|
export function getCacheHandlerPath (name) {
|
|
26
26
|
return fileURLToPath(new URL(`./caching/${name}.js`, import.meta.url)).replaceAll(sep, '/')
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseCapability,
|
|
3
|
+
errors as basicErrors,
|
|
4
|
+
createServerListener,
|
|
5
|
+
getServerUrl,
|
|
6
|
+
importFile,
|
|
7
|
+
resolvePackageViaCJS
|
|
8
|
+
} from '@platformatic/basic'
|
|
9
|
+
import { cleanBasePath } from '@platformatic/basic/lib/utils.js'
|
|
10
|
+
import { ensureLoggableError } from '@platformatic/foundation/lib/errors.js'
|
|
11
|
+
import { createQueue } from '@platformatic/image-optimizer'
|
|
12
|
+
import { FileStorage, MemoryStorage, RedisStorage } from '@platformatic/job-queue'
|
|
13
|
+
import inject from 'light-my-request'
|
|
14
|
+
import { readFile } from 'node:fs/promises'
|
|
15
|
+
import { createServer } from 'node:http'
|
|
16
|
+
import { dirname, resolve as resolvePath } from 'node:path'
|
|
17
|
+
import { satisfies } from 'semver'
|
|
18
|
+
import { supportedVersions } from './capability.js'
|
|
19
|
+
import { version } from './schema.js'
|
|
20
|
+
|
|
21
|
+
export class NextImageOptimizerCapability extends BaseCapability {
|
|
22
|
+
#basePath
|
|
23
|
+
#fallbackDomain
|
|
24
|
+
#next
|
|
25
|
+
#nextConfig
|
|
26
|
+
#validateParams
|
|
27
|
+
#app
|
|
28
|
+
#server
|
|
29
|
+
#dispatcher
|
|
30
|
+
#queue
|
|
31
|
+
#fetchTimeout
|
|
32
|
+
|
|
33
|
+
constructor (root, config, context) {
|
|
34
|
+
super('next-image', version, root, config, context)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async init () {
|
|
38
|
+
await super.init()
|
|
39
|
+
|
|
40
|
+
// This is needed to avoid Next.js to throw an error when the lockfile is not correct
|
|
41
|
+
// and the user is using npm but has pnpm in its $PATH.
|
|
42
|
+
//
|
|
43
|
+
// See: https://github.com/platformatic/composer-next-node-fastify/pull/3
|
|
44
|
+
//
|
|
45
|
+
// PS by Paolo: Sob.
|
|
46
|
+
process.env.NEXT_IGNORE_INCORRECT_LOCKFILE = 'true'
|
|
47
|
+
|
|
48
|
+
this.#next = resolvePath(dirname(await resolvePackageViaCJS(this.root, 'next')), '../..')
|
|
49
|
+
const nextPackage = JSON.parse(await readFile(resolvePath(this.#next, 'package.json'), 'utf-8'))
|
|
50
|
+
|
|
51
|
+
/* c8 ignore next 3 */
|
|
52
|
+
if (!this.isProduction && !supportedVersions.some(v => satisfies(nextPackage.version, v))) {
|
|
53
|
+
throw new basicErrors.UnsupportedVersion('next', nextPackage.version, supportedVersions)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const imageOptimizerConfig = this.config.next?.imageOptimizer ?? {}
|
|
57
|
+
|
|
58
|
+
this.#fetchTimeout = imageOptimizerConfig?.timeout ?? 30000
|
|
59
|
+
this.#fallbackDomain = this.config.next?.imageOptimizer?.fallback
|
|
60
|
+
|
|
61
|
+
// If it's not a full URL, it's a local service
|
|
62
|
+
if (!this.#fallbackDomain.startsWith('http://') && !this.#fallbackDomain.startsWith('https://')) {
|
|
63
|
+
this.#fallbackDomain = `http://${this.#fallbackDomain}.plt.local`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (this.#fallbackDomain.endsWith('/')) {
|
|
67
|
+
this.#fallbackDomain = this.#fallbackDomain.slice(0, -1)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.#queue = await this.#createQueue(imageOptimizerConfig)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async start ({ listen }) {
|
|
74
|
+
// Make this idempotent
|
|
75
|
+
if (this.url) {
|
|
76
|
+
return this.url
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const config = this.config
|
|
80
|
+
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
81
|
+
await super._start({ listen })
|
|
82
|
+
|
|
83
|
+
if (this.#app && listen) {
|
|
84
|
+
const serverOptions = this.serverConfig
|
|
85
|
+
const listenOptions = { host: serverOptions?.hostname || '127.0.0.1', port: serverOptions?.port || 0 }
|
|
86
|
+
|
|
87
|
+
if (typeof serverOptions?.backlog === 'number') {
|
|
88
|
+
createServerListener(false, false, { backlog: serverOptions.backlog })
|
|
89
|
+
listenOptions.backlog = serverOptions.backlog
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.#server = await new Promise((resolve, reject) => {
|
|
93
|
+
return this.#app
|
|
94
|
+
.listen(listenOptions, function () {
|
|
95
|
+
resolve(this)
|
|
96
|
+
})
|
|
97
|
+
.on('error', reject)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
this.url = getServerUrl(this.#server)
|
|
101
|
+
|
|
102
|
+
return this.url
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { ImageOptimizerCache } = await importFile(resolvePath(this.#next, './dist/server/image-optimizer.js'))
|
|
106
|
+
this.#validateParams = ImageOptimizerCache.validateParams.bind(ImageOptimizerCache)
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const { default: loadConfigAPI } = await importFile(resolvePath(this.#next, './dist/server/config.js'))
|
|
110
|
+
this.#nextConfig = await loadConfigAPI.default('production', this.root)
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.logger.error({ err: ensureLoggableError(error) }, 'Error loading Next.js configuration.')
|
|
113
|
+
throw new Error('Failed to load Next.js configuration.', { cause: error })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.#app = createServer(this.#handleRequest.bind(this))
|
|
117
|
+
this.#dispatcher = this.#app.listeners('request')[0]
|
|
118
|
+
await this.#queue.start()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async stop () {
|
|
122
|
+
await super.stop()
|
|
123
|
+
await this.#queue?.stop()
|
|
124
|
+
|
|
125
|
+
globalThis.platformatic.events.emit('plt:next:close')
|
|
126
|
+
|
|
127
|
+
if (!this.#app || !this.#server?.listening) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
const { promise, resolve, reject } = Promise.withResolvers()
|
|
131
|
+
|
|
132
|
+
this.#server.close(error => {
|
|
133
|
+
if (error) {
|
|
134
|
+
return reject(error)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
resolve()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return promise
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* c8 ignore next 5 */
|
|
144
|
+
async getWatchConfig () {
|
|
145
|
+
return {
|
|
146
|
+
enabled: false,
|
|
147
|
+
path: this.root
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getMeta () {
|
|
152
|
+
const gateway = { prefix: this.basePath ?? this.#basePath, wantsAbsoluteUrls: true, needsRootTrailingSlash: false }
|
|
153
|
+
|
|
154
|
+
if (this.url) {
|
|
155
|
+
gateway.tcp = true
|
|
156
|
+
gateway.url = this.url
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { gateway }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async inject (injectParams, onInject) {
|
|
163
|
+
this.logger.trace({ injectParams }, 'injecting via light-my-request')
|
|
164
|
+
const res = inject(this.#dispatcher ?? this.#app, injectParams, onInject)
|
|
165
|
+
|
|
166
|
+
/* c8 ignore next 3 */
|
|
167
|
+
if (onInject) {
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Since inject might be called from the main thread directly via ITC, let's clean it up
|
|
172
|
+
const { statusCode, headers, body, payload, rawPayload } = res
|
|
173
|
+
|
|
174
|
+
return { statusCode, headers, body, payload, rawPayload }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async #createQueue (imageOptimizerConfig) {
|
|
178
|
+
const queueOptions = {
|
|
179
|
+
visibilityTimeout: imageOptimizerConfig.timeout,
|
|
180
|
+
maxRetries: imageOptimizerConfig.maxAttempts,
|
|
181
|
+
logger: this.logger
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (imageOptimizerConfig.storage.type === 'memory') {
|
|
185
|
+
queueOptions.storage = new MemoryStorage()
|
|
186
|
+
} else if (imageOptimizerConfig.storage.type === 'filesystem') {
|
|
187
|
+
queueOptions.storage = new FileStorage({
|
|
188
|
+
basePath: imageOptimizerConfig.storage.path ?? resolvePath(this.root, '.next/cache/image-optimizer')
|
|
189
|
+
})
|
|
190
|
+
} else {
|
|
191
|
+
const redisStorageOptions = {
|
|
192
|
+
url: this.#buildRedisStorageUrl(imageOptimizerConfig.storage.url, imageOptimizerConfig.storage.db)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (typeof imageOptimizerConfig.storage.prefix === 'string') {
|
|
196
|
+
redisStorageOptions.keyPrefix = imageOptimizerConfig.storage.prefix
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
queueOptions.storage = new RedisStorage(redisStorageOptions)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return createQueue(queueOptions)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#buildRedisStorageUrl (url, db) {
|
|
206
|
+
if (typeof db === 'undefined') {
|
|
207
|
+
return url
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const parsedUrl = new URL(url)
|
|
211
|
+
parsedUrl.pathname = `/${db}`
|
|
212
|
+
return parsedUrl.toString()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async #handleRequest (request, response) {
|
|
216
|
+
const { pathname, searchParams } = new URL(request.url, 'http://localhost')
|
|
217
|
+
const imagePath = `${this.#basePath}/_next/image`
|
|
218
|
+
|
|
219
|
+
if (request.method !== 'GET' || pathname !== imagePath) {
|
|
220
|
+
response.statusCode = 404
|
|
221
|
+
response.end('Not Found')
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const query = {}
|
|
226
|
+
|
|
227
|
+
for (const [key, value] of searchParams.entries()) {
|
|
228
|
+
if (typeof query[key] === 'undefined') {
|
|
229
|
+
query[key] = value
|
|
230
|
+
} else if (Array.isArray(query[key])) {
|
|
231
|
+
query[key].push(value)
|
|
232
|
+
} else {
|
|
233
|
+
query[key] = [query[key], value]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
// Extract and validate the parameters
|
|
239
|
+
const params = this.#validateParams(request, query, this.#nextConfig, false)
|
|
240
|
+
|
|
241
|
+
if (params.errorMessage) {
|
|
242
|
+
const error = new Error('Invalid optimization parameters.')
|
|
243
|
+
error.reason = params.errorMessage
|
|
244
|
+
throw error
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let url
|
|
248
|
+
if (params.isAbsolute) {
|
|
249
|
+
url = params.href
|
|
250
|
+
} else {
|
|
251
|
+
url = `${this.#fallbackDomain}${params.href.startsWith('/') ? '' : '/'}${params.href}`
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const result = await this.#queue.fetchAndOptimize(
|
|
255
|
+
url,
|
|
256
|
+
params.width,
|
|
257
|
+
params.quality,
|
|
258
|
+
this.#nextConfig.images.dangerouslyAllowSVG,
|
|
259
|
+
{ timeout: this.#fetchTimeout }
|
|
260
|
+
)
|
|
261
|
+
const buffer = Buffer.from(result.buffer, 'base64')
|
|
262
|
+
|
|
263
|
+
response.statusCode = 200
|
|
264
|
+
response.setHeader('Content-Type', result.contentType)
|
|
265
|
+
response.setHeader('Cache-Control', result.cacheControl)
|
|
266
|
+
response.end(buffer)
|
|
267
|
+
} catch (error) {
|
|
268
|
+
response.statusCode = 502
|
|
269
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
270
|
+
response.end(
|
|
271
|
+
JSON.stringify({
|
|
272
|
+
error: 'Bad Gateway',
|
|
273
|
+
message: 'An error occurred while optimizing the image.',
|
|
274
|
+
statusCode: 502,
|
|
275
|
+
cause: {
|
|
276
|
+
...ensureLoggableError(error.originalError ? JSON.parse(error.originalError) : error),
|
|
277
|
+
stack: undefined
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -97,6 +97,107 @@ const next = {
|
|
|
97
97
|
}
|
|
98
98
|
},
|
|
99
99
|
additionalProperties: false
|
|
100
|
+
},
|
|
101
|
+
imageOptimizer: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
enabled: {
|
|
105
|
+
type: 'boolean',
|
|
106
|
+
default: false
|
|
107
|
+
},
|
|
108
|
+
fallback: {
|
|
109
|
+
type: 'string'
|
|
110
|
+
},
|
|
111
|
+
storage: {
|
|
112
|
+
oneOf: [
|
|
113
|
+
{
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
type: {
|
|
117
|
+
const: 'memory'
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
required: ['type'],
|
|
121
|
+
additionalProperties: false
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
type: {
|
|
127
|
+
const: 'filesystem'
|
|
128
|
+
},
|
|
129
|
+
path: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
resolvePath: true
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
required: ['type'],
|
|
135
|
+
additionalProperties: false
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
type: {
|
|
141
|
+
type: 'string',
|
|
142
|
+
enum: ['redis', 'valkey']
|
|
143
|
+
},
|
|
144
|
+
url: {
|
|
145
|
+
type: 'string'
|
|
146
|
+
},
|
|
147
|
+
prefix: {
|
|
148
|
+
type: 'string'
|
|
149
|
+
},
|
|
150
|
+
db: {
|
|
151
|
+
anyOf: [
|
|
152
|
+
{
|
|
153
|
+
type: 'number',
|
|
154
|
+
minimum: 0,
|
|
155
|
+
default: 0
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'string',
|
|
159
|
+
pattern: '^[0-9]+$'
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
required: ['type', 'url'],
|
|
165
|
+
additionalProperties: false
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
default: {
|
|
169
|
+
type: 'memory'
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
timeout: {
|
|
173
|
+
default: 30000,
|
|
174
|
+
anyOf: [
|
|
175
|
+
{
|
|
176
|
+
type: 'number',
|
|
177
|
+
minimum: 0
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: 'string',
|
|
181
|
+
pattern: '^[0-9]+$'
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
maxAttempts: {
|
|
186
|
+
default: 3,
|
|
187
|
+
anyOf: [
|
|
188
|
+
{
|
|
189
|
+
type: 'number',
|
|
190
|
+
minimum: 1
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
type: 'string',
|
|
194
|
+
pattern: '^[1-9][0-9]*$'
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
required: ['enabled', 'fallback'],
|
|
200
|
+
additionalProperties: false
|
|
100
201
|
}
|
|
101
202
|
},
|
|
102
203
|
default: {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/next",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.40.0",
|
|
4
4
|
"description": "Platformatic Next.js Capability",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
"@babel/parser": "^7.25.3",
|
|
20
20
|
"@babel/traverse": "^7.25.3",
|
|
21
21
|
"@babel/types": "^7.25.2",
|
|
22
|
+
"@platformatic/image-optimizer": "^0.2.0",
|
|
22
23
|
"amaro": "^0.3.0",
|
|
23
24
|
"iovalkey": "^0.3.0",
|
|
25
|
+
"light-my-request": "^6.0.0",
|
|
24
26
|
"msgpackr": "^1.11.2",
|
|
25
27
|
"semver": "^7.6.3",
|
|
26
|
-
"@platformatic/
|
|
27
|
-
"@platformatic/
|
|
28
|
+
"@platformatic/basic": "3.40.0",
|
|
29
|
+
"@platformatic/foundation": "3.40.0"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
30
32
|
"@fastify/reply-from": "^12.0.0",
|
|
@@ -34,14 +36,14 @@
|
|
|
34
36
|
"cleaner-spec-reporter": "^0.5.0",
|
|
35
37
|
"eslint": "9",
|
|
36
38
|
"execa": "^9.5.1",
|
|
37
|
-
"fastify": "^5.7.
|
|
39
|
+
"fastify": "^5.7.3",
|
|
38
40
|
"json-schema-to-typescript": "^15.0.1",
|
|
39
41
|
"neostandard": "^0.12.0",
|
|
40
42
|
"next": "^16.1.0",
|
|
41
43
|
"typescript": "^5.5.4",
|
|
42
44
|
"ws": "^8.18.0",
|
|
43
|
-
"@platformatic/gateway": "3.
|
|
44
|
-
"@platformatic/service": "3.
|
|
45
|
+
"@platformatic/gateway": "3.40.0",
|
|
46
|
+
"@platformatic/service": "3.40.0"
|
|
45
47
|
},
|
|
46
48
|
"engines": {
|
|
47
49
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/next/3.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/next/3.40.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Next.js Config",
|
|
5
5
|
"type": "object",
|
|
@@ -736,6 +736,29 @@
|
|
|
736
736
|
"additionalProperties": false
|
|
737
737
|
}
|
|
738
738
|
]
|
|
739
|
+
},
|
|
740
|
+
"management": {
|
|
741
|
+
"anyOf": [
|
|
742
|
+
{
|
|
743
|
+
"type": "boolean"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"type": "object",
|
|
747
|
+
"properties": {
|
|
748
|
+
"enabled": {
|
|
749
|
+
"type": "boolean",
|
|
750
|
+
"default": true
|
|
751
|
+
},
|
|
752
|
+
"operations": {
|
|
753
|
+
"type": "array",
|
|
754
|
+
"items": {
|
|
755
|
+
"type": "string"
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
"additionalProperties": false
|
|
760
|
+
}
|
|
761
|
+
]
|
|
739
762
|
}
|
|
740
763
|
}
|
|
741
764
|
}
|
|
@@ -1475,6 +1498,29 @@
|
|
|
1475
1498
|
],
|
|
1476
1499
|
"default": true
|
|
1477
1500
|
},
|
|
1501
|
+
"management": {
|
|
1502
|
+
"anyOf": [
|
|
1503
|
+
{
|
|
1504
|
+
"type": "boolean"
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
"type": "object",
|
|
1508
|
+
"properties": {
|
|
1509
|
+
"enabled": {
|
|
1510
|
+
"type": "boolean",
|
|
1511
|
+
"default": true
|
|
1512
|
+
},
|
|
1513
|
+
"operations": {
|
|
1514
|
+
"type": "array",
|
|
1515
|
+
"items": {
|
|
1516
|
+
"type": "string"
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
"additionalProperties": false
|
|
1521
|
+
}
|
|
1522
|
+
]
|
|
1523
|
+
},
|
|
1478
1524
|
"metrics": {
|
|
1479
1525
|
"anyOf": [
|
|
1480
1526
|
{
|
|
@@ -2437,6 +2483,120 @@
|
|
|
2437
2483
|
}
|
|
2438
2484
|
},
|
|
2439
2485
|
"additionalProperties": false
|
|
2486
|
+
},
|
|
2487
|
+
"imageOptimizer": {
|
|
2488
|
+
"type": "object",
|
|
2489
|
+
"properties": {
|
|
2490
|
+
"enabled": {
|
|
2491
|
+
"type": "boolean",
|
|
2492
|
+
"default": false
|
|
2493
|
+
},
|
|
2494
|
+
"fallback": {
|
|
2495
|
+
"type": "string"
|
|
2496
|
+
},
|
|
2497
|
+
"storage": {
|
|
2498
|
+
"oneOf": [
|
|
2499
|
+
{
|
|
2500
|
+
"type": "object",
|
|
2501
|
+
"properties": {
|
|
2502
|
+
"type": {
|
|
2503
|
+
"const": "memory"
|
|
2504
|
+
}
|
|
2505
|
+
},
|
|
2506
|
+
"required": [
|
|
2507
|
+
"type"
|
|
2508
|
+
],
|
|
2509
|
+
"additionalProperties": false
|
|
2510
|
+
},
|
|
2511
|
+
{
|
|
2512
|
+
"type": "object",
|
|
2513
|
+
"properties": {
|
|
2514
|
+
"type": {
|
|
2515
|
+
"const": "filesystem"
|
|
2516
|
+
},
|
|
2517
|
+
"path": {
|
|
2518
|
+
"type": "string",
|
|
2519
|
+
"resolvePath": true
|
|
2520
|
+
}
|
|
2521
|
+
},
|
|
2522
|
+
"required": [
|
|
2523
|
+
"type"
|
|
2524
|
+
],
|
|
2525
|
+
"additionalProperties": false
|
|
2526
|
+
},
|
|
2527
|
+
{
|
|
2528
|
+
"type": "object",
|
|
2529
|
+
"properties": {
|
|
2530
|
+
"type": {
|
|
2531
|
+
"type": "string",
|
|
2532
|
+
"enum": [
|
|
2533
|
+
"redis",
|
|
2534
|
+
"valkey"
|
|
2535
|
+
]
|
|
2536
|
+
},
|
|
2537
|
+
"url": {
|
|
2538
|
+
"type": "string"
|
|
2539
|
+
},
|
|
2540
|
+
"prefix": {
|
|
2541
|
+
"type": "string"
|
|
2542
|
+
},
|
|
2543
|
+
"db": {
|
|
2544
|
+
"anyOf": [
|
|
2545
|
+
{
|
|
2546
|
+
"type": "number",
|
|
2547
|
+
"minimum": 0,
|
|
2548
|
+
"default": 0
|
|
2549
|
+
},
|
|
2550
|
+
{
|
|
2551
|
+
"type": "string",
|
|
2552
|
+
"pattern": "^[0-9]+$"
|
|
2553
|
+
}
|
|
2554
|
+
]
|
|
2555
|
+
}
|
|
2556
|
+
},
|
|
2557
|
+
"required": [
|
|
2558
|
+
"type",
|
|
2559
|
+
"url"
|
|
2560
|
+
],
|
|
2561
|
+
"additionalProperties": false
|
|
2562
|
+
}
|
|
2563
|
+
],
|
|
2564
|
+
"default": {
|
|
2565
|
+
"type": "memory"
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
"timeout": {
|
|
2569
|
+
"default": 30000,
|
|
2570
|
+
"anyOf": [
|
|
2571
|
+
{
|
|
2572
|
+
"type": "number",
|
|
2573
|
+
"minimum": 0
|
|
2574
|
+
},
|
|
2575
|
+
{
|
|
2576
|
+
"type": "string",
|
|
2577
|
+
"pattern": "^[0-9]+$"
|
|
2578
|
+
}
|
|
2579
|
+
]
|
|
2580
|
+
},
|
|
2581
|
+
"maxAttempts": {
|
|
2582
|
+
"default": 3,
|
|
2583
|
+
"anyOf": [
|
|
2584
|
+
{
|
|
2585
|
+
"type": "number",
|
|
2586
|
+
"minimum": 1
|
|
2587
|
+
},
|
|
2588
|
+
{
|
|
2589
|
+
"type": "string",
|
|
2590
|
+
"pattern": "^[1-9][0-9]*$"
|
|
2591
|
+
}
|
|
2592
|
+
]
|
|
2593
|
+
}
|
|
2594
|
+
},
|
|
2595
|
+
"required": [
|
|
2596
|
+
"enabled",
|
|
2597
|
+
"fallback"
|
|
2598
|
+
],
|
|
2599
|
+
"additionalProperties": false
|
|
2440
2600
|
}
|
|
2441
2601
|
},
|
|
2442
2602
|
"default": {},
|