@xenterprises/fastify-xconfig 2.0.2 → 2.1.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/.env +33 -0
- package/.gitlab-ci.yml +45 -0
- package/package.json +1 -1
- package/test/index.js +0 -17
- package/test/xConfig.test.js +0 -278
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/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# GitLab CI/CD Pipeline - xConfig
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Runs tests on merge requests and commits to main/master
|
|
5
|
+
|
|
6
|
+
stages:
|
|
7
|
+
- test
|
|
8
|
+
|
|
9
|
+
variables:
|
|
10
|
+
NODE_ENV: test
|
|
11
|
+
|
|
12
|
+
# ============================================================================
|
|
13
|
+
# Shared Configuration
|
|
14
|
+
# ============================================================================
|
|
15
|
+
.shared_rules: &shared_rules
|
|
16
|
+
rules:
|
|
17
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
18
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
19
|
+
- if: '$CI_COMMIT_BRANCH == "master"'
|
|
20
|
+
- if: '$CI_COMMIT_TAG'
|
|
21
|
+
|
|
22
|
+
# ============================================================================
|
|
23
|
+
# STAGE: TEST
|
|
24
|
+
# ============================================================================
|
|
25
|
+
test:
|
|
26
|
+
stage: test
|
|
27
|
+
image: node:20-alpine
|
|
28
|
+
<<: *shared_rules
|
|
29
|
+
|
|
30
|
+
cache:
|
|
31
|
+
key: ${CI_COMMIT_REF_SLUG}
|
|
32
|
+
paths:
|
|
33
|
+
- node_modules/
|
|
34
|
+
|
|
35
|
+
before_script:
|
|
36
|
+
- npm ci
|
|
37
|
+
|
|
38
|
+
script:
|
|
39
|
+
- echo "Running xConfig tests..."
|
|
40
|
+
- npm test
|
|
41
|
+
- npm audit --audit-level=high || true
|
|
42
|
+
|
|
43
|
+
retry:
|
|
44
|
+
max: 2
|
|
45
|
+
when: runner_system_failure
|
package/package.json
CHANGED
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
|
-
}
|
package/test/xConfig.test.js
DELETED
|
@@ -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
|
-
});
|