@ricsam/quickjs-fetch 0.0.1 → 0.2.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/README.md +109 -43
- package/dist/cjs/index.cjs +47 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/mjs/index.mjs +22 -0
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/types/globals/abort-controller.d.ts +9 -0
- package/dist/types/globals/fetch.d.ts +5 -0
- package/dist/types/globals/form-data.d.ts +37 -0
- package/dist/types/globals/headers.d.ts +36 -0
- package/dist/types/globals/request.d.ts +16 -0
- package/dist/types/globals/response.d.ts +25 -0
- package/dist/types/globals/serve.d.ts +16 -0
- package/dist/types/handle.d.ts +6 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/setup.d.ts +48 -0
- package/dist/types/types.d.ts +175 -0
- package/package.json +51 -6
package/README.md
CHANGED
|
@@ -1,45 +1,111 @@
|
|
|
1
1
|
# @ricsam/quickjs-fetch
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
3
|
+
Fetch API and HTTP server handler.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { setupFetch } from "@ricsam/quickjs-fetch";
|
|
7
|
+
|
|
8
|
+
const handle = setupFetch(context, {
|
|
9
|
+
onFetch: async (request) => {
|
|
10
|
+
// Handle outbound fetch() calls from QuickJS
|
|
11
|
+
console.log(`Fetching: ${request.url}`);
|
|
12
|
+
return fetch(request);
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Injected Globals:**
|
|
18
|
+
- `fetch`, `Request`, `Response`, `Headers`
|
|
19
|
+
- `FormData`, `AbortController`, `AbortSignal`
|
|
20
|
+
- `serve` (HTTP server handler)
|
|
21
|
+
|
|
22
|
+
**Usage in QuickJS:**
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// Outbound fetch
|
|
26
|
+
const response = await fetch("https://api.example.com/data");
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
|
|
29
|
+
// Request/Response
|
|
30
|
+
const request = new Request("https://example.com", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
body: JSON.stringify({ name: "test" }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const response = new Response(JSON.stringify({ ok: true }), {
|
|
37
|
+
status: 200,
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Static methods
|
|
42
|
+
Response.json({ message: "hello" });
|
|
43
|
+
Response.redirect("https://example.com", 302);
|
|
44
|
+
|
|
45
|
+
// AbortController
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
setTimeout(() => controller.abort(), 5000);
|
|
48
|
+
await fetch(url, { signal: controller.signal });
|
|
49
|
+
|
|
50
|
+
// FormData
|
|
51
|
+
const formData = new FormData();
|
|
52
|
+
formData.append("name", "John");
|
|
53
|
+
formData.append("file", new File(["content"], "file.txt"));
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### HTTP Server
|
|
57
|
+
|
|
58
|
+
Register a server handler in QuickJS and dispatch requests from the host:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// In QuickJS
|
|
62
|
+
context.evalCode(`
|
|
63
|
+
serve({
|
|
64
|
+
fetch(request, server) {
|
|
65
|
+
const url = new URL(request.url);
|
|
66
|
+
|
|
67
|
+
if (url.pathname === "/ws") {
|
|
68
|
+
if (server.upgrade(request, { data: { userId: "123" } })) {
|
|
69
|
+
return new Response(null, { status: 101 });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return Response.json({ path: url.pathname });
|
|
74
|
+
},
|
|
75
|
+
websocket: {
|
|
76
|
+
open(ws) {
|
|
77
|
+
console.log("Connected:", ws.data.userId);
|
|
78
|
+
},
|
|
79
|
+
message(ws, message) {
|
|
80
|
+
ws.send("Echo: " + message);
|
|
81
|
+
},
|
|
82
|
+
close(ws, code, reason) {
|
|
83
|
+
console.log("Closed:", code, reason);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
`);
|
|
88
|
+
|
|
89
|
+
// From host - dispatch HTTP request
|
|
90
|
+
const response = await handle.dispatchRequest(
|
|
91
|
+
new Request("http://localhost/api/users")
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Check for WebSocket upgrade
|
|
95
|
+
const upgrade = handle.getUpgradeRequest();
|
|
96
|
+
if (upgrade?.requested) {
|
|
97
|
+
// Handle WebSocket connection
|
|
98
|
+
const connectionId = crypto.randomUUID();
|
|
99
|
+
handle.dispatchWebSocketOpen(connectionId, upgrade.data);
|
|
100
|
+
|
|
101
|
+
// Listen for outgoing messages
|
|
102
|
+
handle.onWebSocketCommand((command) => {
|
|
103
|
+
if (command.type === "message") {
|
|
104
|
+
// Send to actual WebSocket client
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Forward incoming messages
|
|
109
|
+
handle.dispatchWebSocketMessage(connectionId, "Hello from client");
|
|
110
|
+
}
|
|
111
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @bun @bun-cjs
|
|
2
|
+
(function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
7
|
+
var __toCommonJS = (from) => {
|
|
8
|
+
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
if (entry)
|
|
10
|
+
return entry;
|
|
11
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
13
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
14
|
+
get: () => from[key],
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
}));
|
|
17
|
+
__moduleCache.set(from, entry);
|
|
18
|
+
return entry;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// packages/fetch/src/index.ts
|
|
31
|
+
var exports_src = {};
|
|
32
|
+
__export(exports_src, {
|
|
33
|
+
setupFetch: () => import_setup.setupFetch,
|
|
34
|
+
responseStateToNative: () => import_response.responseStateToNative,
|
|
35
|
+
headersStateToNative: () => import_headers.headersStateToNative,
|
|
36
|
+
createResponseStateFromNative: () => import_response.createResponseStateFromNative,
|
|
37
|
+
createRequestStateFromNative: () => import_request.createRequestStateFromNative,
|
|
38
|
+
createHeadersStateFromNative: () => import_headers.createHeadersStateFromNative
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(exports_src);
|
|
41
|
+
var import_setup = require("./setup.ts");
|
|
42
|
+
var import_headers = require("./globals/headers.ts");
|
|
43
|
+
var import_response = require("./globals/response.ts");
|
|
44
|
+
var import_request = require("./globals/request.ts");
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
//# debugId=1A00B620C92685AE64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export { setupFetch } from \"./setup.ts\";\n\nexport type {\n SetupFetchOptions,\n FetchHandle,\n WebSocketData,\n UpgradeRequest,\n WebSocketCommand,\n WebSocketOutgoingMessage,\n WebSocketClose,\n ServeState,\n ServerWebSocketState,\n} from \"./types.ts\";\n\n// Re-export utilities that might be useful\nexport {\n createHeadersStateFromNative,\n headersStateToNative,\n} from \"./globals/headers.ts\";\nexport {\n responseStateToNative,\n createResponseStateFromNative,\n} from \"./globals/response.ts\";\nexport { createRequestStateFromNative } from \"./globals/request.ts\";\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAA2B,IAA3B;AAkBO,IAHP;AAOO,IAHP;AAI6C,IAA7C;",
|
|
8
|
+
"debugId": "1A00B620C92685AE64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/fetch/src/index.ts
|
|
3
|
+
import { setupFetch } from "./setup.ts";
|
|
4
|
+
import {
|
|
5
|
+
createHeadersStateFromNative,
|
|
6
|
+
headersStateToNative
|
|
7
|
+
} from "./globals/headers.ts";
|
|
8
|
+
import {
|
|
9
|
+
responseStateToNative,
|
|
10
|
+
createResponseStateFromNative
|
|
11
|
+
} from "./globals/response.ts";
|
|
12
|
+
import { createRequestStateFromNative } from "./globals/request.ts";
|
|
13
|
+
export {
|
|
14
|
+
setupFetch,
|
|
15
|
+
responseStateToNative,
|
|
16
|
+
headersStateToNative,
|
|
17
|
+
createResponseStateFromNative,
|
|
18
|
+
createRequestStateFromNative,
|
|
19
|
+
createHeadersStateFromNative
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//# debugId=DCC271F8F4EC31DA64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export { setupFetch } from \"./setup.ts\";\n\nexport type {\n SetupFetchOptions,\n FetchHandle,\n WebSocketData,\n UpgradeRequest,\n WebSocketCommand,\n WebSocketOutgoingMessage,\n WebSocketClose,\n ServeState,\n ServerWebSocketState,\n} from \"./types.ts\";\n\n// Re-export utilities that might be useful\nexport {\n createHeadersStateFromNative,\n headersStateToNative,\n} from \"./globals/headers.ts\";\nexport {\n responseStateToNative,\n createResponseStateFromNative,\n} from \"./globals/response.ts\";\nexport { createRequestStateFromNative } from \"./globals/request.ts\";\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;AAAA;AAeA;AAAA;AAAA;AAAA;AAIA;AAAA;AAAA;AAAA;AAIA;",
|
|
8
|
+
"debugId": "DCC271F8F4EC31DA64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { QuickJSContext } from "quickjs-emscripten";
|
|
2
|
+
/**
|
|
3
|
+
* Setup AbortController and AbortSignal classes in QuickJS.
|
|
4
|
+
* Uses pure QuickJS implementation so that:
|
|
5
|
+
* - controller.signal returns an actual AbortSignal instance
|
|
6
|
+
* - Event listeners work correctly
|
|
7
|
+
* - All prototype methods are accessible
|
|
8
|
+
*/
|
|
9
|
+
export declare function setupAbortControllerAndSignal(context: QuickJSContext): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
import type { StateMap } from "@ricsam/quickjs-core";
|
|
3
|
+
/**
|
|
4
|
+
* Internal state for FormData entries
|
|
5
|
+
* Each entry can have multiple values (same key appended multiple times)
|
|
6
|
+
*/
|
|
7
|
+
export interface FormDataEntry {
|
|
8
|
+
name: string;
|
|
9
|
+
value: string | FormDataFileValue;
|
|
10
|
+
}
|
|
11
|
+
export interface FormDataFileValue {
|
|
12
|
+
data: Uint8Array;
|
|
13
|
+
filename: string;
|
|
14
|
+
type: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FormDataState {
|
|
17
|
+
entries: FormDataEntry[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create the FormData class for QuickJS
|
|
21
|
+
*/
|
|
22
|
+
export declare function createFormDataClass(context: QuickJSContext, stateMap: StateMap): QuickJSHandle;
|
|
23
|
+
/**
|
|
24
|
+
* Parse multipart/form-data body
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseMultipartFormData(body: Uint8Array, contentType: string): FormDataState;
|
|
27
|
+
/**
|
|
28
|
+
* Parse URL-encoded form data
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseUrlEncodedFormData(body: Uint8Array): FormDataState;
|
|
31
|
+
/**
|
|
32
|
+
* Serialize FormData to multipart/form-data format
|
|
33
|
+
*/
|
|
34
|
+
export declare function serializeFormData(state: FormDataState): {
|
|
35
|
+
body: Uint8Array;
|
|
36
|
+
contentType: string;
|
|
37
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
import type { StateMap } from "@ricsam/quickjs-core";
|
|
3
|
+
import type { HeadersState } from "../types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Create the Headers class for QuickJS
|
|
6
|
+
*/
|
|
7
|
+
export declare function createHeadersClass(context: QuickJSContext, stateMap: StateMap): QuickJSHandle;
|
|
8
|
+
/**
|
|
9
|
+
* Create a HeadersState from a native Headers object
|
|
10
|
+
*/
|
|
11
|
+
export declare function createHeadersStateFromNative(headers: Headers): HeadersState;
|
|
12
|
+
/**
|
|
13
|
+
* Convert HeadersState to native Headers
|
|
14
|
+
*/
|
|
15
|
+
export declare function headersStateToNative(state: HeadersState): Headers;
|
|
16
|
+
/**
|
|
17
|
+
* Interface for a Headers-like object that can be returned from getters
|
|
18
|
+
*/
|
|
19
|
+
export interface HeadersLike {
|
|
20
|
+
headers: Map<string, string[]>;
|
|
21
|
+
append(name: string, value: string): void;
|
|
22
|
+
delete(name: string): void;
|
|
23
|
+
get(name: string): string | null;
|
|
24
|
+
has(name: string): boolean;
|
|
25
|
+
set(name: string, value: string): void;
|
|
26
|
+
entries(): Array<[string, string]>;
|
|
27
|
+
keys(): string[];
|
|
28
|
+
values(): string[];
|
|
29
|
+
forEach(callback: (value: string, key: string) => void): void;
|
|
30
|
+
getSetCookie(): string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a Headers-like object from HeadersState that has all the Headers methods
|
|
34
|
+
* This is used for getters that need to return an object with Headers API
|
|
35
|
+
*/
|
|
36
|
+
export declare function createHeadersLike(state: HeadersState): HeadersLike;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
import type { StateMap } from "@ricsam/quickjs-core";
|
|
3
|
+
import type { RequestState } from "../types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Type for the stream factory function
|
|
6
|
+
*/
|
|
7
|
+
type StreamFactory = (source: UnderlyingSource) => QuickJSHandle;
|
|
8
|
+
/**
|
|
9
|
+
* Create the Request class for QuickJS
|
|
10
|
+
*/
|
|
11
|
+
export declare function createRequestClass(context: QuickJSContext, stateMap: StateMap, createStream?: StreamFactory): QuickJSHandle;
|
|
12
|
+
/**
|
|
13
|
+
* Create a RequestState from a native Request object
|
|
14
|
+
*/
|
|
15
|
+
export declare function createRequestStateFromNative(request: Request): Promise<RequestState>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
import type { StateMap } from "@ricsam/quickjs-core";
|
|
3
|
+
import type { ResponseState } from "../types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Type for the stream factory function
|
|
6
|
+
*/
|
|
7
|
+
type StreamFactory = (source: UnderlyingSource) => QuickJSHandle;
|
|
8
|
+
/**
|
|
9
|
+
* Create the Response class for QuickJS
|
|
10
|
+
*/
|
|
11
|
+
export declare function createResponseClass(context: QuickJSContext, stateMap: StateMap, createStream?: StreamFactory): QuickJSHandle;
|
|
12
|
+
/**
|
|
13
|
+
* Add static methods to Response class after it's been set on global
|
|
14
|
+
* This must be called after Response and Headers are available on global
|
|
15
|
+
*/
|
|
16
|
+
export declare function addResponseStaticMethods(context: QuickJSContext): void;
|
|
17
|
+
/**
|
|
18
|
+
* Convert ResponseState (or unmarshalled Response object) to native Response
|
|
19
|
+
*/
|
|
20
|
+
export declare function responseStateToNative(state: ResponseState | Record<string, unknown>): Response;
|
|
21
|
+
/**
|
|
22
|
+
* Create a ResponseState from a native Response
|
|
23
|
+
*/
|
|
24
|
+
export declare function createResponseStateFromNative(response: Response): Promise<ResponseState>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { QuickJSContext, QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
import type { StateMap } from "@ricsam/quickjs-core";
|
|
3
|
+
import type { ServeState, WebSocketCommand } from "../types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Create the ServerWebSocket class for QuickJS
|
|
6
|
+
*/
|
|
7
|
+
export declare function createServerWebSocketClass(context: QuickJSContext, stateMap: StateMap, onWebSocketCommand: (cmd: WebSocketCommand) => void): QuickJSHandle;
|
|
8
|
+
/**
|
|
9
|
+
* Create the Server class for QuickJS
|
|
10
|
+
*/
|
|
11
|
+
export declare function createServerClass(context: QuickJSContext, stateMap: StateMap, serveState: ServeState): QuickJSHandle;
|
|
12
|
+
/**
|
|
13
|
+
* Create the serve function for QuickJS
|
|
14
|
+
* Uses context.newFunction instead of defineFunction to properly manage handle lifetimes
|
|
15
|
+
*/
|
|
16
|
+
export declare function createServeFunction(context: QuickJSContext, stateMap: StateMap, serveState: ServeState): QuickJSHandle;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { QuickJSContext } from "quickjs-emscripten";
|
|
2
|
+
import type { StateMap, FetchHandle, ServeState } from "./types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Create the FetchHandle implementation
|
|
5
|
+
*/
|
|
6
|
+
export declare function createFetchHandle(context: QuickJSContext, stateMap: StateMap, serveState: ServeState): FetchHandle;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { setupFetch } from "./setup.ts";
|
|
2
|
+
export type { SetupFetchOptions, FetchHandle, WebSocketData, UpgradeRequest, WebSocketCommand, WebSocketOutgoingMessage, WebSocketClose, ServeState, ServerWebSocketState, } from "./types.ts";
|
|
3
|
+
export { createHeadersStateFromNative, headersStateToNative, } from "./globals/headers.ts";
|
|
4
|
+
export { responseStateToNative, createResponseStateFromNative, } from "./globals/response.ts";
|
|
5
|
+
export { createRequestStateFromNative } from "./globals/request.ts";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { QuickJSContext } from "quickjs-emscripten";
|
|
2
|
+
import type { SetupFetchOptions, FetchHandle } from "./types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Setup Fetch API in a QuickJS context
|
|
5
|
+
*
|
|
6
|
+
* Injects the following globals:
|
|
7
|
+
* - fetch
|
|
8
|
+
* - Request
|
|
9
|
+
* - Response
|
|
10
|
+
* - Headers
|
|
11
|
+
* - AbortController
|
|
12
|
+
* - AbortSignal
|
|
13
|
+
* - serve
|
|
14
|
+
* - FormData
|
|
15
|
+
*
|
|
16
|
+
* Also sets up Core APIs (Streams, Blob, File) if not already present.
|
|
17
|
+
*
|
|
18
|
+
* **Private globals (internal use):**
|
|
19
|
+
* - `__Server__` - Server class for serve() handler, instantiated via evalCode
|
|
20
|
+
* - `__ServerWebSocket__` - WebSocket class for connection handling
|
|
21
|
+
* - `__scheduleTimeout__` - Host function to schedule AbortSignal.timeout()
|
|
22
|
+
* - `__checkTimeout__` - Host function to check if timeout elapsed
|
|
23
|
+
*
|
|
24
|
+
* These private globals follow the `__Name__` convention and are required for
|
|
25
|
+
* JavaScript code in QuickJS to create class instances via evalCode.
|
|
26
|
+
* See PATTERNS.md section 5 for details.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const handle = setupFetch(context, {
|
|
30
|
+
* onFetch: async (request) => {
|
|
31
|
+
* // Proxy to real fetch
|
|
32
|
+
* return fetch(request);
|
|
33
|
+
* }
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* context.evalCode(`
|
|
37
|
+
* serve({
|
|
38
|
+
* fetch(request, server) {
|
|
39
|
+
* return new Response("Hello!");
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
* `);
|
|
43
|
+
*
|
|
44
|
+
* const response = await handle.dispatchRequest(
|
|
45
|
+
* new Request("http://localhost/")
|
|
46
|
+
* );
|
|
47
|
+
*/
|
|
48
|
+
export declare function setupFetch(context: QuickJSContext, options?: SetupFetchOptions): FetchHandle;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { QuickJSHandle } from "quickjs-emscripten";
|
|
2
|
+
import type { StateMap, CoreHandle } from "@ricsam/quickjs-core";
|
|
3
|
+
export type { StateMap, CoreHandle };
|
|
4
|
+
/**
|
|
5
|
+
* Options for setting up Fetch APIs
|
|
6
|
+
*/
|
|
7
|
+
export interface SetupFetchOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Handler for outbound fetch() calls from QuickJS
|
|
10
|
+
* If not provided, fetch() will throw an error
|
|
11
|
+
*/
|
|
12
|
+
onFetch?: (request: Request) => Promise<Response>;
|
|
13
|
+
/** Existing state map (creates new one if not provided) */
|
|
14
|
+
stateMap?: StateMap;
|
|
15
|
+
/** Existing core handle (sets up core if not provided) */
|
|
16
|
+
coreHandle?: CoreHandle;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Data associated with a WebSocket connection
|
|
20
|
+
*/
|
|
21
|
+
export interface WebSocketData {
|
|
22
|
+
/** User-provided data from server.upgrade() */
|
|
23
|
+
data: unknown;
|
|
24
|
+
/** Connection ID assigned by the host */
|
|
25
|
+
connectionId: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pending upgrade request info
|
|
29
|
+
*/
|
|
30
|
+
export interface UpgradeRequest {
|
|
31
|
+
/** Whether an upgrade was requested */
|
|
32
|
+
requested: true;
|
|
33
|
+
/** User data passed to server.upgrade() */
|
|
34
|
+
data: unknown;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Message to send to a WebSocket
|
|
38
|
+
*/
|
|
39
|
+
export interface WebSocketOutgoingMessage {
|
|
40
|
+
type: "message";
|
|
41
|
+
connectionId: string;
|
|
42
|
+
data: string | ArrayBuffer;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Close command for a WebSocket
|
|
46
|
+
*/
|
|
47
|
+
export interface WebSocketClose {
|
|
48
|
+
type: "close";
|
|
49
|
+
connectionId: string;
|
|
50
|
+
code?: number;
|
|
51
|
+
reason?: string;
|
|
52
|
+
}
|
|
53
|
+
export type WebSocketCommand = WebSocketOutgoingMessage | WebSocketClose;
|
|
54
|
+
/**
|
|
55
|
+
* Internal state for serve() handler
|
|
56
|
+
*/
|
|
57
|
+
export interface ServeState {
|
|
58
|
+
fetchHandler: QuickJSHandle | null;
|
|
59
|
+
websocketHandlers: {
|
|
60
|
+
open?: QuickJSHandle;
|
|
61
|
+
message?: QuickJSHandle;
|
|
62
|
+
close?: QuickJSHandle;
|
|
63
|
+
error?: QuickJSHandle;
|
|
64
|
+
};
|
|
65
|
+
pendingUpgrade: UpgradeRequest | null;
|
|
66
|
+
activeConnections: Map<string, ServerWebSocketState>;
|
|
67
|
+
}
|
|
68
|
+
export interface ServerWebSocketState {
|
|
69
|
+
data: unknown;
|
|
70
|
+
readyState: number;
|
|
71
|
+
connectionId: string;
|
|
72
|
+
wsHandle: QuickJSHandle | null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Handle returned from setupFetch
|
|
76
|
+
*/
|
|
77
|
+
export interface FetchHandle {
|
|
78
|
+
/** State map containing all internal states */
|
|
79
|
+
readonly stateMap: StateMap;
|
|
80
|
+
/**
|
|
81
|
+
* Dispatch an HTTP request to the serve() handler
|
|
82
|
+
*
|
|
83
|
+
* @returns Response from the QuickJS handler
|
|
84
|
+
* @throws If no serve() handler is registered
|
|
85
|
+
*/
|
|
86
|
+
dispatchRequest(request: Request): Promise<Response>;
|
|
87
|
+
/**
|
|
88
|
+
* Check if the last request resulted in an upgrade request
|
|
89
|
+
* Must be called immediately after dispatchRequest()
|
|
90
|
+
*/
|
|
91
|
+
getUpgradeRequest(): UpgradeRequest | null;
|
|
92
|
+
/**
|
|
93
|
+
* Dispatch WebSocket open event
|
|
94
|
+
*
|
|
95
|
+
* @param connectionId Unique ID for this connection (host-assigned)
|
|
96
|
+
* @param data The data object from getUpgradeRequest()
|
|
97
|
+
*/
|
|
98
|
+
dispatchWebSocketOpen(connectionId: string, data?: unknown): void;
|
|
99
|
+
/**
|
|
100
|
+
* Dispatch WebSocket message event
|
|
101
|
+
*/
|
|
102
|
+
dispatchWebSocketMessage(connectionId: string, message: string | ArrayBuffer): void;
|
|
103
|
+
/**
|
|
104
|
+
* Dispatch WebSocket close event
|
|
105
|
+
*/
|
|
106
|
+
dispatchWebSocketClose(connectionId: string, code: number, reason: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Dispatch WebSocket error event
|
|
109
|
+
*/
|
|
110
|
+
dispatchWebSocketError(connectionId: string, error: Error): void;
|
|
111
|
+
/**
|
|
112
|
+
* Register a callback for outgoing WebSocket messages/commands
|
|
113
|
+
* Called when QuickJS code calls ws.send() or ws.close()
|
|
114
|
+
*/
|
|
115
|
+
onWebSocketCommand(callback: (command: WebSocketCommand) => void): () => void;
|
|
116
|
+
/**
|
|
117
|
+
* Check if a serve() handler is registered
|
|
118
|
+
*/
|
|
119
|
+
hasServeHandler(): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Dispose all handles and cleanup
|
|
122
|
+
*/
|
|
123
|
+
dispose(): void;
|
|
124
|
+
}
|
|
125
|
+
export interface HeadersState {
|
|
126
|
+
headers: Map<string, string[]>;
|
|
127
|
+
}
|
|
128
|
+
export interface RequestState {
|
|
129
|
+
method: string;
|
|
130
|
+
url: string;
|
|
131
|
+
headersState: HeadersState;
|
|
132
|
+
body: Uint8Array | null;
|
|
133
|
+
bodyUsed: boolean;
|
|
134
|
+
cache: string;
|
|
135
|
+
credentials: string;
|
|
136
|
+
destination: string;
|
|
137
|
+
integrity: string;
|
|
138
|
+
keepalive: boolean;
|
|
139
|
+
mode: string;
|
|
140
|
+
redirect: string;
|
|
141
|
+
referrer: string;
|
|
142
|
+
referrerPolicy: string;
|
|
143
|
+
signal: AbortSignalState | null;
|
|
144
|
+
}
|
|
145
|
+
export interface ResponseState {
|
|
146
|
+
status: number;
|
|
147
|
+
statusText: string;
|
|
148
|
+
headersState: HeadersState;
|
|
149
|
+
body: Uint8Array | null;
|
|
150
|
+
bodyUsed: boolean;
|
|
151
|
+
url: string;
|
|
152
|
+
redirected: boolean;
|
|
153
|
+
type: string;
|
|
154
|
+
ok: boolean;
|
|
155
|
+
}
|
|
156
|
+
export interface AbortControllerState {
|
|
157
|
+
signalState: AbortSignalState;
|
|
158
|
+
}
|
|
159
|
+
export interface AbortSignalState {
|
|
160
|
+
aborted: boolean;
|
|
161
|
+
reason: unknown;
|
|
162
|
+
listeners: Set<() => void>;
|
|
163
|
+
}
|
|
164
|
+
export interface FormDataFileValue {
|
|
165
|
+
data: Uint8Array;
|
|
166
|
+
filename: string;
|
|
167
|
+
type: string;
|
|
168
|
+
}
|
|
169
|
+
export interface FormDataEntry {
|
|
170
|
+
name: string;
|
|
171
|
+
value: string | FormDataFileValue;
|
|
172
|
+
}
|
|
173
|
+
export interface FormDataState {
|
|
174
|
+
entries: FormDataEntry[];
|
|
175
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/quickjs-fetch",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"main": "./dist/cjs/index.cjs",
|
|
5
|
+
"types": "./dist/types/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"require": "./dist/cjs/index.cjs",
|
|
10
|
+
"import": "./dist/mjs/index.mjs"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun",
|
|
15
|
+
"test": "bun test",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@ricsam/quickjs-core": "^0.2.0",
|
|
20
|
+
"quickjs-emscripten": "^0.31.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"quickjs-emscripten": "^0.31.0"
|
|
24
|
+
},
|
|
25
|
+
"author": "Richard Samuelsson",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/ricsam/richie-qjs.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/ricsam/richie-qjs/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/ricsam/richie-qjs#readme",
|
|
5
35
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
36
|
+
"quickjs",
|
|
37
|
+
"sandbox",
|
|
38
|
+
"javascript",
|
|
39
|
+
"runtime",
|
|
40
|
+
"fetch",
|
|
41
|
+
"filesystem",
|
|
42
|
+
"streams",
|
|
43
|
+
"wasm",
|
|
44
|
+
"emscripten"
|
|
45
|
+
],
|
|
46
|
+
"description": "Fetch API implementation for QuickJS runtime",
|
|
47
|
+
"module": "./dist/mjs/index.mjs",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"README.md"
|
|
9
54
|
]
|
|
10
|
-
}
|
|
55
|
+
}
|