@xenterprises/fastify-xconfig 1.1.8 → 2.0.1
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/CHANGELOG.md +189 -0
- package/README.md +127 -135
- package/package.json +16 -24
- package/server/app.js +9 -46
- package/src/lifecycle/xFastifyAfter.js +4 -4
- package/src/middleware/cors.js +6 -1
- package/src/utils/health.js +10 -11
- package/src/xConfig.js +1 -29
- package/test/index.js +6 -6
- package/test/xConfig.test.js +278 -0
- package/xConfigReference.js +103 -1505
- package/src/auth/admin.js +0 -182
- package/src/auth/portal.js +0 -176
- package/src/integrations/cloudinary.js +0 -98
- package/src/integrations/geocode.js +0 -43
- package/src/integrations/sendgrid.js +0 -58
- package/src/integrations/twilio.js +0 -146
- package/xConfigWorkingList.js +0 -720
package/src/utils/health.js
CHANGED
|
@@ -103,17 +103,16 @@ export async function setupHealth(fastify, options) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Configuration validation
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
status.details.missingEnvVars = missingEnvVars;
|
|
106
|
+
// Only check required env vars if Prisma is enabled
|
|
107
|
+
if (fastify.prisma) {
|
|
108
|
+
const requiredEnvVars = ["DATABASE_URL"];
|
|
109
|
+
const missingEnvVars = requiredEnvVars.filter(
|
|
110
|
+
(varName) => !process.env[varName]
|
|
111
|
+
);
|
|
112
|
+
if (missingEnvVars.length > 0) {
|
|
113
|
+
status.status = "degraded";
|
|
114
|
+
status.details.missingEnvVars = missingEnvVars;
|
|
115
|
+
}
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
// Determine overall health
|
package/src/xConfig.js
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
// src/xConfig.js
|
|
2
2
|
import fp from "fastify-plugin";
|
|
3
3
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
4
|
-
/*
|
|
5
|
-
* Auth
|
|
6
|
-
*/
|
|
7
|
-
import { setupAdminAuth } from "./auth/admin.js";
|
|
8
|
-
import { setupAuth } from "./auth/portal.js";
|
|
9
|
-
|
|
10
4
|
|
|
11
5
|
/*
|
|
12
6
|
* Integrations
|
|
13
7
|
*/
|
|
14
8
|
import { setupPrisma } from "./integrations/prisma.js";
|
|
15
|
-
import { setupSendgrid } from "./integrations/sendgrid.js";
|
|
16
|
-
import { setupTwilio } from "./integrations/twilio.js";
|
|
17
|
-
import { setupCloudinary } from "./integrations/cloudinary.js";
|
|
18
|
-
import { setupGeocode } from "./integrations/geocode.js";
|
|
19
9
|
|
|
20
10
|
/*
|
|
21
11
|
* Lifecycle
|
|
22
12
|
*/
|
|
23
13
|
import { xFastifyAfter } from "./lifecycle/xFastifyAfter.js";
|
|
24
14
|
|
|
25
|
-
|
|
26
15
|
/*
|
|
27
16
|
* Middleware
|
|
28
17
|
*/
|
|
@@ -47,16 +36,10 @@ async function xConfig(fastify, options) {
|
|
|
47
36
|
fancyErrors = true,
|
|
48
37
|
prisma: prismaOptions = {},
|
|
49
38
|
bugsnag: bugsnagOptions = {},
|
|
50
|
-
stripe: stripeOptions = {},
|
|
51
|
-
sendGrid: sendGridOptions = {},
|
|
52
|
-
twilio: twilioOptions = {},
|
|
53
|
-
cloudinary: cloudinaryOptions = {},
|
|
54
|
-
auth: authOptions = {},
|
|
55
39
|
cors: corsOptions = {},
|
|
56
40
|
underPressure: underPressureOptions = {},
|
|
57
41
|
multipart: multipartOptions = {},
|
|
58
42
|
rateLimit: rateLimitOptions = {},
|
|
59
|
-
geocode: geocodeOptions = {},
|
|
60
43
|
} = options;
|
|
61
44
|
|
|
62
45
|
// add starting console with emoji
|
|
@@ -73,10 +56,6 @@ async function xConfig(fastify, options) {
|
|
|
73
56
|
* Integrations
|
|
74
57
|
*/
|
|
75
58
|
await setupPrisma(fastify, prismaOptions);
|
|
76
|
-
await setupSendgrid(fastify, sendGridOptions);
|
|
77
|
-
await setupTwilio(fastify, twilioOptions);
|
|
78
|
-
await setupCloudinary(fastify, cloudinaryOptions);
|
|
79
|
-
await setupGeocode(fastify, geocodeOptions);
|
|
80
59
|
|
|
81
60
|
/*
|
|
82
61
|
* Middleware
|
|
@@ -98,13 +77,6 @@ async function xConfig(fastify, options) {
|
|
|
98
77
|
fastify.register(xUUID);
|
|
99
78
|
await setupHealth(fastify);
|
|
100
79
|
|
|
101
|
-
/*
|
|
102
|
-
* Auth
|
|
103
|
-
*/
|
|
104
|
-
await setupAuth(fastify, authOptions);
|
|
105
|
-
await setupAdminAuth(fastify, authOptions);
|
|
106
|
-
|
|
107
|
-
|
|
108
80
|
//.after() method to ensure this runs after all plugins are registered.
|
|
109
81
|
await xFastifyAfter(fastify, { professional, routes });
|
|
110
82
|
|
|
@@ -112,4 +84,4 @@ async function xConfig(fastify, options) {
|
|
|
112
84
|
|
|
113
85
|
export default fp(xConfig, {
|
|
114
86
|
name: "xConfig",
|
|
115
|
-
});
|
|
87
|
+
});
|
package/test/index.js
CHANGED
|
@@ -8,10 +8,10 @@ fastify.get('/', async (request, reply) => {
|
|
|
8
8
|
return { message: fastify.myPluginMethod() };
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.error(err);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
11
|
+
try {
|
|
12
|
+
const address = await fastify.listen({ port: 3000 });
|
|
16
13
|
console.log(`Server running at ${address}`);
|
|
17
|
-
})
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error(err);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
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.randomUUID();
|
|
137
|
+
const uuid2 = fastify.randomUUID();
|
|
138
|
+
|
|
139
|
+
// Check that randomUUID is a function
|
|
140
|
+
assert.equal(typeof fastify.randomUUID, "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.slugify("Hello World"), "hello-world", "Converts to lowercase and replaces spaces");
|
|
163
|
+
assert.equal(fastify.slugify("UPPERCASE"), "uppercase", "Converts uppercase to lowercase");
|
|
164
|
+
assert.equal(fastify.slugify("Multiple Spaces"), "multiple-spaces", "Collapses multiple spaces");
|
|
165
|
+
|
|
166
|
+
// Test special character removal
|
|
167
|
+
assert.equal(fastify.slugify("Hello@World!"), "helloworld", "Removes special characters");
|
|
168
|
+
assert.equal(fastify.slugify("Test-String"), "test-string", "Preserves dashes");
|
|
169
|
+
|
|
170
|
+
// Test edge cases
|
|
171
|
+
assert.equal(fastify.slugify(" Trim "), "trim", "Trims leading and trailing spaces");
|
|
172
|
+
assert.equal(fastify.slugify("---multiple-dashes---"), "multiple-dashes", "Collapses multiple dashes");
|
|
173
|
+
assert.equal(fastify.slugify(""), "", "Handles empty string");
|
|
174
|
+
|
|
175
|
+
// Test with numbers
|
|
176
|
+
assert.equal(fastify.slugify("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.randomUUID, "randomUUID decorator available");
|
|
267
|
+
assert.ok(fastify.generateUUID, "generateUUID decorator available");
|
|
268
|
+
assert.ok(fastify.slugify, "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.randomUUID, "function", "randomUUID is a function");
|
|
273
|
+
assert.equal(typeof fastify.generateUUID, "function", "generateUUID is a function");
|
|
274
|
+
assert.equal(typeof fastify.slugify, "function", "slugify is a function");
|
|
275
|
+
} finally {
|
|
276
|
+
await fastify.close();
|
|
277
|
+
}
|
|
278
|
+
});
|