console-bridge-sveltekit 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chintan Thakkar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # console-bridge-sveltekit
2
+
3
+ Forward frontend console logs to your SvelteKit backend for easier debugging. Perfect for AI agents and developers who want to see all logs in one place.
4
+
5
+ ## Features
6
+
7
+ - ✅ Forward frontend console logs to backend
8
+ - ✅ See frontend + backend logs in single terminal
9
+ - ✅ Dev-only (zero production overhead)
10
+ - ✅ Batched requests for efficiency
11
+ - ✅ Recursive-safe
12
+ - ✅ Configurable log levels
13
+ - ✅ TypeScript support
14
+ - ✅ Stack trace capture for errors
15
+ - ✅ Capture fetch + XHR network calls
16
+ - ✅ Capture global errors/unhandled rejections
17
+ - ✅ Optional response body capture w limits
18
+ - ✅ URL include/ignore filters
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install console-bridge-sveltekit
24
+ # or
25
+ pnpm add console-bridge-sveltekit
26
+ # or
27
+ yarn add console-bridge-sveltekit
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### 1. Create API Endpoint
33
+
34
+ Create `src/routes/api/console-bridge/+server.ts`:
35
+
36
+ ```typescript
37
+ import { createConsoleBridgeEndpoint } from 'console-bridge-sveltekit/server';
38
+
39
+ export const POST = createConsoleBridgeEndpoint();
40
+ ```
41
+
42
+ ### 2. Initialize in Layout
43
+
44
+ In `src/routes/+layout.svelte`:
45
+
46
+ ```svelte
47
+ <script>
48
+ import { onMount } from 'svelte';
49
+ import { initConsolebridge } from 'console-bridge-sveltekit/client';
50
+
51
+ onMount(() => {
52
+ initConsolebridge();
53
+ });
54
+ </script>
55
+ ```
56
+
57
+ That's it! Now all frontend console logs will appear in your server terminal.
58
+
59
+ ## Configuration
60
+
61
+ ### Client Options
62
+
63
+ ```typescript
64
+ initConsolebridge({
65
+ endpoint: '/api/console-bridge', // API endpoint
66
+ batchSize: 10, // Logs per batch
67
+ batchDelay: 100, // Batch delay (ms)
68
+ levels: ['error', 'warn'], // Only forward errors and warnings
69
+ captureNetwork: true, // Capture fetch/XHR
70
+ captureErrors: true, // Capture global errors
71
+ networkBodyLimit: 500, // Truncate response body length
72
+ networkInclude: [], // Only these URLs
73
+ networkIgnore: [] // Skip these URLs
74
+ });
75
+ ```
76
+
77
+ ### Server Options
78
+
79
+ ```typescript
80
+ export const POST = createConsoleBridgeEndpoint({
81
+ prefix: '[FRONTEND', // Log prefix
82
+ formatter: (level, url, timestamp, args) => {
83
+ return `[CUSTOM] ${level} from ${url}`;
84
+ },
85
+ onLog: (level, url, timestamp, args) => {
86
+ // Custom handler (e.g., send to external service)
87
+ if (level === 'error') {
88
+ sendToSentry(args);
89
+ }
90
+ }
91
+ });
92
+ ```
93
+
94
+ ## Example Output
95
+
96
+ **Browser console:**
97
+ ```
98
+ Hello from frontend
99
+ ```
100
+
101
+ **Server terminal:**
102
+ ```
103
+ [FRONTEND LOG] http://localhost:5173/app/dashboard @ 2024-01-11T10:30:00.000Z Hello from frontend
104
+ ```
105
+
106
+ ## API
107
+
108
+ ### Client
109
+
110
+ #### `initConsolebridge(options?)`
111
+
112
+ Initialize console bridge in browser.
113
+
114
+ **Options:**
115
+ - `endpoint?: string` - Backend endpoint (default: `/api/console-bridge`)
116
+ - `batchSize?: number` - Logs per batch (default: 10)
117
+ - `batchDelay?: number` - Batch delay in ms (default: 100)
118
+ - `levels?: LogLevel[]` - Log levels to forward (default: all)
119
+ - `captureNetwork?: boolean` - Forward fetch/XHR calls (default: true)
120
+ - `captureErrors?: boolean` - Forward global errors/rejections (default: true)
121
+ - `networkBodyLimit?: number` - Max body chars, 0 disables (default: 500)
122
+ - `networkInclude?: (string | RegExp)[]` - Include-only URL patterns (default: [])
123
+ - `networkIgnore?: (string | RegExp)[]` - Ignore URL patterns (default: [])
124
+
125
+ #### `restoreConsole()`
126
+
127
+ Restore original console methods.
128
+
129
+ ### Server
130
+
131
+ #### `createConsoleBridgeEndpoint(options?)`
132
+
133
+ Create SvelteKit RequestHandler for console bridge.
134
+
135
+ **Options:**
136
+ - `prefix?: string` - Log prefix (default: `[FRONTEND`)
137
+ - `formatter?: (level, url, timestamp, args) => string` - Custom formatter
138
+ - `onLog?: (level, url, timestamp, args) => void` - Custom log handler
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ # Build
144
+ pnpm build
145
+
146
+ # Publish
147
+ npm publish
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,3 @@
1
+ export { initConsolebridge, restoreConsole, type ConsoleBridgeOptions } from './lib/client.js';
2
+ export { createConsoleBridgeEndpoint, type ConsoleBridgeServerOptions } from './lib/server.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACN,iBAAiB,EACjB,cAAc,EACd,KAAK,oBAAoB,EACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,2BAA2B,EAC3B,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // Re-export everything for convenience
2
+ export { initConsolebridge, restoreConsole } from './lib/client.js';
3
+ export { createConsoleBridgeEndpoint } from './lib/server.js';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Client-side console bridge for SvelteKit
3
+ * Intercepts console methods and forwards logs to backend
4
+ */
5
+ export interface ConsoleBridgeOptions {
6
+ endpoint?: string;
7
+ batchSize?: number;
8
+ batchDelay?: number;
9
+ levels?: LogLevel[];
10
+ captureNetwork?: boolean;
11
+ captureErrors?: boolean;
12
+ networkBodyLimit?: number;
13
+ networkIgnore?: (string | RegExp)[];
14
+ networkInclude?: (string | RegExp)[];
15
+ }
16
+ type LogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug';
17
+ /**
18
+ * Initialize console bridge
19
+ * Call this in your root +layout.svelte onMount()
20
+ */
21
+ export declare function initConsolebridge(userOptions?: ConsoleBridgeOptions): void;
22
+ /**
23
+ * Restore original console methods
24
+ */
25
+ export declare function restoreConsole(): void;
26
+ export {};
27
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CACrC;AAED,KAAK,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAmW5D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,GAAE,oBAAyB,QA4BvE;AAED;;GAEG;AACH,wBAAgB,cAAc,SAU7B"}
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Client-side console bridge for SvelteKit
3
+ * Intercepts console methods and forwards logs to backend
4
+ */
5
+ import { browser, dev } from '$app/environment';
6
+ const DEFAULT_OPTIONS = {
7
+ endpoint: '/api/console-bridge',
8
+ batchSize: 10,
9
+ batchDelay: 100,
10
+ levels: ['log', 'warn', 'error', 'info', 'debug'],
11
+ captureNetwork: true,
12
+ captureErrors: true,
13
+ networkBodyLimit: 500,
14
+ networkIgnore: [],
15
+ networkInclude: []
16
+ };
17
+ let isSending = false;
18
+ let logQueue = [];
19
+ let batchTimer = null;
20
+ let options = null;
21
+ let isInitialized = false;
22
+ let networkPatched = false;
23
+ let errorListenersAttached = false;
24
+ let originalFetch = null;
25
+ let originalXhrOpen = null;
26
+ let originalXhrSend = null;
27
+ let errorHandler = null;
28
+ let rejectionHandler = null;
29
+ const xhrMeta = new WeakMap();
30
+ // Store original console methods
31
+ const originalConsole = {
32
+ log: console.log,
33
+ warn: console.warn,
34
+ error: console.error,
35
+ info: console.info,
36
+ debug: console.debug
37
+ };
38
+ function resolveUrl(input) {
39
+ try {
40
+ return new URL(input, window.location.href).toString();
41
+ }
42
+ catch {
43
+ return input;
44
+ }
45
+ }
46
+ function matchesPattern(value, pattern) {
47
+ if (pattern instanceof RegExp)
48
+ return pattern.test(value);
49
+ return value.includes(pattern);
50
+ }
51
+ function isNetworkTracked(targetUrl) {
52
+ if (!options)
53
+ return false;
54
+ const resolved = resolveUrl(targetUrl);
55
+ if (resolved === resolveUrl(options.endpoint))
56
+ return false;
57
+ if (options.networkIgnore.some((pattern) => matchesPattern(resolved, pattern))) {
58
+ return false;
59
+ }
60
+ if (options.networkInclude.length > 0) {
61
+ return options.networkInclude.some((pattern) => matchesPattern(resolved, pattern));
62
+ }
63
+ return true;
64
+ }
65
+ function queueEntry(entry) {
66
+ if (!dev || !browser || !options)
67
+ return;
68
+ if (isSending)
69
+ return;
70
+ logQueue.push(entry);
71
+ scheduleBatch();
72
+ }
73
+ function sendBatch() {
74
+ if (!options || isSending || logQueue.length === 0)
75
+ return;
76
+ isSending = true;
77
+ const batch = logQueue.splice(0, options.batchSize);
78
+ const sender = originalFetch ?? fetch;
79
+ sender(options.endpoint, {
80
+ method: 'POST',
81
+ headers: { 'Content-Type': 'application/json' },
82
+ body: JSON.stringify(batch.length === 1 ? batch[0] : { batch })
83
+ })
84
+ .catch((err) => {
85
+ originalConsole.error('[Console Bridge] Failed to send logs:', err);
86
+ })
87
+ .finally(() => {
88
+ isSending = false;
89
+ if (logQueue.length > 0) {
90
+ scheduleBatch();
91
+ }
92
+ });
93
+ }
94
+ function scheduleBatch() {
95
+ if (!options || batchTimer)
96
+ return;
97
+ batchTimer = setTimeout(() => {
98
+ batchTimer = null;
99
+ sendBatch();
100
+ }, options.batchDelay);
101
+ }
102
+ function createInterceptor(level) {
103
+ return function (...args) {
104
+ originalConsole[level](...args);
105
+ if (dev && browser && options?.levels.includes(level)) {
106
+ const entry = {
107
+ kind: 'console',
108
+ level,
109
+ args,
110
+ timestamp: new Date().toISOString(),
111
+ url: window.location.href
112
+ };
113
+ // Capture stack trace for errors
114
+ if (level === 'error' && args[0] instanceof Error) {
115
+ const error = args[0];
116
+ if (error.stack) {
117
+ // Truncate to prevent huge payloads
118
+ entry.stack = error.stack.slice(0, 1000);
119
+ }
120
+ }
121
+ queueEntry(entry);
122
+ }
123
+ };
124
+ }
125
+ function getFetchDetails(args) {
126
+ const [input, init] = args;
127
+ if (input instanceof Request) {
128
+ return { method: input.method ?? 'GET', url: input.url };
129
+ }
130
+ const url = typeof input === 'string' || input instanceof URL ? input.toString() : String(input);
131
+ return { method: init?.method ?? 'GET', url };
132
+ }
133
+ async function readResponseBody(response) {
134
+ if (!options || options.networkBodyLimit <= 0)
135
+ return undefined;
136
+ try {
137
+ const clone = response.clone();
138
+ const text = await clone.text();
139
+ return text.slice(0, options.networkBodyLimit);
140
+ }
141
+ catch {
142
+ return undefined;
143
+ }
144
+ }
145
+ function readXhrBody(xhr) {
146
+ if (!options || options.networkBodyLimit <= 0)
147
+ return undefined;
148
+ try {
149
+ if (xhr.responseType && xhr.responseType !== 'text')
150
+ return undefined;
151
+ const text = typeof xhr.responseText === 'string' ? xhr.responseText : '';
152
+ return text.slice(0, options.networkBodyLimit);
153
+ }
154
+ catch {
155
+ return undefined;
156
+ }
157
+ }
158
+ function patchFetch() {
159
+ if (!('fetch' in window))
160
+ return;
161
+ if (!originalFetch)
162
+ originalFetch = window.fetch.bind(window);
163
+ window.fetch = (async (...args) => {
164
+ const [input, init] = args;
165
+ const { method, url } = getFetchDetails(args);
166
+ const resolvedUrl = resolveUrl(url);
167
+ if (!isNetworkTracked(resolvedUrl)) {
168
+ return originalFetch.call(window, input, init);
169
+ }
170
+ const start = performance.now();
171
+ try {
172
+ const response = await originalFetch.call(window, input, init);
173
+ const duration = Math.round(performance.now() - start);
174
+ const responseBody = await readResponseBody(response);
175
+ queueEntry({
176
+ kind: 'network',
177
+ level: 'network',
178
+ args: [`${method} ${resolvedUrl}`, `status: ${response.status}`, `duration: ${duration}ms`],
179
+ timestamp: new Date().toISOString(),
180
+ url: resolvedUrl,
181
+ method,
182
+ status: response.status,
183
+ duration,
184
+ requestType: 'fetch',
185
+ pageUrl: window.location.href,
186
+ responseBody
187
+ });
188
+ return response;
189
+ }
190
+ catch (err) {
191
+ const duration = Math.round(performance.now() - start);
192
+ const message = err instanceof Error ? err.message : String(err);
193
+ const stack = err instanceof Error && err.stack ? err.stack.slice(0, 1000) : undefined;
194
+ queueEntry({
195
+ kind: 'network',
196
+ level: 'network',
197
+ args: [`${method} ${resolvedUrl}`, `error: ${message}`, `duration: ${duration}ms`],
198
+ timestamp: new Date().toISOString(),
199
+ url: resolvedUrl,
200
+ method,
201
+ status: 0,
202
+ duration,
203
+ requestType: 'fetch',
204
+ pageUrl: window.location.href,
205
+ stack
206
+ });
207
+ throw err;
208
+ }
209
+ });
210
+ }
211
+ function patchXhr() {
212
+ if (!('XMLHttpRequest' in window))
213
+ return;
214
+ if (!originalXhrOpen)
215
+ originalXhrOpen = XMLHttpRequest.prototype.open;
216
+ if (!originalXhrSend)
217
+ originalXhrSend = XMLHttpRequest.prototype.send;
218
+ XMLHttpRequest.prototype.open = function (method, url, ...rest) {
219
+ xhrMeta.set(this, { method, url, start: 0 });
220
+ return originalXhrOpen.apply(this, [method, url, ...rest]);
221
+ };
222
+ XMLHttpRequest.prototype.send = function (...args) {
223
+ const meta = xhrMeta.get(this);
224
+ if (meta)
225
+ meta.start = performance.now();
226
+ const resolvedUrl = meta ? resolveUrl(meta.url) : '';
227
+ const onDone = async () => {
228
+ this.removeEventListener('loadend', onDone);
229
+ if (!meta || !resolvedUrl || !isNetworkTracked(resolvedUrl))
230
+ return;
231
+ const duration = Math.round(performance.now() - meta.start);
232
+ const status = this.status || 0;
233
+ const responseBody = await readXhrBody(this);
234
+ queueEntry({
235
+ kind: 'network',
236
+ level: 'network',
237
+ args: [`${meta.method} ${resolvedUrl}`, `status: ${status}`, `duration: ${duration}ms`],
238
+ timestamp: new Date().toISOString(),
239
+ url: resolvedUrl,
240
+ method: meta.method,
241
+ status,
242
+ duration,
243
+ requestType: 'xhr',
244
+ pageUrl: window.location.href,
245
+ responseBody
246
+ });
247
+ };
248
+ this.addEventListener('loadend', onDone);
249
+ const body = args[0];
250
+ return originalXhrSend.call(this, body);
251
+ };
252
+ }
253
+ function restoreNetwork() {
254
+ if (!networkPatched)
255
+ return;
256
+ if (originalFetch)
257
+ window.fetch = originalFetch;
258
+ if (originalXhrOpen)
259
+ XMLHttpRequest.prototype.open = originalXhrOpen;
260
+ if (originalXhrSend)
261
+ XMLHttpRequest.prototype.send = originalXhrSend;
262
+ networkPatched = false;
263
+ }
264
+ function patchNetwork() {
265
+ if (networkPatched)
266
+ return;
267
+ networkPatched = true;
268
+ patchFetch();
269
+ patchXhr();
270
+ }
271
+ function attachErrorListeners() {
272
+ if (errorListenersAttached)
273
+ return;
274
+ errorListenersAttached = true;
275
+ errorHandler = (event) => {
276
+ const stack = event.error?.stack ? event.error.stack.slice(0, 1000) : undefined;
277
+ queueEntry({
278
+ kind: 'error',
279
+ level: 'error',
280
+ args: [event.message],
281
+ timestamp: new Date().toISOString(),
282
+ url: event.filename ?? window.location.href,
283
+ stack,
284
+ pageUrl: window.location.href
285
+ });
286
+ };
287
+ rejectionHandler = (event) => {
288
+ const reason = event.reason;
289
+ const message = reason instanceof Error ? reason.message : String(reason);
290
+ const stack = reason instanceof Error && reason.stack ? reason.stack.slice(0, 1000) : undefined;
291
+ queueEntry({
292
+ kind: 'error',
293
+ level: 'error',
294
+ args: [message],
295
+ timestamp: new Date().toISOString(),
296
+ url: window.location.href,
297
+ stack,
298
+ pageUrl: window.location.href
299
+ });
300
+ };
301
+ window.addEventListener('error', errorHandler);
302
+ window.addEventListener('unhandledrejection', rejectionHandler);
303
+ }
304
+ function detachErrorListeners() {
305
+ if (!errorListenersAttached)
306
+ return;
307
+ errorListenersAttached = false;
308
+ if (errorHandler)
309
+ window.removeEventListener('error', errorHandler);
310
+ if (rejectionHandler)
311
+ window.removeEventListener('unhandledrejection', rejectionHandler);
312
+ errorHandler = null;
313
+ rejectionHandler = null;
314
+ }
315
+ /**
316
+ * Initialize console bridge
317
+ * Call this in your root +layout.svelte onMount()
318
+ */
319
+ export function initConsolebridge(userOptions = {}) {
320
+ if (!browser || !dev)
321
+ return;
322
+ options = { ...DEFAULT_OPTIONS, ...userOptions };
323
+ // Intercept only specified levels
324
+ if (options.levels.includes('log'))
325
+ console.log = createInterceptor('log');
326
+ if (options.levels.includes('warn'))
327
+ console.warn = createInterceptor('warn');
328
+ if (options.levels.includes('error'))
329
+ console.error = createInterceptor('error');
330
+ if (options.levels.includes('info'))
331
+ console.info = createInterceptor('info');
332
+ if (options.levels.includes('debug'))
333
+ console.debug = createInterceptor('debug');
334
+ if (options.captureNetwork) {
335
+ patchNetwork();
336
+ }
337
+ else {
338
+ restoreNetwork();
339
+ }
340
+ if (options.captureErrors) {
341
+ attachErrorListeners();
342
+ }
343
+ else {
344
+ detachErrorListeners();
345
+ }
346
+ if (!isInitialized) {
347
+ isInitialized = true;
348
+ originalConsole.info(`[Console Bridge] Active - logs forwarded to ${options.endpoint}`);
349
+ }
350
+ }
351
+ /**
352
+ * Restore original console methods
353
+ */
354
+ export function restoreConsole() {
355
+ console.log = originalConsole.log;
356
+ console.warn = originalConsole.warn;
357
+ console.error = originalConsole.error;
358
+ console.info = originalConsole.info;
359
+ console.debug = originalConsole.debug;
360
+ restoreNetwork();
361
+ detachErrorListeners();
362
+ options = null;
363
+ isInitialized = false;
364
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Server-side console bridge endpoint factory
3
+ */
4
+ import type { RequestHandler } from '@sveltejs/kit';
5
+ export interface ConsoleBridgeServerOptions {
6
+ prefix?: string;
7
+ formatter?: (level: string, url: string, timestamp: string, args: any[]) => string;
8
+ onLog?: (level: string, url: string, timestamp: string, args: any[]) => void;
9
+ }
10
+ /**
11
+ * Create a SvelteKit RequestHandler for console bridge endpoint
12
+ * Use in your +server.ts: export const POST = createConsoleBridgeEndpoint();
13
+ */
14
+ export declare function createConsoleBridgeEndpoint(userOptions?: ConsoleBridgeServerOptions): RequestHandler;
15
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/lib/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,MAAM,WAAW,0BAA0B;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC;IACnF,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CAC7E;AASD;;;GAGG;AACH,wBAAgB,2BAA2B,CAC1C,WAAW,GAAE,0BAA+B,GAC1C,cAAc,CAuDhB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Server-side console bridge endpoint factory
3
+ */
4
+ import { json, error } from '@sveltejs/kit';
5
+ const DEFAULT_OPTIONS = {
6
+ prefix: '[FRONTEND',
7
+ formatter: (level, url, timestamp) => `[FRONTEND ${level.toUpperCase()}] ${url} @ ${timestamp}`,
8
+ onLog: () => { }
9
+ };
10
+ /**
11
+ * Create a SvelteKit RequestHandler for console bridge endpoint
12
+ * Use in your +server.ts: export const POST = createConsoleBridgeEndpoint();
13
+ */
14
+ export function createConsoleBridgeEndpoint(userOptions = {}) {
15
+ const options = { ...DEFAULT_OPTIONS, ...userOptions };
16
+ return async ({ request }) => {
17
+ // Only allow in dev mode
18
+ if (import.meta.env.PROD) {
19
+ throw error(404);
20
+ }
21
+ try {
22
+ const body = await request.json();
23
+ // Handle both single log and batch
24
+ const logs = Array.isArray(body.batch) ? body.batch : [body];
25
+ for (const log of logs) {
26
+ const { level, args, timestamp, url, stack } = log;
27
+ // Custom formatter or default
28
+ const prefix = options.formatter(level, url, timestamp, args);
29
+ // Prepare args for logging (include stack if present)
30
+ const logArgs = stack ? [...args, `\nStack: ${stack}`] : args;
31
+ // Log to console
32
+ switch (level) {
33
+ case 'error':
34
+ console.error(prefix, ...logArgs);
35
+ break;
36
+ case 'warn':
37
+ console.warn(prefix, ...logArgs);
38
+ break;
39
+ case 'info':
40
+ console.info(prefix, ...logArgs);
41
+ break;
42
+ case 'debug':
43
+ console.debug(prefix, ...logArgs);
44
+ break;
45
+ case 'network':
46
+ console.info(prefix, ...logArgs);
47
+ break;
48
+ default:
49
+ console.log(prefix, ...logArgs);
50
+ }
51
+ // Custom callback
52
+ options.onLog(level, url, timestamp, args);
53
+ }
54
+ return json({ success: true });
55
+ }
56
+ catch (err) {
57
+ console.error('[Console Bridge] Failed to process logs:', err);
58
+ return json({ success: false }, { status: 400 });
59
+ }
60
+ };
61
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "console-bridge-sveltekit",
3
+ "version": "1.0.0",
4
+ "description": "Forward frontend console logs to backend for easier debugging in SvelteKit",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./client": {
12
+ "types": "./dist/lib/client.d.ts",
13
+ "import": "./dist/lib/client.js"
14
+ },
15
+ "./server": {
16
+ "types": "./dist/lib/server.d.ts",
17
+ "import": "./dist/lib/server.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "prepublishOnly": "pnpm build"
28
+ },
29
+ "keywords": [
30
+ "sveltekit",
31
+ "console",
32
+ "logging",
33
+ "debugging",
34
+ "development"
35
+ ],
36
+ "author": "Chintan Thakkar",
37
+ "license": "MIT",
38
+ "peerDependencies": {
39
+ "@sveltejs/kit": "^2.0.0",
40
+ "svelte": "^5.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@sveltejs/kit": "^2.0.0",
44
+ "svelte": "^5.0.0",
45
+ "typescript": "^5.0.0"
46
+ }
47
+ }