@xenterprises/fastify-xconfig 2.1.0 → 2.1.2

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/.env ADDED
@@ -0,0 +1,33 @@
1
+ # Environment variables for xConfig Plugin
2
+ # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
3
+ # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
4
+
5
+ # Database Configuration (Required)
6
+ DATABASE_URL="postgresql://user:password@localhost:5432/dbname?sslmode=require"
7
+
8
+ # Server Configuration
9
+ NODE_ENV=development
10
+ PORT=3002
11
+ FASTIFY_ADDRESS=0.0.0.0
12
+
13
+ # Error Tracking (optional)
14
+ BUGSNAG_API_KEY="your-bugsnag-api-key"
15
+
16
+ # CORS Configuration (optional)
17
+ CORS_ORIGIN="http://localhost:3000,https://example.com"
18
+ RATE_LIMIT_MAX=100
19
+ RATE_LIMIT_TIME_WINDOW="1 minute"
20
+
21
+ # ========================================
22
+ # DEPRECATED / NO LONGER USED BY XCONFIG
23
+ # ========================================
24
+ # The following services have been extracted to separate plugins:
25
+ #
26
+ # Authentication → Use @xenterprises/fastify-xauth-jwks
27
+ # Geolocation → Use @xenterprises/fastify-xgeocode
28
+ # SMS Services → Use xTwilio plugin (separate module)
29
+ # Email Services → Use separate email plugin
30
+ # Image/File Storage → Use xStorage plugin (separate module)
31
+ # Payment Processing → Use xStripe plugin (separate module)
32
+ #
33
+ # Old Stack Auth variables are deprecated - use xAuthJWSK instead
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xenterprises/fastify-xconfig",
3
3
  "type": "module",
4
- "version": "2.1.0",
4
+ "version": "2.1.2",
5
5
  "description": "Fastify configuration plugin for setting up middleware, services, and route handling.",
6
6
  "main": "src/xConfig.js",
