@voltts/serializer 0.0.0-stage → 0.1.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/package.json CHANGED
@@ -1,6 +1,17 @@
1
1
  {
2
2
  "name": "@voltts/serializer",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Ultra-fast response serialization and content-type handling for Volt",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./src/index.ts",
11
+ "types": "./src/index.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "test": "bun test"
16
+ }
17
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './serializer';
@@ -0,0 +1,122 @@
1
+ import { ResponseInitOptions } from './types';
2
+
3
+ const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
4
+ const TEXT_CONTENT_TYPE = 'text/plain; charset=utf-8';
5
+ const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
6
+ const OCTET_CONTENT_TYPE = 'application/octet-stream';
7
+
8
+ export function json(data: unknown, init?: number | ResponseInitOptions): Response {
9
+ if ((init === undefined || init === 200) && typeof Response.json === 'function') {
10
+ return Response.json(data);
11
+ }
12
+
13
+ const options = typeof init === 'number' ? { status: init } : init || {};
14
+ const status = options.status ?? 200;
15
+
16
+ // Use Bun's highly optimized Response.json when available
17
+ if (typeof Response.json === 'function') {
18
+ return Response.json(data, {
19
+ ...options,
20
+ status,
21
+ headers: {
22
+ 'content-type': JSON_CONTENT_TYPE,
23
+ ...options.headers,
24
+ },
25
+ });
26
+ }
27
+
28
+ return new Response(JSON.stringify(data), {
29
+ ...options,
30
+ status,
31
+ headers: {
32
+ 'content-type': JSON_CONTENT_TYPE,
33
+ ...options.headers,
34
+ },
35
+ });
36
+ }
37
+
38
+ export function text(text: string, init?: number | ResponseInitOptions): Response {
39
+ const options = typeof init === 'number' ? { status: init } : init || {};
40
+ return new Response(text, {
41
+ ...options,
42
+ status: options.status ?? 200,
43
+ headers: {
44
+ 'content-type': TEXT_CONTENT_TYPE,
45
+ ...options.headers,
46
+ },
47
+ });
48
+ }
49
+
50
+ export function html(html: string, init?: number | ResponseInitOptions): Response {
51
+ const options = typeof init === 'number' ? { status: init } : init || {};
52
+ return new Response(html, {
53
+ ...options,
54
+ status: options.status ?? 200,
55
+ headers: {
56
+ 'content-type': HTML_CONTENT_TYPE,
57
+ ...options.headers,
58
+ },
59
+ });
60
+ }
61
+
62
+ export function binary(buffer: BodyInit, contentType = OCTET_CONTENT_TYPE, init?: number | ResponseInitOptions): Response {
63
+ const options = typeof init === 'number' ? { status: init } : init || {};
64
+ return new Response(buffer, {
65
+ ...options,
66
+ status: options.status ?? 200,
67
+ headers: {
68
+ 'content-type': contentType,
69
+ ...options.headers,
70
+ },
71
+ });
72
+ }
73
+
74
+ export function stream(readable: ReadableStream, contentType = OCTET_CONTENT_TYPE, init?: number | ResponseInitOptions): Response {
75
+ const options = typeof init === 'number' ? { status: init } : init || {};
76
+ return new Response(readable, {
77
+ ...options,
78
+ status: options.status ?? 200,
79
+ headers: {
80
+ 'content-type': contentType,
81
+ ...options.headers,
82
+ },
83
+ });
84
+ }
85
+
86
+ export function serializeResponse(value: unknown, defaultStatus = 200): Response {
87
+ if (value instanceof Response) {
88
+ return value;
89
+ }
90
+
91
+ if (typeof value === 'object') {
92
+ if (value === null) {
93
+ return new Response(null, { status: 204 });
94
+ }
95
+
96
+ // Fast-path: 99% of API responses are plain objects/arrays with 200 status
97
+ if (defaultStatus === 200 && typeof Response.json === 'function' && ((value as any).constructor === Object || Array.isArray(value))) {
98
+ return Response.json(value);
99
+ }
100
+
101
+ if (value instanceof Blob || value instanceof ArrayBuffer || value instanceof Uint8Array) {
102
+ return binary(value as BodyInit, OCTET_CONTENT_TYPE, defaultStatus);
103
+ }
104
+
105
+ if (value instanceof ReadableStream) {
106
+ return stream(value, OCTET_CONTENT_TYPE, defaultStatus);
107
+ }
108
+
109
+ return json(value, defaultStatus);
110
+ }
111
+
112
+ if (typeof value === 'string') {
113
+ return text(value, defaultStatus);
114
+ }
115
+
116
+ if (value === undefined) {
117
+ return new Response(null, { status: 204 });
118
+ }
119
+
120
+ return text(String(value), defaultStatus);
121
+ }
122
+
package/src/types.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type ResponseInitOptions = {
2
+ status?: number;
3
+ statusText?: string;
4
+ headers?: HeadersInit;
5
+ };
6
+
7
+ export type Serializer<T = any> = (data: T) => string;
8
+
9
+ export interface SerializerOptions {
10
+ detectContentType?: boolean;
11
+ }
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { html, json, serializeResponse, text } from '../src/index';
3
+
4
+ describe('@voltts/serializer', () => {
5
+ it('serializes JSON responses with correct headers', async () => {
6
+ const res = json({ hello: 'volt' }, 201);
7
+ expect(res.status).toBe(201);
8
+ expect(res.headers.get('content-type')).toContain('application/json');
9
+ const data = await res.json();
10
+ expect(data).toEqual({ hello: 'volt' });
11
+ });
12
+
13
+ it('serializes text responses', async () => {
14
+ const res = text('Plain text response', 200);
15
+ expect(res.status).toBe(200);
16
+ expect(res.headers.get('content-type')).toContain('text/plain');
17
+ expect(await res.text()).toBe('Plain text response');
18
+ });
19
+
20
+ it('serializes HTML responses', async () => {
21
+ const res = html('<h1>Volt</h1>', 200);
22
+ expect(res.status).toBe(200);
23
+ expect(res.headers.get('content-type')).toContain('text/html');
24
+ expect(await res.text()).toBe('<h1>Volt</h1>');
25
+ });
26
+
27
+ it('automatically detects and serializes arbitrary handler return values', async () => {
28
+ // 1. Primitive string
29
+ const resStr = serializeResponse('hello');
30
+ expect(resStr.headers.get('content-type')).toContain('text/plain');
31
+ expect(await resStr.text()).toBe('hello');
32
+
33
+ // 2. Object
34
+ const resObj = serializeResponse({ a: 1 });
35
+ expect(resObj.headers.get('content-type')).toContain('application/json');
36
+ expect(await resObj.json()).toEqual({ a: 1 });
37
+
38
+ // 3. Null / Undefined -> 204 No Content
39
+ const resNull = serializeResponse(null);
40
+ expect(resNull.status).toBe(204);
41
+
42
+ // 4. Pass-through Response
43
+ const raw = new Response('raw', { status: 418 });
44
+ const resRaw = serializeResponse(raw);
45
+ expect(resRaw.status).toBe(418);
46
+ });
47
+ });
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.