@gjsify/http 0.3.21 → 0.4.3

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/src/index.ts DELETED
@@ -1,149 +0,0 @@
1
- // Node.js http module for GJS
2
- // Server: Soup.Server, Client: Soup.Session
3
- // Reference: Node.js lib/http.js
4
-
5
- export { STATUS_CODES, METHODS } from './constants.js';
6
- export { IncomingMessage } from './incoming-message.js';
7
- export { OutgoingMessage, Server, ServerResponse } from './server.js';
8
- export { ClientRequest } from './client-request.js';
9
- export { validateHeaderName, validateHeaderValue } from './validators.js';
10
- import { validateHeaderName, validateHeaderValue } from './validators.js';
11
- import { IncomingMessage } from './incoming-message.js';
12
- import { OutgoingMessage, Server, ServerResponse } from './server.js';
13
- import { ClientRequest } from './client-request.js';
14
- import type { ClientRequestOptions } from './client-request.js';
15
- import { URL } from 'node:url';
16
-
17
- export interface AgentOptions {
18
- keepAlive?: boolean;
19
- keepAliveMsecs?: number;
20
- maxSockets?: number;
21
- maxTotalSockets?: number;
22
- maxFreeSockets?: number;
23
- timeout?: number;
24
- scheduling?: 'fifo' | 'lifo';
25
- }
26
-
27
- /**
28
- * Agent class for connection pooling.
29
- * Soup.Session handles actual TCP connection pooling internally.
30
- * This class provides the Node.js-compatible API surface for frameworks.
31
- */
32
- export class Agent {
33
- defaultPort = 80;
34
- protocol = 'http:';
35
- maxSockets: number;
36
- maxTotalSockets: number;
37
- maxFreeSockets: number;
38
- keepAliveMsecs: number;
39
- keepAlive: boolean;
40
- scheduling: 'fifo' | 'lifo';
41
-
42
- /** Pending requests per host (compatibility — Soup manages internally). */
43
- readonly requests: Record<string, unknown[]> = {};
44
- /** Active sockets per host (compatibility — Soup manages internally). */
45
- readonly sockets: Record<string, unknown[]> = {};
46
- /** Idle sockets per host (compatibility — Soup manages internally). */
47
- readonly freeSockets: Record<string, unknown[]> = {};
48
-
49
- constructor(options?: AgentOptions) {
50
- this.keepAlive = options?.keepAlive ?? false;
51
- this.keepAliveMsecs = options?.keepAliveMsecs ?? 1000;
52
- this.maxSockets = options?.maxSockets ?? Infinity;
53
- this.maxTotalSockets = options?.maxTotalSockets ?? Infinity;
54
- this.maxFreeSockets = options?.maxFreeSockets ?? 256;
55
- this.scheduling = options?.scheduling ?? 'lifo';
56
- }
57
-
58
- /** Destroy the agent and close idle connections. */
59
- destroy(): void {
60
- // Soup.Session handles cleanup on GC.
61
- }
62
-
63
- /** Return a connection pool key for the given options. */
64
- getName(options: { host?: string; port?: number; localAddress?: string; family?: number }): string {
65
- let name = options.host || 'localhost';
66
- if (options.port) name += ':' + options.port;
67
- if (options.localAddress) name += ':' + options.localAddress;
68
- if (options.family === 4 || options.family === 6) name += ':' + options.family;
69
- return name;
70
- }
71
- }
72
-
73
- export const globalAgent = new Agent();
74
-
75
- /**
76
- * Create an HTTP server.
77
- */
78
- export function createServer(options?: Record<string, unknown> | ((req: IncomingMessage, res: ServerResponse) => void), requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server {
79
- if (typeof options === 'function') {
80
- return new Server(options);
81
- }
82
- return new Server(requestListener);
83
- }
84
-
85
- /**
86
- * Make an HTTP request.
87
- *
88
- * @param url URL string, URL object, or request options
89
- * @param options Request options (if url is string/URL)
90
- * @param callback Response callback
91
- */
92
- export function request(url: string | URL | ClientRequestOptions, options?: ClientRequestOptions | ((res: IncomingMessage) => void), callback?: (res: IncomingMessage) => void): ClientRequest {
93
- return new ClientRequest(url, options, callback);
94
- }
95
-
96
- /**
97
- * Make an HTTP GET request (convenience wrapper that calls req.end() automatically).
98
- */
99
- export function get(url: string | URL | ClientRequestOptions, options?: ClientRequestOptions | ((res: IncomingMessage) => void), callback?: (res: IncomingMessage) => void): ClientRequest {
100
- // Normalize arguments
101
- let opts: ClientRequestOptions;
102
- let cb: ((res: IncomingMessage) => void) | undefined = callback;
103
-
104
- if (typeof url === 'string' || url instanceof URL) {
105
- opts = typeof options === 'object' ? { ...options, method: 'GET' } : { method: 'GET' };
106
- if (typeof options === 'function') cb = options;
107
- } else {
108
- opts = { ...url, method: 'GET' };
109
- if (typeof options === 'function') cb = options;
110
- url = opts as ClientRequestOptions;
111
- }
112
-
113
- const req = typeof url === 'string' || url instanceof URL
114
- ? new ClientRequest(url, { ...opts, method: 'GET' }, cb)
115
- : new ClientRequest({ ...opts, method: 'GET' }, cb);
116
- req.end();
117
- return req;
118
- }
119
-
120
- /** Max header size in bytes. */
121
- export const maxHeaderSize = 16384;
122
-
123
- /**
124
- * Set the maximum number of idle HTTP parsers. Soup.Session handles
125
- * connection pooling internally, so this is a no-op for compatibility.
126
- * @since v18.8.0
127
- */
128
- export function setMaxIdleHTTPParsers(_max: number): void {}
129
-
130
- import { STATUS_CODES as _STATUS_CODES, METHODS as _METHODS } from './constants.js';
131
-
132
- export default {
133
- STATUS_CODES: _STATUS_CODES,
134
- METHODS: _METHODS,
135
- Server,
136
- IncomingMessage,
137
- OutgoingMessage,
138
- ServerResponse,
139
- ClientRequest,
140
- Agent,
141
- globalAgent,
142
- createServer,
143
- request,
144
- get,
145
- validateHeaderName,
146
- validateHeaderValue,
147
- maxHeaderSize,
148
- setMaxIdleHTTPParsers,
149
- };
@@ -1,73 +0,0 @@
1
- // Ported from refs/node-test/parallel/test-net-server-listen-handle.js
2
- // Original: MIT license, Node.js contributors
3
- // Tests that http.Server emits 'error' with code EADDRINUSE when port is busy.
4
-
5
- import { describe, it, expect } from '@gjsify/unit';
6
- import * as http from 'node:http';
7
-
8
- export default async () => {
9
-
10
- await describe('http.Server listen error', async () => {
11
-
12
- await it('should emit error with EADDRINUSE when port is already in use', async () => {
13
- // First server binds a random port
14
- const server1 = http.createServer();
15
- const port = await new Promise<number>((resolve, reject) => {
16
- server1.on('error', reject);
17
- server1.listen(0, () => {
18
- const addr = server1.address() as { port: number };
19
- resolve(addr.port);
20
- });
21
- });
22
-
23
- try {
24
- // Second server tries to bind the same port — must emit 'error'
25
- const error = await new Promise<any>((resolve, reject) => {
26
- const server2 = http.createServer();
27
- server2.on('error', resolve);
28
- server2.on('listening', () => {
29
- server2.close();
30
- reject(new Error('Expected EADDRINUSE but server started successfully'));
31
- });
32
- server2.listen(port);
33
- });
34
-
35
- expect(error.code).toBe('EADDRINUSE');
36
- expect(error.syscall).toBe('listen');
37
- expect(typeof error.port).toBe('number');
38
- expect(error.port).toBe(port);
39
- } finally {
40
- server1.close();
41
- }
42
- });
43
-
44
- await it('should include address and port in error message', async () => {
45
- const server1 = http.createServer();
46
- const port = await new Promise<number>((resolve, reject) => {
47
- server1.on('error', reject);
48
- server1.listen(0, () => {
49
- const addr = server1.address() as { port: number };
50
- resolve(addr.port);
51
- });
52
- });
53
-
54
- try {
55
- const error = await new Promise<any>((resolve, reject) => {
56
- const server2 = http.createServer();
57
- server2.on('error', resolve);
58
- server2.on('listening', () => {
59
- server2.close();
60
- reject(new Error('Expected EADDRINUSE'));
61
- });
62
- server2.listen(port);
63
- });
64
-
65
- expect(error.message).toContain('EADDRINUSE');
66
- expect(error.message).toContain('listen');
67
- } finally {
68
- server1.close();
69
- }
70
- });
71
-
72
- });
73
- };
@@ -1,88 +0,0 @@
1
- // `req.socket` for our HTTP server.
2
- //
3
- // Reference: Node.js lib/net.js Socket interface
4
- // Reimplemented for GJS — `@gjsify/http-soup-bridge` owns the underlying
5
- // TCP connection, so a real `Gio.Socket` is not directly accessible from
6
- // JS. This class satisfies the net.Socket duck-type expected by HTTP
7
- // consumers (Hono, MCP SDK, engine.io, …) using values copied off the
8
- // bridge `Request` instance at construction time.
9
- //
10
- // Extends Duplex (not EventEmitter) so that `instanceof stream.Duplex`
11
- // checks and stream API calls (`pipe`, `pause`, `resume`) work. `_read`
12
- // and `_write` are no-ops because the bridge owns the actual bytes.
13
-
14
- import { Duplex } from 'node:stream';
15
- import type { Response as BridgeResponse } from '@gjsify/http-soup-bridge';
16
-
17
- export class ServerRequestSocket extends Duplex {
18
- readonly remoteAddress: string;
19
- readonly remotePort: number;
20
- readonly localAddress: string;
21
- readonly localPort: number;
22
- readonly remoteFamily = 'IPv4';
23
- readonly encrypted: boolean;
24
- readonly connecting = false;
25
- readonly pending = false;
26
- bytesRead = 0;
27
- bytesWritten = 0;
28
-
29
- // Bridge response we forward pause/resume to (via super.pause/resume
30
- // for now; the bridge will grow explicit pause/unpause hooks later).
31
- private readonly _bridgeRes: BridgeResponse;
32
- private _bridgePaused = false;
33
-
34
- constructor(
35
- remoteAddress: string,
36
- remotePort: number,
37
- localAddress: string,
38
- localPort: number,
39
- bridgeRes: BridgeResponse,
40
- encrypted = false,
41
- ) {
42
- super({ allowHalfOpen: true });
43
- this.remoteAddress = remoteAddress;
44
- this.remotePort = remotePort;
45
- this.localAddress = localAddress;
46
- this.localPort = localPort;
47
- this.encrypted = encrypted;
48
- this._bridgeRes = bridgeRes;
49
- }
50
-
51
- pause(): this {
52
- if (this._bridgePaused) return this;
53
- this._bridgePaused = true;
54
- return super.pause() as this;
55
- }
56
-
57
- resume(): this {
58
- if (!this._bridgePaused) return super.resume() as this;
59
- this._bridgePaused = false;
60
- return super.resume() as this;
61
- }
62
-
63
- // The bridge owns the TCP connection — no data flows through this Duplex.
64
- _read(_size: number): void {}
65
- _write(_chunk: unknown, _encoding: BufferEncoding, cb: (err?: Error | null) => void): void { cb(); }
66
-
67
- destroySoon(): void {
68
- if (!this.writableEnded) this.end();
69
- if (this.writableFinished)
70
- this.destroy();
71
- else
72
- this.once('finish', () => this.destroy());
73
- }
74
-
75
- setTimeout(_timeout: number, cb?: () => void): this {
76
- if (cb) this.once('timeout', cb);
77
- return this;
78
- }
79
-
80
- setNoDelay(_noDelay?: boolean): this { return this; }
81
- setKeepAlive(_enable?: boolean, _delay?: number): this { return this; }
82
- ref(): this { return this; }
83
- unref(): this { return this; }
84
-
85
- address(): { address: string; family: string; port: number } {
86
- return { address: this.localAddress, family: 'IPv4', port: this.localPort };
87
- }
88
- }