7
7
  "scripts": {
package/CHANGELOG.md DELETED
@@ -1,189 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- ## [2.0.0] - 2025-12-28
6
-
7
- ### BREAKING CHANGES
8
-
9
- This is a major version release with significant architectural changes.
10
-
11
- #### Removed Services
12
-
13
- The following services have been extracted to dedicated plugins and are **no longer** available in xConfig:
14
-
15
- - **SendGrid Integration** - Use `@xenterprises/fastify-xsendgrid` (planned) or implement separately
16
- - **Twilio Integration** - Use `@xenterprises/fastify-xtwilio` (planned) or implement separately
17
- - **Cloudinary Integration** - Use `@xenterprises/fastify-xstorage` (planned) or implement separately
18
- - **Stripe Integration** - Use `@xenterprises/fastify-xstripe` (planned) or implement separately
19
- - **Stack Auth Integration** - Use `@xenterprises/fastify-xauth-jwks` instead
20
-
21
- #### Configuration Changes
22
-
23
- The following options are **no longer supported** in xConfig registration:
24
-
25
- ```javascript
26
- // ❌ These options are now REMOVED
27
- fastify.register(xConfig, {
28
- sendGrid: { ... }, // REMOVED - use separate plugin
29
- twilio: { ... }, // REMOVED - use separate plugin
30
- cloudinary: { ... }, // REMOVED - use separate plugin
31
- stripe: { ... }, // REMOVED - use separate plugin
32
- auth: { ... }, // REMOVED - use xAuthJWSK plugin
33
- geocode: { ... }, // REMOVED - use xGeocode plugin
34
- });
35
- ```
36
-
37
- #### New Plugin-Based Architecture
38
-
39
- xConfig now focuses on core middleware only. Use these dedicated plugins for removed services:
40
-
41
- ```javascript
42
- import Fastify from 'fastify';
43
- import xConfig from '@xenterprises/fastify-xconfig';
44
- import xAuthJWSK from '@xenterprises/fastify-xauth-jwks';
45
- import xGeocode from '@xenterprises/fastify-xgeocode';
46
-
47
- const fastify = Fastify();
48
-
49
- // Core config (middleware & database)
50
- await fastify.register(xConfig, {
51
- prisma: {},
52
- cors: { origin: ['https://example.com'] },
53
- rateLimit: { max: 100, timeWindow: '1 minute' },
54
- });
55
-
56
- // Dedicated plugins for other services
57
- await fastify.register(xAuthJWSK, { /* config */ });
58
- await fastify.register(xGeocode, { /* config */ });
59
- ```
60
-
61
- ### Added
62
-
63
- - ✅ Enhanced CORS security - Environment-aware origin defaults
64
- - Production: Only allows configured domain
65
- - Development: Allows localhost:3000 and localhost:3001
66
- - ✅ Improved health check endpoint - Better environment validation
67
- - ✅ Test suite - 8 comprehensive tests covering core functionality
68
- - ✅ Production hardening - Security audit and best practices applied
69
-
70
- ### Changed
71
-
72
- - **Core Focus**: xConfig now provides only essential middleware and configuration
73
- - **Plugin Architecture**: Services moved to dedicated, focused plugins
74
- - **Dependencies**: Reduced from 22 to 11 (50% reduction)
75
- - **Bundle Size**: ~42% smaller with removed integrations
76
- - **Code Quality**: Improved maintainability and single responsibility
77
-
78
- ### Fixed
79
-
80
- - ⚠️ Prisma disconnect hook - Now properly called only once on shutdown
81
- - ✅ Health endpoint - No longer fails when Prisma is disabled
82
- - ✅ Lifecycle hooks - Fixed "already listening" errors in test scenarios
83
-
84
- ### Security
85
-
86
- - **npm audit**: 0 vulnerabilities
87
- - **CORS**: Secure defaults based on environment
88
- - **Error Handling**: Stack traces hidden in production
89
- - **Error Reporting**: Optional Bugsnag integration
90
-
91
- ### Migration Guide
92
-
93
- #### Before (v1.x)
94
-
95
- ```javascript
96
- fastify.register(xConfig, {
97
- sendGrid: { apiKey: '...' },
98
- twilio: { accountSid: '...', authToken: '...', phoneNumber: '...' },
99
- cloudinary: { cloudName: '...' },
100
- auth: { admin: { ... }, user: { ... } },
101
- geocode: { apiKey: '...' },
102
- });
103
- ```
104
-
105
- #### After (v2.0)
106
-
107
- ```javascript
108
- // Step 1: Core config only
109
- fastify.register(xConfig, {
110
- prisma: {},
111
- cors: { active: true, origin: ['https://yourdomain.com'] },
112
- rateLimit: { max: 100, timeWindow: '1 minute' },
113
- });
114
-
115
- // Step 2: Add service plugins
116
- fastify.register(xAuthJWSK, {
117
- paths: {
118
- admin: { pathPattern: '/admin', jwksUrl: '...' },
119
- api: { pathPattern: '/api', jwksUrl: '...' }
120
- }
121
- });
122
-
123
- fastify.register(xGeocode, {
124
- apiKey: process.env.GEOCODIO_API_KEY
125
- });
126
-
127
- // Step 3: Implement other services separately or wait for dedicated plugins
128
- // - Email: Use SendGrid SDK directly or wait for @xenterprises/fastify-xsendgrid
129
- // - SMS: Use Twilio SDK directly or wait for @xenterprises/fastify-xtwilio
130
- // - Storage: Use AWS S3 SDK directly or wait for @xenterprises/fastify-xstorage
131
- // - Payments: Use Stripe SDK directly or wait for @xenterprises/fastify-xstripe
132
- ```
133
-
134
- ### Deprecation Notes
135
-
136
- - The `auth` option is deprecated. Use `@xenterprises/fastify-xauth-jwks` instead
137
- - The `geocode` option is deprecated. Use `@xenterprises/fastify-xgeocode` instead
138
- - Stack Auth integration is deprecated. Use `@xenterprises/fastify-xauth-jwks` instead
139
-
140
- ### Environment Variables
141
-
142
- **Removed** (no longer supported):
143
- - `ADMIN_STACK_PROJECT_ID`
144
- - `ADMIN_STACK_PUBLISHABLE_CLIENT_KEY`
145
- - `ADMIN_STACK_SECRET_SERVER_KEY`
146
- - `USER_STACK_PROJECT_ID`
147
- - `USER_STACK_PUBLISHABLE_CLIENT_KEY`
148
- - `USER_STACK_SECRET_SERVER_KEY`
149
- - `SENDGRID_API_KEY`
150
- - `SENDGRID_API_EMAIL_VALIDATION_KEY`
151
- - `SENDGRID_FROM_EMAIL`
152
- - `TWILIO_ACCOUNT_SID`
153
- - `TWILIO_AUTH_TOKEN`
154
- - `TWILIO_PHONE_NUMBER`
155
- - `CLOUDINARY_CLOUD_NAME`
156
- - `CLOUDINARY_API_KEY`
157
- - `CLOUDINARY_API_SECRET`
158
- - `CLOUDINARY_BASE_PATH`
159
- - `STRIPE_API_KEY`
160
-
161
- **Still Supported**:
162
- - `DATABASE_URL` - PostgreSQL connection string (if Prisma enabled)
163
- - `NODE_ENV` - Application environment
164
- - `PORT` - Server port
165
- - `FASTIFY_ADDRESS` - Server address
166
- - `BUGSNAG_API_KEY` - Error tracking (optional)
167
- - `CORS_ORIGIN` - Allowed CORS origins
168
- - `RATE_LIMIT_MAX` - Rate limit threshold
169
- - `RATE_LIMIT_TIME_WINDOW` - Rate limit window
170
-
171
- ### Testing
172
-
173
- - ✅ All tests passing (8/8)
174
- - ✅ npm audit clean (0 vulnerabilities)
175
- - ✅ Production hardening complete
176
- - ✅ Graceful shutdown verified
177
- - ✅ Health endpoint validated
178
-
179
- ### Dependencies Updated
180
-
181
- - Updated `@prisma/client` to ^7.2.0
182
- - All @fastify packages verified and up-to-date
183
- - Removed: @sendgrid/mail, @sendgrid/client, twilio, cloudinary, jose, stripe
184
-
185
- ---
186
-
187
- ## [1.x] - Previous Versions
188
-
189
- See git history for previous releases.
package/server/app.js DELETED
@@ -1,43 +0,0 @@
1
- // server/app.js
2
- import Fastify from 'fastify';
3
- import xConfig from '../src/xConfig.js'; // Import your plugin correctly
4
-
5
- const fastify = Fastify();
6
-
7
-
8
- export default async function (fastify, opts) {
9
- fastify.register(xConfig, {
10
- professional: false,
11
- fancyErrors: true,
12
- prisma: {
13
- active: false,
14
- },
15
- bugsnag: {
16
- apiKey: process.env.BUGSNAG_API_KEY
17
- },
18
- rateLimit: {
19
- max: process.env.RATE_LIMIT_MAX || 100,
20
- timeWindow: process.env.RATE_LIMIT_TIME_WINDOW || '1 minute'
21
- },
22
- cors: {
23
- active: true,
24
- origin: process.env.CORS_ORIGIN || ['http://localhost:3000'],
25
- credentials: true
26
- },
27
- multipart: {
28
- limits: {
29
- fileSize: 52428800 // 50MB
30
- }
31
- },
32
- underPressure: {
33
- maxEventLoopDelay: 1000,
34
- maxHeapUsedBytes: 1000000000,
35
- maxRssBytes: 1000000000
36
- }
37
- }); // Register the default export, which should be a function
38
- fastify.get('/', async (request, reply) => {
39
- console.log(fastify.xEcho())
40
- return { status: fastify.xEcho() }
41
- })
42
-
43
- };
package/test/index.js DELETED
@@ -1,17 +0,0 @@
1
- // test.js
2
- const fastify = require('fastify')();
3
- const myPlugin = require('../src/xConfig');
4
-
5
- fastify.register(myPlugin);
6
-
7
- fastify.get('/', async (request, reply) => {
8
- return { message: fastify.myPluginMethod() };
9
- });
10
-
11
- try {
12
- const address = await fastify.listen({ port: 3000 });
13
- console.log(`Server running at ${address}`);
14
- } catch (err) {
15
- console.error(err);
16
- process.exit(1);
17
- }
@@ -1,278 +0,0 @@
1
- // test/xConfig.test.js
2
- import { test } from "node:test";
3
- import assert from "node:assert";
4
- import Fastify from "fastify";
5
- import xConfig from "../src/xConfig.js";
6
-
7
- // Minimal config for testing
8
- const minimalConfig = {
9
- prisma: { active: false },
10
- bugsnag: { active: false },
11
- };
12
-
13
- test("xConfig Plugin - registers successfully", async () => {
14
- const fastify = Fastify({ logger: false });
15
- try {
16
- await fastify.register(xConfig, minimalConfig);
17
- assert.ok(true, "Plugin registered successfully");
18
- } finally {
19
- await fastify.close();
20
- }
21
- });
22
-
23
- test("xConfig Plugin - provides utility decorators", async () => {
24
- const fastify = Fastify({ logger: false });
25
- try {
26
- await fastify.register(xConfig, minimalConfig);
27
- // At least one utility decorator should be available
28
- const hasUtilityDecorators =
29
- fastify.xEcho !== undefined ||
30
- fastify.xUUID !== undefined;
31
- assert.ok(hasUtilityDecorators, "At least one utility decorator is available");
32
- } finally {
33
- await fastify.close();
34
- }
35
- });
36
-
37
- test("xConfig Plugin - has health route registered", async () => {
38
- const fastify = Fastify({ logger: false });
39
- try {
40
- const routes = [];
41
- fastify.addHook("onRoute", (r) => routes.push(r));
42
- await fastify.register(xConfig, minimalConfig);
43
-
44
- // Check that health route is present
45
- const hasHealthRoute = routes.some((r) => r.url === "/health");
46
- assert.ok(hasHealthRoute, "Health endpoint route is registered");
47
- } finally {
48
- await fastify.close();
49
- }
50
- });
51
-
52
- test("xConfig Plugin - accepts custom CORS config", async () => {
53
- const fastify = Fastify({ logger: false });
54
- const customConfig = {
55
- ...minimalConfig,
56
- cors: { origin: "http://example.com" },
57
- };
58
- try {
59
- await fastify.register(xConfig, customConfig);
60
- assert.ok(true, "Custom CORS config accepted");
61
- } finally {
62
- await fastify.close();
63
- }
64
- });
65
-
66
- test("xConfig Plugin - accepts custom rate limit config", async () => {
67
- const fastify = Fastify({ logger: false });
68
- const customConfig = {
69
- ...minimalConfig,
70
- rateLimit: { max: 50, timeWindow: "1 minute" },
71
- };
72
- try {
73
- await fastify.register(xConfig, customConfig);
74
- assert.ok(true, "Custom rate limit config accepted");
75
- } finally {
76
- await fastify.close();
77
- }
78
- });
79
-
80
- test("xConfig Plugin - under pressure monitoring available", async () => {
81
- const fastify = Fastify({ logger: false });
82
- try {
83
- const customConfig = {
84
- ...minimalConfig,
85
- underPressure: {
86
- maxEventLoopDelay: 1000,
87
- maxHeapUsedBytes: 1000000000,
88
- maxRssBytes: 1000000000
89
- }
90
- };
91
- await fastify.register(xConfig, customConfig);
92
- assert.ok(true, "Under pressure config accepted");
93
- } finally {
94
- await fastify.close();
95
- }
96
- });
97
-
98
- test("xConfig Plugin - multipart handling available", async () => {
99
- const fastify = Fastify({ logger: false });
100
- try {
101
- const customConfig = {
102
- ...minimalConfig,
103
- multipart: { limits: { fileSize: 52428800 } }
104
- };
105
- await fastify.register(xConfig, customConfig);
106
- assert.ok(true, "Multipart config accepted");
107
- } finally {
108
- await fastify.close();
109
- }
110
- });
111
-
112
- test("xConfig Plugin - health check endpoint responds", async () => {
113
- const fastify = Fastify({ logger: false });
114
- try {
115
- const routes = [];
116
- fastify.addHook("onRoute", (r) => routes.push(r));
117
- await fastify.register(xConfig, minimalConfig);
118
-
119
- const response = await fastify.inject({
120
- method: "GET",
121
- url: "/health"
122
- });
123
- assert.equal(response.statusCode, 200, "Health endpoint returns 200");
124
- const body = JSON.parse(response.body);
125
- assert.ok(body.status, "Health response includes status field");
126
- } finally {
127
- await fastify.close();
128
- }
129
- });
130
-
131
- test("xConfig Utilities - xUUID generates valid UUIDs", async () => {
132
- const fastify = Fastify({ logger: false });
133
- try {
134
- await fastify.register(xConfig, minimalConfig);
135
-
136
- const uuid1 = fastify.xRandomUUID();
137
- const uuid2 = fastify.xRandomUUID();
138
-
139
- // Check that randomUUID is a function
140
- assert.equal(typeof fastify.xRandomUUID, "function", "randomUUID is a function");
141
-
142
- // Check that UUIDs are generated
143
- assert.ok(uuid1, "UUID is generated");
144
- assert.ok(uuid2, "Second UUID is generated");
145
-
146
- // Check that UUIDs are different
147
- assert.notEqual(uuid1, uuid2, "UUIDs are unique");
148
-
149
- // Check UUID format (36 chars, with dashes at positions 8, 13, 18, 23)
150
- assert.match(uuid1, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "UUID matches format");
151
- } finally {
152
- await fastify.close();
153
- }
154
- });
155
-
156
- test("xConfig Utilities - xSlugify handles various inputs", async () => {
157
- const fastify = Fastify({ logger: false });
158
- try {
159
- await fastify.register(xConfig, minimalConfig);
160
-
161
- // Test basic slugification
162
- assert.equal(fastify.xSlugify("Hello World"), "hello-world", "Converts to lowercase and replaces spaces");
163
- assert.equal(fastify.xSlugify("UPPERCASE"), "uppercase", "Converts uppercase to lowercase");
164
- assert.equal(fastify.xSlugify("Multiple Spaces"), "multiple-spaces", "Collapses multiple spaces");
165
-
166
- // Test special character removal
167
- assert.equal(fastify.xSlugify("Hello@World!"), "helloworld", "Removes special characters");
168
- assert.equal(fastify.xSlugify("Test-String"), "test-string", "Preserves dashes");
169
-
170
- // Test edge cases
171
- assert.equal(fastify.xSlugify(" Trim "), "trim", "Trims leading and trailing spaces");
172
- assert.equal(fastify.xSlugify("---multiple-dashes---"), "multiple-dashes", "Collapses multiple dashes");
173
- assert.equal(fastify.xSlugify(""), "", "Handles empty string");
174
-
175
- // Test with numbers
176
- assert.equal(fastify.xSlugify("Test123"), "test123", "Preserves numbers");
177
- } finally {
178
- await fastify.close();
179
- }
180
- });
181
-
182
- test("xConfig Utilities - xEcho returns status message", async () => {
183
- const fastify = Fastify({ logger: false });
184
- try {
185
- await fastify.register(xConfig, minimalConfig);
186
-
187
- const echo = fastify.xEcho();
188
- assert.ok(echo, "xEcho returns a value");
189
- assert.equal(typeof echo, "string", "xEcho returns a string");
190
- } finally {
191
- await fastify.close();
192
- }
193
- });
194
-
195
- test("xConfig Health Endpoint - response structure validation", async () => {
196
- const fastify = Fastify({ logger: false });
197
- try {
198
- await fastify.register(xConfig, minimalConfig);
199
-
200
- const response = await fastify.inject({
201
- method: "GET",
202
- url: "/health"
203
- });
204
-
205
- assert.equal(response.statusCode, 200, "Returns 200 status");
206
-
207
- const body = JSON.parse(response.body);
208
- assert.ok(body.status, "Has status field");
209
- assert.ok(body.timestamp, "Has timestamp field");
210
- assert.ok(body.details, "Has details object");
211
- assert.ok(typeof body.details === "object", "Details is an object");
212
- } finally {
213
- await fastify.close();
214
- }
215
- });
216
-
217
- test("xConfig Configuration - CORS disabled when active is false", async () => {
218
- const fastify = Fastify({ logger: false });
219
- const customConfig = {
220
- ...minimalConfig,
221
- cors: { active: false },
222
- };
223
- try {
224
- await fastify.register(xConfig, customConfig);
225
- assert.ok(true, "Plugin accepts cors.active: false");
226
- } finally {
227
- await fastify.close();
228
- }
229
- });
230
-
231
- test("xConfig Configuration - rate limit disabled when active is false", async () => {
232
- const fastify = Fastify({ logger: false });
233
- const customConfig = {
234
- ...minimalConfig,
235
- rateLimit: { active: false },
236
- };
237
- try {
238
- await fastify.register(xConfig, customConfig);
239
- assert.ok(true, "Plugin accepts rateLimit.active: false");
240
- } finally {
241
- await fastify.close();
242
- }
243
- });
244
-
245
- test("xConfig Configuration - fancy errors can be disabled", async () => {
246
- const fastify = Fastify({ logger: false });
247
- const customConfig = {
248
- ...minimalConfig,
249
- fancyErrors: false,
250
- };
251
- try {
252
- await fastify.register(xConfig, customConfig);
253
- assert.ok(true, "Plugin accepts fancyErrors: false");
254
- } finally {
255
- await fastify.close();
256
- }
257
- });
258
-
259
- test("xConfig Integration - all decorators available together", async () => {
260
- const fastify = Fastify({ logger: false });
261
- try {
262
- await fastify.register(xConfig, minimalConfig);
263
-
264
- // Verify all expected decorators exist
265
- assert.ok(fastify.xEcho, "xEcho decorator available");
266
- assert.ok(fastify.xRandomUUID, "randomUUID decorator available");
267
- assert.ok(fastify.xGenerateUUID, "generateUUID decorator available");
268
- assert.ok(fastify.xSlugify, "slugify decorator available");
269
-
270
- // Verify they're functions
271
- assert.equal(typeof fastify.xEcho, "function", "xEcho is a function");
272
- assert.equal(typeof fastify.xRandomUUID, "function", "randomUUID is a function");
273
- assert.equal(typeof fastify.xGenerateUUID, "function", "generateUUID is a function");
274
- assert.equal(typeof fastify.xSlugify, "function", "slugify is a function");
275
- } finally {
276
- await fastify.close();
277
- }
278
- });