From 9f0b5a2e613d9e058b40dcacf1a807d17655b11c Mon Sep 17 00:00:00 2001 From: heiye111 Date: Sat, 20 Sep 2025 23:02:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=95=B4=E4=BB=A3=E7=A0=81=E5=8A=A0?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2!!!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- db-connector.js | 38 ++ node_modules/.package-lock.json | 6 + node_modules/fastify-plugin/.gitattributes | 2 + .../fastify-plugin/.github/dependabot.yml | 13 + node_modules/fastify-plugin/.github/stale.yml | 21 + .../fastify-plugin/.github/workflows/ci.yml | 23 + node_modules/fastify-plugin/LICENSE | 21 + node_modules/fastify-plugin/README.md | 188 +++++++++ .../fastify-plugin/lib/getPluginName.js | 25 ++ .../fastify-plugin/lib/toCamelCase.js | 10 + node_modules/fastify-plugin/package.json | 40 ++ node_modules/fastify-plugin/plugin.js | 67 +++ .../fastify-plugin/test/bundlers.test.js | 110 +++++ .../fastify-plugin/test/checkVersion.test.js | 67 +++ .../fastify-plugin/test/composite.test.js | 14 + node_modules/fastify-plugin/test/esm/esm.mjs | 11 + .../fastify-plugin/test/esm/index.test.js | 11 + .../test/extractPluginName.test.js | 49 +++ .../test/mu1tip1e.composite.test.js | 15 + node_modules/fastify-plugin/test/test.js | 396 ++++++++++++++++++ .../fastify-plugin/test/toCamelCase.test.js | 24 ++ .../types/example-async.test-d.ts | 19 + .../types/example-callback.test-d.ts | 19 + node_modules/fastify-plugin/types/plugin.d.ts | 61 +++ .../fastify-plugin/types/plugin.test-d.ts | 166 ++++++++ package-lock.json | 7 + package.json | 1 + server.js | 131 ++++++ 29 files changed, 1556 insertions(+), 1 deletion(-) create mode 100644 db-connector.js create mode 100644 node_modules/fastify-plugin/.gitattributes create mode 100644 node_modules/fastify-plugin/.github/dependabot.yml create mode 100644 node_modules/fastify-plugin/.github/stale.yml create mode 100644 node_modules/fastify-plugin/.github/workflows/ci.yml create mode 100644 node_modules/fastify-plugin/LICENSE create mode 100644 node_modules/fastify-plugin/README.md create mode 100644 node_modules/fastify-plugin/lib/getPluginName.js create mode 100644 node_modules/fastify-plugin/lib/toCamelCase.js create mode 100644 node_modules/fastify-plugin/package.json create mode 100644 node_modules/fastify-plugin/plugin.js create mode 100644 node_modules/fastify-plugin/test/bundlers.test.js create mode 100644 node_modules/fastify-plugin/test/checkVersion.test.js create mode 100644 node_modules/fastify-plugin/test/composite.test.js create mode 100644 node_modules/fastify-plugin/test/esm/esm.mjs create mode 100644 node_modules/fastify-plugin/test/esm/index.test.js create mode 100644 node_modules/fastify-plugin/test/extractPluginName.test.js create mode 100644 node_modules/fastify-plugin/test/mu1tip1e.composite.test.js create mode 100644 node_modules/fastify-plugin/test/test.js create mode 100644 node_modules/fastify-plugin/test/toCamelCase.test.js create mode 100644 node_modules/fastify-plugin/types/example-async.test-d.ts create mode 100644 node_modules/fastify-plugin/types/example-callback.test-d.ts create mode 100644 node_modules/fastify-plugin/types/plugin.d.ts create mode 100644 node_modules/fastify-plugin/types/plugin.test-d.ts create mode 100644 server.js diff --git a/.env b/.env index afeaf9f..7b477ca 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ # 替换成你自己的 MongoDB Atlas 或本地数据库的连接字符串 -MONGO_URI="mongodb+srv://:@/?retryWrites=true&w=majority" +MONGO_URI="mongodb://mongo_c6bNmG:mongo_tyNkXh@83.229.121.44:27017/?authSource=admin" # 指定你要操作的数据库名称 DATABASE_NAME="todo_app_fastify" \ No newline at end of file diff --git a/db-connector.js b/db-connector.js new file mode 100644 index 0000000..8b4261e --- /dev/null +++ b/db-connector.js @@ -0,0 +1,38 @@ +const fastifyPlugin = require('fastify-plugin'); +const { MongoClient } = require('mongodb'); + +async function dbConnector(fastify, options) { + const url = process.env.MONGO_URI; + const dbName = process.env.DATABASE_NAME; + + if (!url) { + throw new Error('MONGO_URI must be defined in your .env file'); + } + + const client = new MongoClient(url); + + try { + await client.connect(); + fastify.log.info('成功连接到 MongoDB 数据库!'); + + const db = client.db(dbName); + + // 使用 fastify.decorate 将数据库实例和 ObjectId 挂载到 Fastify 实例上 + // 这样在所有路由中都可以通过 fastify.mongo.db 访问 + fastify.decorate('mongo', { db }); + + } catch (err) { + fastify.log.error(err); + // 如果连接失败,可以选择关闭 fastify 实例或抛出错误 + throw new Error('数据库连接失败'); + } + + // 确保在服务器关闭时断开数据库连接 + fastify.addHook('onClose', async (instance) => { + await client.close(); + instance.log.info('MongoDB 数据库连接已断开。'); + }); +} + +// 使用 fastify-plugin 包装,防止 Fastify 对插件进行不必要的封装 +module.exports = fastifyPlugin(dbConnector); \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index cb3d741..88444cb 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -319,6 +319,12 @@ "toad-cache": "^3.7.0" } }, + "node_modules/fastify-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", + "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", diff --git a/node_modules/fastify-plugin/.gitattributes b/node_modules/fastify-plugin/.gitattributes new file mode 100644 index 0000000..a0e7df9 --- /dev/null +++ b/node_modules/fastify-plugin/.gitattributes @@ -0,0 +1,2 @@ +# Set default behavior to automatically convert line endings +* text=auto eol=lf diff --git a/node_modules/fastify-plugin/.github/dependabot.yml b/node_modules/fastify-plugin/.github/dependabot.yml new file mode 100644 index 0000000..dfa7fa6 --- /dev/null +++ b/node_modules/fastify-plugin/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 diff --git a/node_modules/fastify-plugin/.github/stale.yml b/node_modules/fastify-plugin/.github/stale.yml new file mode 100644 index 0000000..d51ce63 --- /dev/null +++ b/node_modules/fastify-plugin/.github/stale.yml @@ -0,0 +1,21 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 15 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - "discussion" + - "feature request" + - "bug" + - "help wanted" + - "plugin suggestion" + - "good first issue" +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/node_modules/fastify-plugin/.github/workflows/ci.yml b/node_modules/fastify-plugin/.github/workflows/ci.yml new file mode 100644 index 0000000..c2227cf --- /dev/null +++ b/node_modules/fastify-plugin/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: + - main + - master + - next + - 'v*' + paths-ignore: + - 'docs/**' + - '*.md' + pull_request: + paths-ignore: + - 'docs/**' + - '*.md' + +jobs: + test: + uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5.0.0 + with: + lint: true + license-check: true diff --git a/node_modules/fastify-plugin/LICENSE b/node_modules/fastify-plugin/LICENSE new file mode 100644 index 0000000..98544ed --- /dev/null +++ b/node_modules/fastify-plugin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Fastify + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/fastify-plugin/README.md b/node_modules/fastify-plugin/README.md new file mode 100644 index 0000000..0c54316 --- /dev/null +++ b/node_modules/fastify-plugin/README.md @@ -0,0 +1,188 @@ +# fastify-plugin + +![CI](https://github.com/fastify/fastify-plugin/workflows/CI/badge.svg?branch=master) +[![NPM version](https://img.shields.io/npm/v/fastify-plugin.svg?style=flat)](https://www.npmjs.com/package/fastify-plugin) +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) + +`fastify-plugin` is a plugin helper for [Fastify](https://github.com/fastify/fastify). + +When you build plugins for Fastify and you want them to be accessible in the same context where you require them, you have two ways: +1. Use the `skip-override` hidden property +2. Use this module + +__Note: the v4.x series of this module covers Fastify v4__ +__Note: the v2.x & v3.x series of this module covers Fastify v3. For Fastify v2 support, refer to the v1.x series.__ + +## Install + +```sh +npm i fastify-plugin +``` + +## Usage +`fastify-plugin` can do three things for you: +- Add the `skip-override` hidden property +- Check the bare-minimum version of Fastify +- Pass some custom metadata of the plugin to Fastify + +Example using a callback: +```js +const fp = require('fastify-plugin') + +module.exports = fp(function (fastify, opts, done) { + // your plugin code + done() +}) +``` + +Example using an [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) function: +```js +const fp = require('fastify-plugin') + +// A callback function param is not required for async functions +module.exports = fp(async function (fastify, opts) { + // Wait for an async function to fulfill promise before proceeding + await exampleAsyncFunction() +}) +``` + +## Metadata +In addition, if you use this module when creating new plugins, you can declare the dependencies, the name, and the expected Fastify version that your plugin needs. + +#### Fastify version +If you need to set a bare-minimum version of Fastify for your plugin, just add the [semver](https://semver.org/) range that you need: +```js +const fp = require('fastify-plugin') + +module.exports = fp(function (fastify, opts, done) { + // your plugin code + done() +}, { fastify: '5.x' }) +``` + +If you need to check the Fastify version only, you can pass just the version string. + +You can check [here](https://github.com/npm/node-semver#ranges) how to define a `semver` range. + +#### Name +Fastify uses this option to validate the dependency graph, allowing it to ensure that no name collisions occur and making it possible to perform [dependency checks](https://github.com/fastify/fastify-plugin#dependencies). + +```js +const fp = require('fastify-plugin') + +function plugin (fastify, opts, done) { + // your plugin code + done() +} + +module.exports = fp(plugin, { + fastify: '5.x', + name: 'your-plugin-name' +}) +``` + +#### Dependencies +You can also check if the `plugins` and `decorators` that your plugin intend to use are present in the dependency graph. +> *Note:* This is the point where registering `name` of the plugins become important, because you can reference `plugin` dependencies by their [name](https://github.com/fastify/fastify-plugin#name). +```js +const fp = require('fastify-plugin') + +function plugin (fastify, opts, done) { + // your plugin code + done() +} + +module.exports = fp(plugin, { + fastify: '5.x', + decorators: { + fastify: ['plugin1', 'plugin2'], + reply: ['compress'] + }, + dependencies: ['plugin1-name', 'plugin2-name'] +}) +``` + +#### Encapsulate + +By default, `fastify-plugin` breaks the [encapsulation](https://github.com/fastify/fastify/blob/HEAD/docs/Reference/Encapsulation.md) but you can optionally keep the plugin encapsulated. +This allows you to set the plugin's name and validate its dependencies without making the plugin accessible. +```js +const fp = require('fastify-plugin') + +function plugin (fastify, opts, done) { + // the decorator is not accessible outside this plugin + fastify.decorate('util', function() {}) + done() +} + +module.exports = fp(plugin, { + name: 'my-encapsulated-plugin', + fastify: '5.x', + decorators: { + fastify: ['plugin1', 'plugin2'], + reply: ['compress'] + }, + dependencies: ['plugin1-name', 'plugin2-name'], + encapsulate: true +}) +``` + +#### Bundlers and Typescript +`fastify-plugin` adds a `.default` and `[name]` property to the passed in function. +The type definition would have to be updated to leverage this. + +## Known Issue: TypeScript Contextual Inference + +[Documentation Reference](https://www.typescriptlang.org/docs/handbook/functions.html#inferring-the-types) + +It is common for developers to inline their plugin with fastify-plugin such as: + +```js +fp((fastify, opts, done) => { done() }) +fp(async (fastify, opts) => { return }) +``` + +TypeScript can sometimes infer the types of the arguments for these functions. Plugins in Fastify are recommended to be typed using either `FastifyPluginCallback` or `FastifyPluginAsync`. These two definitions only differ in two ways: + +1. The third argument `done` (the callback part) +2. The return type `FastifyPluginCallback` or `FastifyPluginAsync` + +At this time, TypeScript inference is not smart enough to differentiate by definition argument length alone. + +Thus, if you are a TypeScript developer please use on the following patterns instead: + +```ts +// Callback + +// Assign type directly +const pluginCallback: FastifyPluginCallback = (fastify, options, done) => { } +fp(pluginCallback) + +// or define your own function declaration that satisfies the existing definitions +const pluginCallbackWithTypes = (fastify: FastifyInstance, options: FastifyPluginOptions, done: (error?: FastifyError) => void): void => { } +fp(pluginCallbackWithTypes) +// or inline +fp((fastify: FastifyInstance, options: FastifyPluginOptions, done: (error?: FastifyError) => void): void => { }) + +// Async + +// Assign type directly +const pluginAsync: FastifyPluginAsync = async (fastify, options) => { } +fp(pluginAsync) + +// or define your own function declaration that satisfies the existing definitions +const pluginAsyncWithTypes = async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise => { } +fp(pluginAsyncWithTypes) +// or inline +fp(async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise => { }) +``` + +## Acknowledgements + +This project is kindly sponsored by: +- [nearForm](https://nearform.com) +- [LetzDoIt](https://www.letzdoitapp.com/) + +## License + +Licensed under [MIT](./LICENSE). diff --git a/node_modules/fastify-plugin/lib/getPluginName.js b/node_modules/fastify-plugin/lib/getPluginName.js new file mode 100644 index 0000000..accfccf --- /dev/null +++ b/node_modules/fastify-plugin/lib/getPluginName.js @@ -0,0 +1,25 @@ +'use strict' + +const fpStackTracePattern = /at\s{1}(?:.*\.)?plugin\s{1}.*\n\s*(.*)/ +const fileNamePattern = /(\w*(\.\w*)*)\..*/ + +module.exports = function getPluginName (fn) { + if (fn.name.length > 0) return fn.name + + const stackTraceLimit = Error.stackTraceLimit + Error.stackTraceLimit = 10 + try { + throw new Error('anonymous function') + } catch (e) { + Error.stackTraceLimit = stackTraceLimit + return extractPluginName(e.stack) + } +} + +function extractPluginName (stack) { + const m = stack.match(fpStackTracePattern) + + // get last section of path and match for filename + return m ? m[1].split(/[/\\]/).slice(-1)[0].match(fileNamePattern)[1] : 'anonymous' +} +module.exports.extractPluginName = extractPluginName diff --git a/node_modules/fastify-plugin/lib/toCamelCase.js b/node_modules/fastify-plugin/lib/toCamelCase.js new file mode 100644 index 0000000..fdaf141 --- /dev/null +++ b/node_modules/fastify-plugin/lib/toCamelCase.js @@ -0,0 +1,10 @@ +'use strict' + +module.exports = function toCamelCase (name) { + if (name[0] === '@') { + name = name.slice(1).replace('/', '-') + } + return name.replace(/-(.)/g, function (match, g1) { + return g1.toUpperCase() + }) +} diff --git a/node_modules/fastify-plugin/package.json b/node_modules/fastify-plugin/package.json new file mode 100644 index 0000000..78194ef --- /dev/null +++ b/node_modules/fastify-plugin/package.json @@ -0,0 +1,40 @@ +{ + "name": "fastify-plugin", + "version": "5.0.1", + "description": "Plugin helper for Fastify", + "main": "plugin.js", + "type": "commonjs", + "types": "types/plugin.d.ts", + "scripts": { + "lint": "standard", + "test": "npm run test:unit && npm run test:typescript", + "test:unit": "c8 --100 node --test", + "test:coverage": "c8 node --test && c8 report --reporter=html", + "test:typescript": "tsd" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/fastify/fastify-plugin.git" + }, + "keywords": [ + "plugin", + "helper", + "fastify" + ], + "author": "Tomas Della Vedova - @delvedor (http://delved.org)", + "license": "MIT", + "bugs": { + "url": "https://github.com/fastify/fastify-plugin/issues" + }, + "homepage": "https://github.com/fastify/fastify-plugin#readme", + "devDependencies": { + "@fastify/pre-commit": "^2.1.0", + "@fastify/type-provider-typebox": "^5.0.0-pre.fv5.1", + "@types/node": "^22.0.0", + "c8": "^10.1.2", + "fastify": "^5.0.0", + "proxyquire": "^2.1.3", + "standard": "^17.1.0", + "tsd": "^0.31.0" + } +} diff --git a/node_modules/fastify-plugin/plugin.js b/node_modules/fastify-plugin/plugin.js new file mode 100644 index 0000000..3ad686e --- /dev/null +++ b/node_modules/fastify-plugin/plugin.js @@ -0,0 +1,67 @@ +'use strict' + +const getPluginName = require('./lib/getPluginName') +const toCamelCase = require('./lib/toCamelCase') + +let count = 0 + +function plugin (fn, options = {}) { + let autoName = false + + if (fn.default !== undefined) { + // Support for 'export default' behaviour in transpiled ECMAScript module + fn = fn.default + } + + if (typeof fn !== 'function') { + throw new TypeError( + `fastify-plugin expects a function, instead got a '${typeof fn}'` + ) + } + + if (typeof options === 'string') { + options = { + fastify: options + } + } + + if ( + typeof options !== 'object' || + Array.isArray(options) || + options === null + ) { + throw new TypeError('The options object should be an object') + } + + if (options.fastify !== undefined && typeof options.fastify !== 'string') { + throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`) + } + + if (!options.name) { + autoName = true + options.name = getPluginName(fn) + '-auto-' + count++ + } + + fn[Symbol.for('skip-override')] = options.encapsulate !== true + fn[Symbol.for('fastify.display-name')] = options.name + fn[Symbol.for('plugin-meta')] = options + + // Faux modules support + if (!fn.default) { + fn.default = fn + } + + // TypeScript support for named imports + // See https://github.com/fastify/fastify/issues/2404 for more details + // The type definitions would have to be update to match this. + const camelCase = toCamelCase(options.name) + if (!autoName && !fn[camelCase]) { + fn[camelCase] = fn + } + + return fn +} + +module.exports = plugin +module.exports.default = plugin +module.exports.fastifyPlugin = plugin diff --git a/node_modules/fastify-plugin/test/bundlers.test.js b/node_modules/fastify-plugin/test/bundlers.test.js new file mode 100644 index 0000000..ffda456 --- /dev/null +++ b/node_modules/fastify-plugin/test/bundlers.test.js @@ -0,0 +1,110 @@ +'use strict' + +const { test } = require('node:test') +const fp = require('../plugin') + +test('webpack removes require.main.filename', t => { + const filename = require.main.filename + const info = console.info + t.after(() => { + require.main.filename = filename + console.info = info + }) + + require.main.filename = null + + console.info = function (msg) { + t.assert.fail('logged: ' + msg) + } + + fp((fastify, opts, next) => { + next() + }, { + fastify: '^5.0.0' + }) +}) + +test('support faux modules', (t) => { + const plugin = fp((fastify, opts, next) => { + next() + }) + + t.assert.strictEqual(plugin.default, plugin) +}) + +test('support faux modules does not override existing default field in babel module', (t) => { + const module = { + default: (fastify, opts, next) => next() + } + + module.default.default = 'Existing default field' + + const plugin = fp(module) + + t.assert.strictEqual(plugin.default, 'Existing default field') +}) + +test('support ts named imports', (t) => { + const plugin = fp((fastify, opts, next) => { + next() + }, { + name: 'hello' + }) + + t.assert.strictEqual(plugin.hello, plugin) +}) + +test('from kebab-case to camelCase', (t) => { + const plugin = fp((fastify, opts, next) => { + next() + }, { + name: 'hello-world' + }) + + t.assert.strictEqual(plugin.helloWorld, plugin) +}) + +test('from @-prefixed named imports', (t) => { + const plugin = fp((fastify, opts, next) => { + next() + }, { + name: '@hello/world' + }) + + t.assert.strictEqual(plugin.helloWorld, plugin) +}) + +test('from @-prefixed named kebab-case to camelCase', (t) => { + const plugin = fp((fastify, opts, next) => { + next() + }, { + name: '@hello/my-world' + }) + + t.assert.strictEqual(plugin.helloMyWorld, plugin) +}) + +test('from kebab-case to camelCase multiple words', (t) => { + const plugin = fp((fastify, opts, next) => { + next() + }, { + name: 'hello-long-world' + }) + + t.assert.strictEqual(plugin.helloLongWorld, plugin) +}) + +test('from kebab-case to camelCase multiple words does not override', (t) => { + const fn = (fastify, opts, next) => { + next() + } + + const foobar = {} + fn.helloLongWorld = foobar + + const plugin = fp(fn, { + name: 'hello-long-world' + }) + + t.assert.strictEqual(plugin.helloLongWorld, foobar) +}) diff --git a/node_modules/fastify-plugin/test/checkVersion.test.js b/node_modules/fastify-plugin/test/checkVersion.test.js new file mode 100644 index 0000000..ece5b24 --- /dev/null +++ b/node_modules/fastify-plugin/test/checkVersion.test.js @@ -0,0 +1,67 @@ +'use strict' + +const { test } = require('node:test') +const fp = require('../plugin') + +test('checkVersion having require.main.filename', (t) => { + const info = console.info + t.assert.ok(require.main.filename) + t.after(() => { + console.info = info + }) + + console.info = function (msg) { + t.assert.fail('logged: ' + msg) + } + + fp((fastify, opts, next) => { + next() + }, { + fastify: '^5.0.0' + }) +}) + +test('checkVersion having no require.main.filename but process.argv[1]', (t) => { + const filename = require.main.filename + const info = console.info + t.after(() => { + require.main.filename = filename + console.info = info + }) + + require.main.filename = null + + console.info = function (msg) { + t.assert.fail('logged: ' + msg) + } + + fp((fastify, opts, next) => { + next() + }, { + fastify: '^5.0.0' + }) +}) + +test('checkVersion having no require.main.filename and no process.argv[1]', (t) => { + const filename = require.main.filename + const argv = process.argv + const info = console.info + t.after(() => { + require.main.filename = filename + process.argv = argv + console.info = info + }) + + require.main.filename = null + process.argv[1] = null + + console.info = function (msg) { + t.assert.fail('logged: ' + msg) + } + + fp((fastify, opts, next) => { + next() + }, { + fastify: '^5.0.0' + }) +}) diff --git a/node_modules/fastify-plugin/test/composite.test.js b/node_modules/fastify-plugin/test/composite.test.js new file mode 100644 index 0000000..714a1e0 --- /dev/null +++ b/node_modules/fastify-plugin/test/composite.test.js @@ -0,0 +1,14 @@ +'use strict' + +const { test } = require('node:test') +const fp = require('../plugin') + +test('anonymous function should be named composite.test0', (t) => { + t.plan(2) + const fn = fp((fastify, opts, next) => { + next() + }) + + t.assert.strictEqual(fn[Symbol.for('plugin-meta')].name, 'composite.test-auto-0') + t.assert.strictEqual(fn[Symbol.for('fastify.display-name')], 'composite.test-auto-0') +}) diff --git a/node_modules/fastify-plugin/test/esm/esm.mjs b/node_modules/fastify-plugin/test/esm/esm.mjs new file mode 100644 index 0000000..768a1af --- /dev/null +++ b/node_modules/fastify-plugin/test/esm/esm.mjs @@ -0,0 +1,11 @@ +import { test } from 'node:test' +import fp from '../../plugin.js' + +test('esm base support', (t) => { + fp((fastify, opts, next) => { + next() + }, { + fastify: '^5.0.0' + }) + t.assert.ok(true, 'fp function called without throwing an error') +}) diff --git a/node_modules/fastify-plugin/test/esm/index.test.js b/node_modules/fastify-plugin/test/esm/index.test.js new file mode 100644 index 0000000..5941ccc --- /dev/null +++ b/node_modules/fastify-plugin/test/esm/index.test.js @@ -0,0 +1,11 @@ +'use strict' + +// Node v8 throw a `SyntaxError: Unexpected token import` +// even if this branch is never touch in the code, +// by using `eval` we can avoid this issue. +// eslint-disable-next-line +new Function('module', 'return import(module)')('./esm.mjs').catch((err) => { + process.nextTick(() => { + throw err + }) +}) diff --git a/node_modules/fastify-plugin/test/extractPluginName.test.js b/node_modules/fastify-plugin/test/extractPluginName.test.js new file mode 100644 index 0000000..23097e6 --- /dev/null +++ b/node_modules/fastify-plugin/test/extractPluginName.test.js @@ -0,0 +1,49 @@ +'use strict' + +const { test } = require('node:test') +const extractPluginName = require('../lib/getPluginName').extractPluginName + +const winStack = `Error: anonymous function +at checkName (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\index.js:43:11) +at plugin (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\index.js:24:20) +at Test.test (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\test\\hello.test.js:9:14) +at bound (domain.js:396:14) +at Test.runBound (domain.js:409:12) +at ret (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:278:21) +at Test.main (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:282:7) +at writeSubComment (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:371:13) +at TAP.writeSubComment (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:403:5) +at Test.runBeforeEach (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:370:14) +at loop (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\function-loop\\index.js:35:15) +at TAP.runBeforeEach (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:683:7) +at TAP.processSubtest (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:369:12) +at TAP.process (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:306:14) +at TAP.sub (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:185:10) +at TAP.test (C:\\Users\\leonardo.davinci\\Desktop\\fastify-plugin\\node_modules\\tap\\lib\\test.js:209:17)` + +const nixStack = `Error: anonymous function +at checkName (/home/leonardo/desktop/fastify-plugin/index.js:43:11) +at plugin (/home/leonardo/desktop/fastify-plugin/index.js:24:20) +at Test.test (/home/leonardo/desktop/fastify-plugin/test/this.is.a.test.js:9:14) +at bound (domain.js:396:14) +at Test.runBound (domain.js:409:12) +at ret (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:278:21) +at Test.main (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:282:7) +at writeSubComment (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:371:13) +at TAP.writeSubComment (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:403:5) +at Test.runBeforeEach (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:370:14) +at loop (/home/leonardo/desktop/fastify-plugin/node_modules/function-loop/index.js:35:15) +at TAP.runBeforeEach (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:683:7) +at TAP.processSubtest (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:369:12) +at TAP.process (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:306:14) +at TAP.sub (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:185:10) +at TAP.test (/home/leonardo/desktop/fastify-plugin/node_modules/tap/lib/test.js:209:17)` + +const anonymousStack = 'Unable to parse this' + +test('extractPluginName tests', (t) => { + t.plan(3) + t.assert.strictEqual(extractPluginName(winStack), 'hello.test') + t.assert.strictEqual(extractPluginName(nixStack), 'this.is.a.test') + t.assert.strictEqual(extractPluginName(anonymousStack), 'anonymous') +}) diff --git a/node_modules/fastify-plugin/test/mu1tip1e.composite.test.js b/node_modules/fastify-plugin/test/mu1tip1e.composite.test.js new file mode 100644 index 0000000..5696c0b --- /dev/null +++ b/node_modules/fastify-plugin/test/mu1tip1e.composite.test.js @@ -0,0 +1,15 @@ +'use strict' + +const { test } = require('node:test') +const fp = require('../plugin') + +test('anonymous function should be named mu1tip1e.composite.test', (t) => { + t.plan(2) + + const fn = fp((fastify, opts, next) => { + next() + }) + + t.assert.strictEqual(fn[Symbol.for('plugin-meta')].name, 'mu1tip1e.composite.test-auto-0') + t.assert.strictEqual(fn[Symbol.for('fastify.display-name')], 'mu1tip1e.composite.test-auto-0') +}) diff --git a/node_modules/fastify-plugin/test/test.js b/node_modules/fastify-plugin/test/test.js new file mode 100644 index 0000000..ff08e8f --- /dev/null +++ b/node_modules/fastify-plugin/test/test.js @@ -0,0 +1,396 @@ +'use strict' + +const { test } = require('node:test') +const proxyquire = require('proxyquire') +const fp = require('../plugin') +const Fastify = require('fastify') + +const pkg = require('../package.json') + +test('fastify-plugin is a function', (t) => { + t.plan(1) + t.assert.ok(typeof fp === 'function') +}) + +test('should return the function with the skip-override Symbol', (t) => { + t.plan(1) + + function plugin (fastify, opts, next) { + next() + } + + fp(plugin) + t.assert.ok(plugin[Symbol.for('skip-override')]) +}) + +test('should support "default" function from babel module', (t) => { + t.plan(1) + + const plugin = { + default: () => { } + } + + try { + fp(plugin) + t.assert.ok(true) + } catch (e) { + t.assert.strictEqual(e.message, 'fastify-plugin expects a function, instead got a \'object\'') + } +}) + +test('should throw if the plugin is not a function', (t) => { + t.plan(1) + + try { + fp('plugin') + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.message, 'fastify-plugin expects a function, instead got a \'string\'') + } +}) + +test('should check the fastify version', (t) => { + t.plan(1) + + function plugin (fastify, opts, next) { + next() + } + + try { + fp(plugin, { fastify: '>=0.10.0' }) + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('should check the fastify version', (t) => { + t.plan(1) + + function plugin (fastify, opts, next) { + next() + } + + try { + fp(plugin, '>=0.10.0') + t.assert.ok(true) + } catch (e) { + t.assert.fail() + } +}) + +test('the options object should be an object', (t) => { + t.plan(2) + + try { + fp(() => { }, null) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.message, 'The options object should be an object') + } + + try { + fp(() => { }, []) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.message, 'The options object should be an object') + } +}) + +test('should throw if the version number is not a string', (t) => { + t.plan(1) + + try { + fp(() => { }, { fastify: 12 }) + t.assert.fail() + } catch (e) { + t.assert.strictEqual(e.message, 'fastify-plugin expects a version string, instead got \'number\'') + } +}) + +test('Should accept an option object', (t) => { + t.plan(2) + + const opts = { hello: 'world' } + + function plugin (fastify, opts, next) { + next() + } + + fp(plugin, opts) + + t.assert.ok(plugin[Symbol.for('skip-override')], 'skip-override symbol should be present') + t.assert.deepStrictEqual(plugin[Symbol.for('plugin-meta')], opts, 'plugin-meta should match opts') +}) + +test('Should accept an option object and checks the version', (t) => { + t.plan(2) + + const opts = { hello: 'world', fastify: '>=0.10.0' } + + function plugin (fastify, opts, next) { + next() + } + + fp(plugin, opts) + t.assert.ok(plugin[Symbol.for('skip-override')]) + t.assert.deepStrictEqual(plugin[Symbol.for('plugin-meta')], opts) +}) + +test('should set anonymous function name to file it was called from with a counter', (t) => { + const fp = proxyquire('../plugin.js', { stubs: {} }) + + const fn = fp((fastify, opts, next) => { + next() + }) + + t.assert.strictEqual(fn[Symbol.for('plugin-meta')].name, 'test-auto-0') + t.assert.strictEqual(fn[Symbol.for('fastify.display-name')], 'test-auto-0') + + const fn2 = fp((fastify, opts, next) => { + next() + }) + + t.assert.strictEqual(fn2[Symbol.for('plugin-meta')].name, 'test-auto-1') + t.assert.strictEqual(fn2[Symbol.for('fastify.display-name')], 'test-auto-1') +}) + +test('should set function name if Error.stackTraceLimit is set to 0', (t) => { + const stackTraceLimit = Error.stackTraceLimit = 0 + + const fp = proxyquire('../plugin.js', { stubs: {} }) + + const fn = fp((fastify, opts, next) => { + next() + }) + + t.assert.strictEqual(fn[Symbol.for('plugin-meta')].name, 'test-auto-0') + t.assert.strictEqual(fn[Symbol.for('fastify.display-name')], 'test-auto-0') + + const fn2 = fp((fastify, opts, next) => { + next() + }) + + t.assert.strictEqual(fn2[Symbol.for('plugin-meta')].name, 'test-auto-1') + t.assert.strictEqual(fn2[Symbol.for('fastify.display-name')], 'test-auto-1') + + Error.stackTraceLimit = stackTraceLimit +}) + +test('should set display-name to meta name', (t) => { + t.plan(2) + + const functionName = 'superDuperSpecialFunction' + + const fn = fp((fastify, opts, next) => next(), { + name: functionName + }) + + t.assert.strictEqual(fn[Symbol.for('plugin-meta')].name, functionName) + t.assert.strictEqual(fn[Symbol.for('fastify.display-name')], functionName) +}) + +test('should preserve fastify version in meta', (t) => { + t.plan(1) + + const opts = { hello: 'world', fastify: '>=0.10.0' } + + const fn = fp((fastify, opts, next) => next(), opts) + + t.assert.strictEqual(fn[Symbol.for('plugin-meta')].fastify, '>=0.10.0') +}) + +test('should check fastify dependency graph - plugin', async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, next) => next(), { + fastify: '5.x', + name: 'plugin1-name' + })) + + fastify.register(fp((fastify, opts, next) => next(), { + fastify: '5.x', + name: 'test', + dependencies: ['plugin1-name', 'plugin2-name'] + })) + + await t.assert.rejects(fastify.ready(), { message: "The dependency 'plugin2-name' of plugin 'test' is not registered" }) +}) + +test('should check fastify dependency graph - decorate', async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', fp((fastify, opts, next) => next(), { + fastify: '5.x', + name: 'plugin1-name' + })) + + fastify.register(fp((fastify, opts, next) => next(), { + fastify: '5.x', + name: 'test', + decorators: { fastify: ['plugin1', 'plugin2'] } + })) + + await t.assert.rejects(fastify.ready(), { message: "The decorator 'plugin2' required by 'test' is not present in Fastify" }) +}) + +test('should check fastify dependency graph - decorateReply', async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.decorateReply('plugin1', fp((fastify, opts, next) => next(), { + fastify: '5.x', + name: 'plugin1-name' + })) + + fastify.register(fp((fastify, opts, next) => next(), { + fastify: '5.x', + name: 'test', + decorators: { reply: ['plugin1', 'plugin2'] } + })) + + await t.assert.rejects(fastify.ready(), { message: "The decorator 'plugin2' required by 'test' is not present in Reply" }) +}) + +test('should accept an option to encapsulate', async (t) => { + t.plan(3) + + const fastify = Fastify() + + fastify.register(fp((fastify, opts, next) => { + fastify.decorate('accessible', true) + next() + }, { + name: 'accessible-plugin' + })) + + fastify.register(fp((fastify, opts, next) => { + fastify.decorate('alsoAccessible', true) + next() + }, { + name: 'accessible-plugin2', + encapsulate: false + })) + + fastify.register(fp((fastify, opts, next) => { + fastify.decorate('encapsulated', true) + next() + }, { + name: 'encapsulated-plugin', + encapsulate: true + })) + + await fastify.ready() + + t.assert.ok(fastify.hasDecorator('accessible')) + t.assert.ok(fastify.hasDecorator('alsoAccessible')) + t.assert.ok(!fastify.hasDecorator('encapsulated')) +}) + +test('should check dependencies when encapsulated', async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, next) => next(), { + name: 'test', + dependencies: ['missing-dependency-name'], + encapsulate: true + })) + + await t.assert.rejects(fastify.ready(), { message: "The dependency 'missing-dependency-name' of plugin 'test' is not registered" }) +}) + +test( + 'should check version when encapsulated', + { skip: /\d-.+/.test(pkg.devDependencies.fastify) }, + async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.register(fp((fastify, opts, next) => next(), { + name: 'test', + fastify: '<=2.10.0', + encapsulate: true + })) + + await t.assert.rejects(fastify.ready(), { message: /fastify-plugin: test - expected '<=2.10.0' fastify version, '\d.\d+.\d+' is installed/ }) + } +) + +test('should check decorators when encapsulated', async (t) => { + t.plan(1) + const fastify = Fastify() + + fastify.decorate('plugin1', 'foo') + + fastify.register(fp((fastify, opts, next) => next(), { + fastify: '5.x', + name: 'test', + encapsulate: true, + decorators: { fastify: ['plugin1', 'plugin2'] } + })) + + await t.assert.rejects(fastify.ready(), { message: "The decorator 'plugin2' required by 'test' is not present in Fastify" }) +}) + +test('plugin name when encapsulated', async (t) => { + t.plan(6) + const fastify = Fastify() + + fastify.register(function plugin (instance, opts, next) { + next() + }) + + fastify.register(fp(getFn('hello'), { + fastify: '5.x', + name: 'hello', + encapsulate: true + })) + + fastify.register(function plugin (fastify, opts, next) { + fastify.register(fp(getFn('deep'), { + fastify: '5.x', + name: 'deep', + encapsulate: true + })) + + fastify.register(fp(function genericPlugin (fastify, opts, next) { + t.assert.strictEqual(fastify.pluginName, 'deep-deep', 'should be deep-deep') + + fastify.register(fp(getFn('deep-deep-deep'), { + fastify: '5.x', + name: 'deep-deep-deep', + encapsulate: true + })) + + fastify.register(fp(getFn('deep-deep -> not-encapsulated-2'), { + fastify: '5.x', + name: 'not-encapsulated-2' + })) + + next() + }, { + fastify: '5.x', + name: 'deep-deep', + encapsulate: true + })) + + fastify.register(fp(getFn('plugin -> not-encapsulated'), { + fastify: '5.x', + name: 'not-encapsulated' + })) + + next() + }) + + await fastify.ready() + + function getFn (expectedName) { + return function genericPlugin (fastify, opts, next) { + t.assert.strictEqual(fastify.pluginName, expectedName, `should be ${expectedName}`) + next() + } + } +}) diff --git a/node_modules/fastify-plugin/test/toCamelCase.test.js b/node_modules/fastify-plugin/test/toCamelCase.test.js new file mode 100644 index 0000000..0cc02db --- /dev/null +++ b/node_modules/fastify-plugin/test/toCamelCase.test.js @@ -0,0 +1,24 @@ +'use strict' + +const { test } = require('node:test') +const toCamelCase = require('../lib/toCamelCase') + +test('from kebab-case to camelCase', (t) => { + t.plan(1) + t.assert.strictEqual(toCamelCase('hello-world'), 'helloWorld') +}) + +test('from @-prefixed named imports', (t) => { + t.plan(1) + t.assert.strictEqual(toCamelCase('@hello/world'), 'helloWorld') +}) + +test('from @-prefixed named kebab-case to camelCase', (t) => { + t.plan(1) + t.assert.strictEqual(toCamelCase('@hello/my-world'), 'helloMyWorld') +}) + +test('from kebab-case to camelCase multiple words', (t) => { + t.plan(1) + t.assert.strictEqual(toCamelCase('hello-long-world'), 'helloLongWorld') +}) diff --git a/node_modules/fastify-plugin/types/example-async.test-d.ts b/node_modules/fastify-plugin/types/example-async.test-d.ts new file mode 100644 index 0000000..fe2a1cd --- /dev/null +++ b/node_modules/fastify-plugin/types/example-async.test-d.ts @@ -0,0 +1,19 @@ +import { FastifyPluginAsync } from "fastify"; + +type FastifyExampleAsync = FastifyPluginAsync; + +declare namespace fastifyExampleAsync { + + export interface FastifyExampleAsyncOptions { + foo?: 'bar' + } + + export interface FastifyExampleAsyncPluginOptions extends FastifyExampleAsyncOptions { + } + export const fastifyExampleAsync: FastifyExampleAsync + export { fastifyExampleAsync as default } +} + +declare function fastifyExampleAsync(...params: Parameters): ReturnType + +export default fastifyExampleAsync diff --git a/node_modules/fastify-plugin/types/example-callback.test-d.ts b/node_modules/fastify-plugin/types/example-callback.test-d.ts new file mode 100644 index 0000000..c23f1de --- /dev/null +++ b/node_modules/fastify-plugin/types/example-callback.test-d.ts @@ -0,0 +1,19 @@ +import { FastifyPluginCallback } from "fastify"; + +type FastifyExampleCallback = FastifyPluginCallback; + +declare namespace fastifyExampleCallback { + + export interface FastifyExampleCallbackOptions { + foo?: 'bar' + } + + export interface FastifyExampleCallbackPluginOptions extends FastifyExampleCallbackOptions { + } + export const fastifyExampleCallback: FastifyExampleCallback + export { fastifyExampleCallback as default } +} + +declare function fastifyExampleCallback(...params: Parameters): ReturnType + +export default fastifyExampleCallback diff --git a/node_modules/fastify-plugin/types/plugin.d.ts b/node_modules/fastify-plugin/types/plugin.d.ts new file mode 100644 index 0000000..02d874b --- /dev/null +++ b/node_modules/fastify-plugin/types/plugin.d.ts @@ -0,0 +1,61 @@ +/// + +import { + FastifyPluginCallback, + FastifyPluginAsync, + FastifyPluginOptions, + RawServerBase, + RawServerDefault, + FastifyTypeProvider, + FastifyTypeProviderDefault, + FastifyBaseLogger, +} from 'fastify' + +type FastifyPlugin = typeof fastifyPlugin + +declare namespace fastifyPlugin { + export interface PluginMetadata { + /** Bare-minimum version of Fastify for your plugin, just add the semver range that you need. */ + fastify?: string, + name?: string, + /** Decorator dependencies for this plugin */ + decorators?: { + fastify?: (string | symbol)[], + reply?: (string | symbol)[], + request?: (string | symbol)[] + }, + /** The plugin dependencies */ + dependencies?: string[], + encapsulate?: boolean + } + // Exporting PluginOptions for backward compatibility after renaming it to PluginMetadata + /** + * @deprecated Use PluginMetadata instead + */ + export interface PluginOptions extends PluginMetadata {} + + export const fastifyPlugin: FastifyPlugin + export { fastifyPlugin as default } +} + +/** + * This function does three things for you: + * 1. Add the `skip-override` hidden property + * 2. Check bare-minimum version of Fastify + * 3. Pass some custom metadata of the plugin to Fastify + * @param fn Fastify plugin function + * @param options Optional plugin options + */ + +declare function fastifyPlugin< + Options extends FastifyPluginOptions = Record, + RawServer extends RawServerBase = RawServerDefault, + TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, + Logger extends FastifyBaseLogger = FastifyBaseLogger, + Fn extends FastifyPluginCallback | FastifyPluginAsync = FastifyPluginCallback +>( + fn: Fn extends unknown ? Fn extends (...args: any) => Promise ? FastifyPluginAsync : FastifyPluginCallback : Fn, + options?: fastifyPlugin.PluginMetadata | string +): Fn; + +export = fastifyPlugin diff --git a/node_modules/fastify-plugin/types/plugin.test-d.ts b/node_modules/fastify-plugin/types/plugin.test-d.ts new file mode 100644 index 0000000..70ec5a7 --- /dev/null +++ b/node_modules/fastify-plugin/types/plugin.test-d.ts @@ -0,0 +1,166 @@ +import fastifyPlugin from '..'; +import fastify, { FastifyPluginCallback, FastifyPluginAsync, FastifyError, FastifyInstance, FastifyPluginOptions, RawServerDefault, FastifyTypeProviderDefault, FastifyBaseLogger } from 'fastify'; +import { expectAssignable, expectError, expectNotType, expectType } from 'tsd' +import { Server } from "node:https" +import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox" +import fastifyExampleCallback from './example-callback.test-d'; +import fastifyExampleAsync from './example-async.test-d'; + +interface Options { + foo: string +} + +const testSymbol = Symbol('foobar') + +// Callback + +const pluginCallback: FastifyPluginCallback = (fastify, options, next) => { } +expectType(fastifyPlugin(pluginCallback)) + +const pluginCallbackWithTypes = (fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void): void => { } +expectAssignable(fastifyPlugin(pluginCallbackWithTypes)) +expectNotType(fastifyPlugin(pluginCallbackWithTypes)) + +expectAssignable(fastifyPlugin((fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void): void => { })) +expectNotType(fastifyPlugin((fastify: FastifyInstance, options: FastifyPluginOptions, next: (error?: FastifyError) => void): void => { })) + +expectType(fastifyPlugin(pluginCallback, '')) +expectType(fastifyPlugin(pluginCallback, { + fastify: '', + name: '', + decorators: { + fastify: ['', testSymbol], + reply: ['', testSymbol], + request: ['', testSymbol] + }, + dependencies: [''], + encapsulate: true +})) + +const pluginCallbackWithOptions: FastifyPluginCallback = (fastify, options, next) => { + expectType(options.foo) +} + +expectType>(fastifyPlugin(pluginCallbackWithOptions)) + +const pluginCallbackWithServer: FastifyPluginCallback = (fastify, options, next) => { + expectType(fastify.server) +} + +expectType>(fastifyPlugin(pluginCallbackWithServer)) + +const pluginCallbackWithTypeProvider: FastifyPluginCallback = (fastify, options, next) => { } + +expectType>(fastifyPlugin(pluginCallbackWithTypeProvider)) + +// Async + +const pluginAsync: FastifyPluginAsync = async (fastify, options) => { } +expectType(fastifyPlugin(pluginAsync)) + +const pluginAsyncWithTypes = async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise => { } +expectType>(fastifyPlugin(pluginAsyncWithTypes)) + +expectType>(fastifyPlugin(async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise => { })) +expectType(fastifyPlugin(pluginAsync, '')) +expectType(fastifyPlugin(pluginAsync, { + fastify: '', + name: '', + decorators: { + fastify: ['', testSymbol], + reply: ['', testSymbol], + request: ['', testSymbol] + }, + dependencies: [''], + encapsulate: true +})) + +const pluginAsyncWithOptions: FastifyPluginAsync = async (fastify, options) => { + expectType(options.foo) +} + +expectType>(fastifyPlugin(pluginAsyncWithOptions)) + +const pluginAsyncWithServer: FastifyPluginAsync = async (fastify, options) => { + expectType(fastify.server) +} + +expectType>(fastifyPlugin(pluginAsyncWithServer)) + +const pluginAsyncWithTypeProvider: FastifyPluginAsync = async (fastify, options) => { } + +expectType>(fastifyPlugin(pluginAsyncWithTypeProvider)) + +// Fastify register + +const server = fastify() +server.register(fastifyPlugin(pluginCallback)) +server.register(fastifyPlugin(pluginCallbackWithTypes)) +server.register(fastifyPlugin(pluginCallbackWithOptions)) +server.register(fastifyPlugin(pluginCallbackWithServer)) +server.register(fastifyPlugin(pluginCallbackWithTypeProvider)) +server.register(fastifyPlugin(pluginAsync)) +server.register(fastifyPlugin(pluginAsyncWithTypes)) +server.register(fastifyPlugin(pluginAsyncWithOptions)) +server.register(fastifyPlugin(pluginAsyncWithServer)) +server.register(fastifyPlugin(pluginAsyncWithTypeProvider)) + +// properly handling callback and async +fastifyPlugin(function (fastify, options, next) { + expectType(fastify) + expectType>(options) + expectType<(err?: Error) => void>(next) +}) + +fastifyPlugin(function (fastify, options, next) { + expectType(fastify) + expectType(options) + expectType<(err?: Error) => void>(next) +}) + +fastifyPlugin(async function (fastify, options) { + expectType(fastify) + expectType(options) +}) + +expectAssignable>(fastifyPlugin(async function (fastify: FastifyInstance, options: Options) { })) +expectNotType(fastifyPlugin(async function (fastify: FastifyInstance, options: Options) { })) + +fastifyPlugin(async function (fastify, options: Options) { + expectType(fastify) + expectType(options) +}) + +fastifyPlugin(async function (fastify, options) { + expectType(fastify) + expectType>(options) +}) + +expectError( + fastifyPlugin(async function (fastify, options: Options, next) { + expectType(fastify) + expectType(options) + }) +) +expectAssignable>(fastifyPlugin(function (fastify, options, next) { })) +expectNotType(fastifyPlugin(function (fastify, options, next) { })) + +fastifyPlugin(function (fastify, options: Options, next) { + expectType(fastify) + expectType(options) + expectType<(err?: Error) => void>(next) +}) + +expectError( + fastifyPlugin(function (fastify, options: Options, next) { + expectType(fastify) + expectType(options) + return Promise.resolve() + }) +) + +server.register(fastifyExampleCallback, { foo: 'bar' }) +expectError(server.register(fastifyExampleCallback, { foo: 'baz' })) + +server.register(fastifyExampleAsync, { foo: 'bar' }) +expectError(server.register(fastifyExampleAsync, { foo: 'baz' })) diff --git a/package-lock.json b/package-lock.json index 5959316..e4f29a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "dotenv": "^17.2.2", "fastify": "^5.6.0", + "fastify-plugin": "^5.0.1", "mongodb": "^6.20.0" } }, @@ -329,6 +330,12 @@ "toad-cache": "^3.7.0" } }, + "node_modules/fastify-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.0.1.tgz", + "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", diff --git a/package.json b/package.json index 253a426..9b013c0 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "dotenv": "^17.2.2", "fastify": "^5.6.0", + "fastify-plugin": "^5.0.1", "mongodb": "^6.20.0" } } diff --git a/server.js b/server.js new file mode 100644 index 0000000..02ee447 --- /dev/null +++ b/server.js @@ -0,0 +1,131 @@ +// 1. 导入依赖 +require('dotenv').config(); // 尽早加载环境变量 +const fastify = require('fastify')({ logger: true }); // 开启日志,方便调试 +const { ObjectId } = require('mongodb'); // 导入 ObjectId 用于ID操作 + +// 2. 注册 MongoDB 插件 +// Fastify 的插件系统能确保在路由处理前,数据库连接已经准备就绪 +fastify.register(require('./db-connector')); + +// ------------------- API 路由定义 ------------------- + +/** + * @api {post} /todos 创建一个新的 Todo + */ +fastify.post('/todos', async (request, reply) => { + // fastify.mongo.db 是由我们的插件注入的 + const todosCollection = fastify.mongo.db.collection('todos'); + + // 从请求体中获取数据 + const { text, completed = false } = request.body; + + if (!text) { + return reply.code(400).send({ message: '`text` 字段是必需的。' }); + } + + const newTodo = { + text, + completed, + createdAt: new Date(), + }; + + const result = await todosCollection.insertOne(newTodo); + reply.code(201).send(result); // 201 Created +}); + +/** + * @api {get} /todos 获取所有 Todos + */ +fastify.get('/todos', async (request, reply) => { + const todosCollection = fastify.mongo.db.collection('todos'); + const todos = await todosCollection.find({}).toArray(); + reply.send(todos); +}); + +/** + * @api {get} /todos/:id 获取单个 Todo + */ +fastify.get('/todos/:id', async (request, reply) => { + const { id } = request.params; + + // 校验 ID 格式是否正确 + if (!ObjectId.isValid(id)) { + return reply.code(400).send({ message: '无效的ID格式。' }); + } + + const objectId = new ObjectId(id); + const todosCollection = fastify.mongo.db.collection('todos'); + const todo = await todosCollection.findOne({ _id: objectId }); + + if (!todo) { + return reply.code(404).send({ message: '未找到指定的 Todo。' }); + } + + reply.send(todo); +}); + +/** + * @api {put} /todos/:id 更新一个 Todo + */ +fastify.put('/todos/:id', async (request, reply) => { + const { id } = request.params; + const { text, completed } = request.body; + + if (!ObjectId.isValid(id)) { + return reply.code(400).send({ message: '无效的ID格式。' }); + } + + const objectId = new ObjectId(id); + const todosCollection = fastify.mongo.db.collection('todos'); + + const updateDoc = { $set: {} }; + if (text !== undefined) updateDoc.$set.text = text; + if (completed !== undefined) updateDoc.$set.completed = completed; + + // 如果没有提供任何更新字段,则返回错误 + if (Object.keys(updateDoc.$set).length === 0) { + return reply.code(400).send({ message: '请提供需要更新的字段 (text 或 completed)。' }) + } + + const result = await todosCollection.updateOne({ _id: objectId }, updateDoc); + + if (result.matchedCount === 0) { + return reply.code(404).send({ message: '未找到指定的 Todo 进行更新。' }); + } + + reply.send({ message: 'Todo 更新成功。' }); +}); + +/** + * @api {delete} /todos/:id 删除一个 Todo + */ +fastify.delete('/todos/:id', async (request, reply) => { + const { id } = request.params; + + if (!ObjectId.isValid(id)) { + return reply.code(400).send({ message: '无效的ID格式。' }); + } + + const objectId = new ObjectId(id); + const todosCollection = fastify.mongo.db.collection('todos'); + const result = await todosCollection.deleteOne({ _id: objectId }); + + if (result.deletedCount === 0) { + return reply.code(404).send({ message: '未找到指定的 Todo 进行删除。' }); + } + + reply.code(200).send({ message: 'Todo 删除成功。' }); +}); + +// 3. 启动服务器 +const start = async () => { + try { + await fastify.listen({ port: 3000 }); + fastify.log.info(`服务器运行在 ${fastify.server.address().port}`); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +start(); \ No newline at end of file