diff --git a/.npmrc b/.npmrc index 288bb54..fdff003 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,5 @@ ignore-scripts=true min-release-age=7 +; temporary: fast-json-stringify@7.3.0 was published on 2026-11-05, remove after 2026-11-12 +min-release-age-exclude[]=fast-json-stringify package-lock=false diff --git a/README.md b/README.md index 364845b..500e7b0 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,19 @@ The `fast-json-stringify` configuration is the default one. You can check the de You can also override the default configuration by passing the [`serializerOpts`](https://fastify.dev/docs/latest/Reference/Server/#serializeropts) configuration to the Fastify instance. +#### `objectMechanism: 'native'` + +`fast-json-stringify@7.3.0` added the [`objectMechanism`](https://github.com/fastify/fast-json-stringify/#object-mechanism) option. +With `'native'`, the serializer projects the payload into schema-shaped objects and delegates to `JSON.stringify`, so on Node.js 25+ V8 stays on its fast path while the response schema is still applied (undeclared properties stripped, coercions and formats as usual). The output is identical to the default mechanism, and the option is a no-op on Node.js < 25. + +```js +const app = fastify({ + serializerOpts: { + objectMechanism: 'native' + } +}) +``` + ## Usage This module is already used as default by Fastify. diff --git a/package.json b/package.json index 93dba38..9867154 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,6 @@ "tstyche": "^7.1.0" }, "dependencies": { - "fast-json-stringify": "^7.0.0" + "fast-json-stringify": "^7.3.0" } } diff --git a/test/object-mechanism.test.js b/test/object-mechanism.test.js new file mode 100644 index 0000000..981c3d9 --- /dev/null +++ b/test/object-mechanism.test.js @@ -0,0 +1,62 @@ +'use strict' + +const { test } = require('node:test') +const fastify = require('fastify') +const FjsCompiler = require('../index') + +const userSchema = Object.freeze({ + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + createdAt: { type: 'string', format: 'date-time' } + }, + required: ['id', 'name'] +}) + +test('objectMechanism is forwarded to fast-json-stringify', t => { + t.plan(1) + const factory = FjsCompiler() + t.assert.throws(() => { + factory({}, { objectMechanism: 'not-a-mechanism' })({ schema: userSchema }) + }, /Unsupported object mechanism not-a-mechanism/) +}) + +test('objectMechanism: native produces the same output as the default mechanism', t => { + t.plan(1) + const factory = FjsCompiler() + const serializeDefault = factory({}, {})({ schema: userSchema }) + const serializeNative = factory({}, { objectMechanism: 'native' })({ schema: userSchema }) + + const input = { id: 1, name: 'alice', passwordHash: 'secret', createdAt: new Date(0) } + t.assert.equal(serializeNative(input), serializeDefault(input)) +}) + +test('fastify integration with objectMechanism: native', async t => { + const factory = FjsCompiler() + + const app = fastify({ + serializerOpts: { + objectMechanism: 'native' + }, + schemaController: { + compilersFactory: { + buildSerializer: factory + } + } + }) + + app.get('/', { + handler: async () => ({ id: 1, name: 'alice', passwordHash: 'secret', createdAt: new Date(0) }), + schema: { + response: { + 200: userSchema + } + } + }) + + const res = await app.inject({ url: '/', method: 'GET' }) + + t.assert.equal(res.statusCode, 200) + t.assert.equal(res.body, '{"id":1,"name":"alice","createdAt":"1970-01-01T00:00:00.000Z"}') +}) diff --git a/types/index.tst.ts b/types/index.tst.ts index 4b0b6aa..2fa4026 100644 --- a/types/index.tst.ts +++ b/types/index.tst.ts @@ -41,6 +41,10 @@ import SerializerSelector, { expect(serializeFunc).type.toBe() expect(serializeFunc({ name: 'hello' })).type.toBe() + + expect(factory({}, { objectMechanism: 'native' })).type.toBe() + expect(factory({}, { objectMechanism: 'default' })).type.toBe() + expect(factory).type.not.toBeCallableWith({}, { objectMechanism: 'not-a-mechanism' }) } /**