@siddharatha/adapter-node-rolldown 1.0.0
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/LICENSE +21 -0
- package/README.md +581 -0
- package/files/env.js +50 -0
- package/files/handler.js +16 -0
- package/files/index.js +217 -0
- package/files/middlewares.js +162 -0
- package/files/shims.js +6 -0
- package/files/telemetry.js +126 -0
- package/index.d.ts +125 -0
- package/index.js +232 -0
- package/package.json +55 -0
package/files/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import polka from 'polka';
|
|
3
|
+
import { config } from 'ENV';
|
|
4
|
+
import { initTelemetry, shutdownTelemetry } from 'TELEMETRY';
|
|
5
|
+
import { createCompressionMiddleware, createStaticMiddleware, createBodyParser } from 'MIDDLEWARES';
|
|
6
|
+
import { handler } from 'HANDLER';
|
|
7
|
+
|
|
8
|
+
// WebSocket support
|
|
9
|
+
let wss = null;
|
|
10
|
+
if (config.websocket.enabled) {
|
|
11
|
+
const { WebSocketServer } = await import('ws');
|
|
12
|
+
wss = WebSocketServer;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Track server state
|
|
16
|
+
let isShuttingDown = false;
|
|
17
|
+
let server = null;
|
|
18
|
+
let wsServer = null;
|
|
19
|
+
|
|
20
|
+
// Export for instrumentation support
|
|
21
|
+
export const path = '/';
|
|
22
|
+
export let port = config.port;
|
|
23
|
+
export let host = config.host;
|
|
24
|
+
export { server };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize and start the server
|
|
28
|
+
*/
|
|
29
|
+
async function start() {
|
|
30
|
+
console.log('Starting SvelteKit high-performance server...');
|
|
31
|
+
|
|
32
|
+
// Initialize OpenTelemetry first (for request tracing)
|
|
33
|
+
await initTelemetry();
|
|
34
|
+
|
|
35
|
+
// Create HTTP server
|
|
36
|
+
server = createServer();
|
|
37
|
+
|
|
38
|
+
// Configure server performance settings
|
|
39
|
+
server.keepAliveTimeout = config.keepAliveTimeout;
|
|
40
|
+
server.headersTimeout = config.headersTimeout;
|
|
41
|
+
if (config.maxRequestsPerSocket) {
|
|
42
|
+
server.maxRequestsPerSocket = config.maxRequestsPerSocket;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create Polka app
|
|
46
|
+
const app = polka({ server });
|
|
47
|
+
|
|
48
|
+
// Health check endpoints (before other middleware)
|
|
49
|
+
if (config.healthCheck.enabled) {
|
|
50
|
+
app.get('/health', (req, res) => {
|
|
51
|
+
if (isShuttingDown) {
|
|
52
|
+
res.writeHead(503, { 'Content-Type': 'text/plain' });
|
|
53
|
+
res.end('Shutting down');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
57
|
+
res.end('OK');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
app.get('/readiness', async (req, res) => {
|
|
61
|
+
if (isShuttingDown) {
|
|
62
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
63
|
+
res.end(JSON.stringify({ status: 'not ready', reason: 'shutting down' }));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add custom readiness checks here (database, cache, etc.)
|
|
68
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
69
|
+
res.end(JSON.stringify({ status: 'ready' }));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Apply compression middleware
|
|
74
|
+
app.use(createCompressionMiddleware());
|
|
75
|
+
|
|
76
|
+
// Apply body parser
|
|
77
|
+
app.use(createBodyParser());
|
|
78
|
+
|
|
79
|
+
// Serve prerendered pages and static assets
|
|
80
|
+
app.use(createStaticMiddleware('client', true));
|
|
81
|
+
app.use(createStaticMiddleware('prerendered', false));
|
|
82
|
+
|
|
83
|
+
// Handle all other requests with SvelteKit
|
|
84
|
+
app.use((req, res) => {
|
|
85
|
+
// Convert Polka request to format expected by SvelteKit
|
|
86
|
+
handler(req, res);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Initialize WebSocket server if enabled
|
|
90
|
+
if (config.websocket.enabled && wss) {
|
|
91
|
+
wsServer = new wss({
|
|
92
|
+
server,
|
|
93
|
+
path: config.websocket.path
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
wsServer.on('connection', (ws, req) => {
|
|
97
|
+
console.log(`WebSocket connection established: ${req.url}`);
|
|
98
|
+
|
|
99
|
+
ws.on('message', (data) => {
|
|
100
|
+
// Handle WebSocket messages
|
|
101
|
+
// You can add custom logic here or expose via hooks
|
|
102
|
+
try {
|
|
103
|
+
const message = JSON.parse(data.toString());
|
|
104
|
+
console.log('WebSocket message:', message);
|
|
105
|
+
|
|
106
|
+
// Echo back for now (customize as needed)
|
|
107
|
+
ws.send(JSON.stringify({ type: 'echo', data: message }));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('WebSocket message error:', error);
|
|
110
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format' }));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
ws.on('close', () => {
|
|
115
|
+
console.log('WebSocket connection closed');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
ws.on('error', (error) => {
|
|
119
|
+
console.error('WebSocket error:', error);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Send welcome message
|
|
123
|
+
ws.send(JSON.stringify({ type: 'connected', message: 'WebSocket connected' }));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
console.log(`WebSocket server enabled on path: ${config.websocket.path}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Start listening
|
|
130
|
+
server.listen(config.port, config.host, () => {
|
|
131
|
+
console.log(`\n✓ Server running on http://${config.host}:${config.port}`);
|
|
132
|
+
console.log(` - Compression: ${config.compression ? 'enabled' : 'disabled'}`);
|
|
133
|
+
console.log(` - WebSocket: ${config.websocket.enabled ? `enabled (${config.websocket.path})` : 'disabled'}`);
|
|
134
|
+
console.log(` - OpenTelemetry: ${config.telemetry.enabled ? 'enabled' : 'disabled'}`);
|
|
135
|
+
console.log(` - Health checks: ${config.healthCheck.enabled ? 'enabled (/health, /readiness)' : 'disabled'}`);
|
|
136
|
+
console.log(` - Body limit: ${config.bodyLimit}`);
|
|
137
|
+
console.log('');
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Graceful shutdown handler
|
|
143
|
+
*/
|
|
144
|
+
async function gracefulShutdown(signal) {
|
|
145
|
+
if (isShuttingDown) {
|
|
146
|
+
console.log('Shutdown already in progress...');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
isShuttingDown = true;
|
|
151
|
+
console.log(`\n${signal} received, starting graceful shutdown...`);
|
|
152
|
+
|
|
153
|
+
// Set a timeout to force shutdown
|
|
154
|
+
const forceShutdownTimer = setTimeout(() => {
|
|
155
|
+
console.error('Forced shutdown after timeout');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}, config.gracefulShutdownTimeout);
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
// Stop accepting new connections
|
|
161
|
+
if (server) {
|
|
162
|
+
await new Promise((resolve) => {
|
|
163
|
+
server.close(() => {
|
|
164
|
+
console.log('✓ HTTP server closed');
|
|
165
|
+
resolve();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Close WebSocket connections
|
|
171
|
+
if (wsServer) {
|
|
172
|
+
console.log('Closing WebSocket connections...');
|
|
173
|
+
wsServer.clients.forEach((ws) => {
|
|
174
|
+
ws.close(1001, 'Server shutting down');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await new Promise((resolve) => {
|
|
178
|
+
wsServer.close(() => {
|
|
179
|
+
console.log('✓ WebSocket server closed');
|
|
180
|
+
resolve();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Shutdown OpenTelemetry and flush traces
|
|
186
|
+
await shutdownTelemetry();
|
|
187
|
+
|
|
188
|
+
clearTimeout(forceShutdownTimer);
|
|
189
|
+
console.log('✓ Graceful shutdown complete');
|
|
190
|
+
process.exit(0);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('Error during shutdown:', error);
|
|
193
|
+
clearTimeout(forceShutdownTimer);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Register shutdown handlers
|
|
199
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
200
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
201
|
+
|
|
202
|
+
// Handle uncaught errors
|
|
203
|
+
process.on('uncaughtException', (error) => {
|
|
204
|
+
console.error('Uncaught exception:', error);
|
|
205
|
+
gracefulShutdown('UNCAUGHT_EXCEPTION');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
209
|
+
console.error('Unhandled rejection at:', promise, 'reason:', reason);
|
|
210
|
+
gracefulShutdown('UNHANDLED_REJECTION');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Start the server
|
|
214
|
+
start().catch((error) => {
|
|
215
|
+
console.error('Failed to start server:', error);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import compression from 'compression';
|
|
2
|
+
import sirv from 'sirv';
|
|
3
|
+
import { config } from 'ENV';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create compression middleware with optimized settings
|
|
7
|
+
*/
|
|
8
|
+
export function createCompressionMiddleware() {
|
|
9
|
+
if (!config.compression) {
|
|
10
|
+
return (req, res, next) => next();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return compression({
|
|
14
|
+
level: config.compressionLevel,
|
|
15
|
+
threshold: 1024, // Only compress responses > 1KB
|
|
16
|
+
memLevel: 8,
|
|
17
|
+
filter: (req, res) => {
|
|
18
|
+
const contentType = res.getHeader('Content-Type');
|
|
19
|
+
|
|
20
|
+
// Don't compress images, videos, or already compressed content
|
|
21
|
+
if (contentType && (
|
|
22
|
+
contentType.includes('image/') ||
|
|
23
|
+
contentType.includes('video/') ||
|
|
24
|
+
contentType.includes('audio/') ||
|
|
25
|
+
contentType.includes('font/')
|
|
26
|
+
)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Use default compression filter for everything else
|
|
31
|
+
return compression.filter(req, res);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create static file serving middleware with optimized settings
|
|
38
|
+
*/
|
|
39
|
+
export function createStaticMiddleware(dir, precompressed = true) {
|
|
40
|
+
return sirv(dir, {
|
|
41
|
+
dev: false,
|
|
42
|
+
etag: true,
|
|
43
|
+
maxAge: 31536000, // 1 year for immutable assets
|
|
44
|
+
immutable: true,
|
|
45
|
+
gzip: precompressed, // Serve .gz files if available
|
|
46
|
+
brotli: precompressed, // Serve .br files if available
|
|
47
|
+
setHeaders: (res, pathname) => {
|
|
48
|
+
// Cache control based on file type
|
|
49
|
+
if (pathname.includes('immutable')) {
|
|
50
|
+
res.setHeader('Cache-Control', 'public, immutable, max-age=31536000');
|
|
51
|
+
} else if (pathname.endsWith('.html')) {
|
|
52
|
+
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
|
|
53
|
+
} else {
|
|
54
|
+
res.setHeader('Cache-Control', 'public, max-age=3600');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Simple body parser for JSON and URL-encoded data with configurable limits
|
|
62
|
+
*/
|
|
63
|
+
export function createBodyParser() {
|
|
64
|
+
const limitBytes = parseLimit(config.bodyLimit);
|
|
65
|
+
|
|
66
|
+
return (req, res, next) => {
|
|
67
|
+
// Skip if not a body method
|
|
68
|
+
if (req.method !== 'POST' && req.method !== 'PUT' && req.method !== 'PATCH') {
|
|
69
|
+
return next();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Skip if body already parsed
|
|
73
|
+
if (req.body) {
|
|
74
|
+
return next();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const contentType = req.headers['content-type'] || '';
|
|
78
|
+
|
|
79
|
+
// Skip if not JSON or form data (SvelteKit will handle FormData/multipart)
|
|
80
|
+
if (!contentType.includes('application/json') &&
|
|
81
|
+
!contentType.includes('application/x-www-form-urlencoded')) {
|
|
82
|
+
return next();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let data = '';
|
|
86
|
+
let size = 0;
|
|
87
|
+
|
|
88
|
+
req.on('data', chunk => {
|
|
89
|
+
size += chunk.length;
|
|
90
|
+
if (size > limitBytes) {
|
|
91
|
+
req.removeAllListeners('data');
|
|
92
|
+
req.removeAllListeners('end');
|
|
93
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
94
|
+
res.end(JSON.stringify({ error: 'Request body too large' }));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
data += chunk.toString();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
req.on('end', () => {
|
|
101
|
+
try {
|
|
102
|
+
if (contentType.includes('application/json')) {
|
|
103
|
+
req.body = data ? JSON.parse(data) : {};
|
|
104
|
+
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
105
|
+
req.body = parseUrlEncoded(data);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
109
|
+
res.end(JSON.stringify({ error: 'Invalid request body' }));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
next();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
req.on('error', (error) => {
|
|
116
|
+
console.error('Body parse error:', error);
|
|
117
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
118
|
+
res.end(JSON.stringify({ error: 'Request error' }));
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parse size limit string to bytes
|
|
125
|
+
*/
|
|
126
|
+
function parseLimit(limit) {
|
|
127
|
+
if (typeof limit === 'number') return limit;
|
|
128
|
+
|
|
129
|
+
const match = limit.match(/^(\d+(?:\.\d+)?)\s*(kb|mb|gb)?$/i);
|
|
130
|
+
if (!match) return 10 * 1024 * 1024; // Default 10MB
|
|
131
|
+
|
|
132
|
+
const value = parseFloat(match[1]);
|
|
133
|
+
const unit = (match[2] || 'b').toLowerCase();
|
|
134
|
+
|
|
135
|
+
const multipliers = {
|
|
136
|
+
b: 1,
|
|
137
|
+
kb: 1024,
|
|
138
|
+
mb: 1024 * 1024,
|
|
139
|
+
gb: 1024 * 1024 * 1024
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return value * (multipliers[unit] || 1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Parse URL-encoded form data
|
|
147
|
+
*/
|
|
148
|
+
function parseUrlEncoded(str) {
|
|
149
|
+
const obj = {};
|
|
150
|
+
const pairs = str.split('&');
|
|
151
|
+
|
|
152
|
+
for (const pair of pairs) {
|
|
153
|
+
const [key, value] = pair.split('=');
|
|
154
|
+
if (key) {
|
|
155
|
+
const decodedKey = decodeURIComponent(key);
|
|
156
|
+
const decodedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '';
|
|
157
|
+
obj[decodedKey] = decodedValue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return obj;
|
|
162
|
+
}
|
package/files/shims.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { config } from 'ENV';
|
|
2
|
+
|
|
3
|
+
let sdk = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize OpenTelemetry SDK for distributed tracing
|
|
7
|
+
*/
|
|
8
|
+
export async function initTelemetry() {
|
|
9
|
+
if (!config.telemetry.enabled) {
|
|
10
|
+
console.log('OpenTelemetry is disabled');
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Dynamic imports to avoid loading if telemetry is disabled
|
|
16
|
+
const { NodeSDK } = await import('@opentelemetry/sdk-node');
|
|
17
|
+
const { getNodeAutoInstrumentations } = await import('@opentelemetry/auto-instrumentations-node');
|
|
18
|
+
const { Resource } = await import('@opentelemetry/resources');
|
|
19
|
+
const { SemanticResourceAttributes } = await import('@opentelemetry/semantic-conventions');
|
|
20
|
+
|
|
21
|
+
// Choose exporter based on protocol
|
|
22
|
+
let TraceExporter;
|
|
23
|
+
if (config.telemetry.protocol === 'grpc') {
|
|
24
|
+
const module = await import('@opentelemetry/exporter-trace-otlp-grpc');
|
|
25
|
+
TraceExporter = module.OTLPTraceExporter;
|
|
26
|
+
} else {
|
|
27
|
+
const module = await import('@opentelemetry/exporter-trace-otlp-http');
|
|
28
|
+
TraceExporter = module.OTLPTraceExporter;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const exporterConfig = {
|
|
32
|
+
url: config.telemetry.endpoint,
|
|
33
|
+
headers: {}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Add Dynatrace API token if provided
|
|
37
|
+
if (config.telemetry.dynatraceToken) {
|
|
38
|
+
exporterConfig.headers['Authorization'] = `Api-Token ${config.telemetry.dynatraceToken}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Merge custom headers from config
|
|
42
|
+
if (config.telemetry.customConfig.headers) {
|
|
43
|
+
Object.assign(exporterConfig.headers, config.telemetry.customConfig.headers);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
sdk = new NodeSDK({
|
|
47
|
+
resource: new Resource({
|
|
48
|
+
[SemanticResourceAttributes.SERVICE_NAME]: config.telemetry.serviceName,
|
|
49
|
+
[SemanticResourceAttributes.SERVICE_VERSION]: config.telemetry.serviceVersion,
|
|
50
|
+
...config.telemetry.customConfig.resourceAttributes
|
|
51
|
+
}),
|
|
52
|
+
traceExporter: new TraceExporter(exporterConfig),
|
|
53
|
+
instrumentations: [
|
|
54
|
+
getNodeAutoInstrumentations({
|
|
55
|
+
// Disable noisy instrumentations
|
|
56
|
+
'@opentelemetry/instrumentation-fs': {
|
|
57
|
+
enabled: false
|
|
58
|
+
},
|
|
59
|
+
'@opentelemetry/instrumentation-http': {
|
|
60
|
+
enabled: true,
|
|
61
|
+
ignoreIncomingPaths: ['/health', '/readiness'],
|
|
62
|
+
ignoreOutgoingUrls: [/\/health$/, /\/readiness$/]
|
|
63
|
+
},
|
|
64
|
+
'@opentelemetry/instrumentation-dns': {
|
|
65
|
+
enabled: false
|
|
66
|
+
},
|
|
67
|
+
// Enable all others by default
|
|
68
|
+
...config.telemetry.customConfig.instrumentations
|
|
69
|
+
})
|
|
70
|
+
],
|
|
71
|
+
// Sampling configuration
|
|
72
|
+
...(config.telemetry.sampleRate < 1.0 && {
|
|
73
|
+
sampler: {
|
|
74
|
+
shouldSample: () => {
|
|
75
|
+
return Math.random() < config.telemetry.sampleRate
|
|
76
|
+
? { decision: 1 } // RECORD_AND_SAMPLED
|
|
77
|
+
: { decision: 0 }; // NOT_RECORD
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await sdk.start();
|
|
84
|
+
console.log('OpenTelemetry SDK initialized successfully');
|
|
85
|
+
console.log(` Service: ${config.telemetry.serviceName}`);
|
|
86
|
+
console.log(` Endpoint: ${config.telemetry.endpoint || 'default'}`);
|
|
87
|
+
console.log(` Protocol: ${config.telemetry.protocol}`);
|
|
88
|
+
console.log(` Sample Rate: ${(config.telemetry.sampleRate * 100).toFixed(1)}%`);
|
|
89
|
+
|
|
90
|
+
return sdk;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('Failed to initialize OpenTelemetry:', error.message);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Shutdown telemetry SDK and flush remaining spans
|
|
99
|
+
*/
|
|
100
|
+
export async function shutdownTelemetry() {
|
|
101
|
+
if (sdk) {
|
|
102
|
+
console.log('Shutting down OpenTelemetry SDK...');
|
|
103
|
+
try {
|
|
104
|
+
await sdk.shutdown();
|
|
105
|
+
console.log('OpenTelemetry SDK shutdown complete');
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('Error shutting down OpenTelemetry:', error.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get tracer for custom instrumentation
|
|
114
|
+
*/
|
|
115
|
+
export function getTracer(name = 'sveltekit-adapter') {
|
|
116
|
+
if (!config.telemetry.enabled) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const { trace } = require('@opentelemetry/api');
|
|
122
|
+
return trace.getTracer(name);
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export interface AdapterOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Output directory for the build
|
|
4
|
+
* @default 'build'
|
|
5
|
+
*/
|
|
6
|
+
out?: string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pre-compress static assets with gzip and brotli
|
|
10
|
+
* @default true
|
|
11
|
+
*/
|
|
12
|
+
precompress?: boolean;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Prefix for environment variables
|
|
16
|
+
* @default ''
|
|
17
|
+
*/
|
|
18
|
+
envPrefix?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Enable runtime compression middleware
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
compression?: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compression level (1-9, where 9 is maximum compression)
|
|
28
|
+
* @default 6
|
|
29
|
+
*/
|
|
30
|
+
compressionLevel?: number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Maximum request body size
|
|
34
|
+
* @default '10mb'
|
|
35
|
+
*/
|
|
36
|
+
bodyLimit?: string | number;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Enable WebSocket support
|
|
40
|
+
* @default true
|
|
41
|
+
*/
|
|
42
|
+
websocket?: boolean;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* WebSocket endpoint path
|
|
46
|
+
* @default '/ws'
|
|
47
|
+
*/
|
|
48
|
+
websocketPath?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Enable OpenTelemetry tracing
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
telemetry?: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Additional OpenTelemetry configuration
|
|
58
|
+
*/
|
|
59
|
+
telemetryConfig?: {
|
|
60
|
+
/**
|
|
61
|
+
* Custom resource attributes
|
|
62
|
+
*/
|
|
63
|
+
resourceAttributes?: Record<string, string>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Custom HTTP headers for exporter
|
|
67
|
+
*/
|
|
68
|
+
headers?: Record<string, string>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Instrumentation-specific configuration
|
|
72
|
+
*/
|
|
73
|
+
instrumentations?: Record<string, any>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Telemetry sampling rate (0.0 to 1.0)
|
|
78
|
+
* @default 1.0
|
|
79
|
+
*/
|
|
80
|
+
telemetrySampleRate?: number;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Enable health check endpoints (/health, /readiness)
|
|
84
|
+
* @default true
|
|
85
|
+
*/
|
|
86
|
+
healthCheck?: boolean;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Graceful shutdown timeout in milliseconds
|
|
90
|
+
* @default 30000
|
|
91
|
+
*/
|
|
92
|
+
gracefulShutdownTimeout?: number;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Inject global polyfills (fetch, Headers, etc.)
|
|
96
|
+
* @default true
|
|
97
|
+
*/
|
|
98
|
+
polyfill?: boolean;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* External packages to exclude from bundle.
|
|
102
|
+
* Can be an array of package names or a function that receives package.json and returns an array.
|
|
103
|
+
* If not specified, uses package.json dependencies by default.
|
|
104
|
+
* @default undefined
|
|
105
|
+
* @example
|
|
106
|
+
* // Array of package names
|
|
107
|
+
* external: ['polka', 'sirv']
|
|
108
|
+
*
|
|
109
|
+
* // Function to dynamically determine externals
|
|
110
|
+
* external: (pkg) => [...Object.keys(pkg.dependencies), 'some-other-package']
|
|
111
|
+
*/
|
|
112
|
+
external?: string[] | ((pkg: any) => string[]);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Bundle all dependencies (ignore package.json dependencies).
|
|
116
|
+
* When true, all code including node_modules will be bundled.
|
|
117
|
+
* @default false
|
|
118
|
+
*/
|
|
119
|
+
bundleAll?: boolean;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Additional rolldown configuration options.
|
|
123
|
+
* These will be merged with the default rolldown config.
|
|
124
|
+
*/
|
|
125
|
+
rolldownOptions?: Record<string, any>;
|