@lovrozagar/honey 0.1.0 → 0.1.1
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 +1490 -3
- package/package.json +8 -2
- package/src/cli.ts +1 -1
- package/src/index.ts +52 -9
- package/src/node-request.ts +2 -1
- package/src/node.ts +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovrozagar/honey",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Type-safe API framework built on Web Standards. Builder-pattern DX, precompiled radix tree in production.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -11,10 +11,16 @@
|
|
|
11
11
|
"type-safe",
|
|
12
12
|
"web-standards"
|
|
13
13
|
],
|
|
14
|
+
"homepage": "https://github.com/lovrozagar/honey#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/lovrozagar/honey/issues"
|
|
17
|
+
},
|
|
14
18
|
"license": "MIT",
|
|
19
|
+
"author": "lovrozagar",
|
|
15
20
|
"repository": {
|
|
16
21
|
"type": "git",
|
|
17
|
-
"url": "https://github.com/lovrozagar/honey"
|
|
22
|
+
"url": "git+https://github.com/lovrozagar/honey.git",
|
|
23
|
+
"directory": "packages/core"
|
|
18
24
|
},
|
|
19
25
|
"publishConfig": {
|
|
20
26
|
"access": "public"
|
package/src/cli.ts
CHANGED
|
@@ -74,7 +74,7 @@ async function loadConfigFromVite(configPath: string): Promise<HoneyVitePluginCo
|
|
|
74
74
|
await jiti.import(configPath)
|
|
75
75
|
|
|
76
76
|
/* read stashed config from the same module graph */
|
|
77
|
-
const pluginMod = (await jiti.import("honey/plugin")) as Record<string, unknown>
|
|
77
|
+
const pluginMod = (await jiti.import("@lovrozagar/honey/plugin")) as Record<string, unknown>
|
|
78
78
|
const getter = pluginMod.getLastHoneyConfig as typeof getLastHoneyConfig | undefined
|
|
79
79
|
return getter?.()
|
|
80
80
|
}
|
package/src/index.ts
CHANGED
|
@@ -231,6 +231,15 @@ function walkTreeHandlers(
|
|
|
231
231
|
const EMPTY_PARAMS = EMPTY_OBJ as Record<string, string>
|
|
232
232
|
const EMPTY_MW: RuntimeMiddleware[] = []
|
|
233
233
|
|
|
234
|
+
function requestIsWsUpgrade(request: Request): boolean {
|
|
235
|
+
try {
|
|
236
|
+
return request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
237
|
+
} catch {
|
|
238
|
+
/* Deno closes the Request after upgradeWebSocket */
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
234
243
|
/** Find first '?' or '#' in url starting from pos */
|
|
235
244
|
function findSearchOrHash(url: string, pos: number): number {
|
|
236
245
|
for (let i = pos; i < url.length; i++) {
|
|
@@ -262,8 +271,32 @@ type FetchCtx<TEnv> = {
|
|
|
262
271
|
request: Request;
|
|
263
272
|
startTime: number;
|
|
264
273
|
url: () => URL;
|
|
274
|
+
/** Already known from fetch() before Deno.upgradeWebSocket consumes the request. */
|
|
275
|
+
wsUpgrade?: boolean;
|
|
276
|
+
/** Headers copied before Deno.upgradeWebSocket closes the Request. */
|
|
277
|
+
headerSnap?: Headers;
|
|
265
278
|
};
|
|
266
279
|
|
|
280
|
+
function requestWithHeaders(req: Request, headers: Headers): Request {
|
|
281
|
+
return new Proxy(req, {
|
|
282
|
+
get(target, prop) {
|
|
283
|
+
if (prop === "headers") return headers
|
|
284
|
+
const value = Reflect.get(target, prop, target)
|
|
285
|
+
return typeof value === "function" ? (value as (...args: never[]) => unknown).bind(target) : value
|
|
286
|
+
},
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function ctxRequest(fc: FetchCtx<unknown>): Request {
|
|
291
|
+
if (!fc.headerSnap) return fc.request
|
|
292
|
+
try {
|
|
293
|
+
void fc.request.headers.get("upgrade")
|
|
294
|
+
return fc.request
|
|
295
|
+
} catch {
|
|
296
|
+
return requestWithHeaders(fc.request, fc.headerSnap)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
267
300
|
function defaultErrorFormatter(
|
|
268
301
|
_error: HoneyError,
|
|
269
302
|
defaultShape: Record<string, unknown>,
|
|
@@ -1778,6 +1811,7 @@ export class Honey<
|
|
|
1778
1811
|
return this._doFetch(request, env, executionCtx);
|
|
1779
1812
|
}
|
|
1780
1813
|
const isWsUpgrade = request.headers.get("upgrade")?.toLowerCase() === "websocket"
|
|
1814
|
+
const headerSnap = isWsUpgrade ? new Headers(request.headers) : undefined
|
|
1781
1815
|
const path = this.pathFromRequest(request)
|
|
1782
1816
|
const canPreUpgrade =
|
|
1783
1817
|
isWsUpgrade &&
|
|
@@ -1785,7 +1819,14 @@ export class Honey<
|
|
|
1785
1819
|
!this.trailingSlashRedirects(path) &&
|
|
1786
1820
|
matchWsRoute(this._root, this.pathAfterPrefix(path)) !== null
|
|
1787
1821
|
const pre = canPreUpgrade ? this._wsAdapter?.preUpgrade?.(request) : undefined
|
|
1788
|
-
|
|
1822
|
+
/* After Deno.upgradeWebSocket the Request is closed. A sync throw here
|
|
1823
|
+
* used to be boxed by async _doFetch; keep 101 returning either way. */
|
|
1824
|
+
let work: Response | Promise<Response>
|
|
1825
|
+
try {
|
|
1826
|
+
work = this._doFetch(request, env, executionCtx, isWsUpgrade === true, headerSnap)
|
|
1827
|
+
} catch (err) {
|
|
1828
|
+
work = Promise.reject(err)
|
|
1829
|
+
}
|
|
1789
1830
|
if (!pre) return work
|
|
1790
1831
|
void Promise.resolve(work)
|
|
1791
1832
|
.then((res) => {
|
|
@@ -1842,6 +1883,8 @@ export class Honey<
|
|
|
1842
1883
|
request: Request,
|
|
1843
1884
|
env: TEnv,
|
|
1844
1885
|
executionCtx?: { waitUntil?: (p: Promise<unknown>) => void },
|
|
1886
|
+
knownWsUpgrade = false,
|
|
1887
|
+
headerSnap?: Headers,
|
|
1845
1888
|
): Response | Promise<Response> {
|
|
1846
1889
|
const startTime = performance.now();
|
|
1847
1890
|
|
|
@@ -1875,6 +1918,8 @@ export class Honey<
|
|
|
1875
1918
|
request,
|
|
1876
1919
|
startTime,
|
|
1877
1920
|
url: getUrl,
|
|
1921
|
+
wsUpgrade: knownWsUpgrade,
|
|
1922
|
+
headerSnap,
|
|
1878
1923
|
};
|
|
1879
1924
|
|
|
1880
1925
|
if (this._telemetry !== null) {
|
|
@@ -1913,8 +1958,8 @@ export class Honey<
|
|
|
1913
1958
|
}
|
|
1914
1959
|
}
|
|
1915
1960
|
|
|
1916
|
-
/* WebSocket route check */
|
|
1917
|
-
const isWsUpgrade = request
|
|
1961
|
+
/* WebSocket route check — do not re-read headers after Deno.upgradeWebSocket */
|
|
1962
|
+
const isWsUpgrade = knownWsUpgrade || requestIsWsUpgrade(request);
|
|
1918
1963
|
if (isWsUpgrade) {
|
|
1919
1964
|
const wsMatch = matchWsRoute(this._root, path);
|
|
1920
1965
|
if (wsMatch !== null) {
|
|
@@ -2085,8 +2130,7 @@ export class Honey<
|
|
|
2085
2130
|
return this._handleRealtime(fc, wsMatch, realtimeConfig);
|
|
2086
2131
|
}
|
|
2087
2132
|
|
|
2088
|
-
const isUpgrade =
|
|
2089
|
-
fc.request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
2133
|
+
const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request);
|
|
2090
2134
|
if (!isUpgrade) {
|
|
2091
2135
|
return new Response(null, {
|
|
2092
2136
|
headers: { upgrade: "websocket" },
|
|
@@ -2108,7 +2152,7 @@ export class Honey<
|
|
|
2108
2152
|
env: fc.env,
|
|
2109
2153
|
executionCtx: fc.executionCtx,
|
|
2110
2154
|
params: wsMatch.params,
|
|
2111
|
-
req: fc
|
|
2155
|
+
req: ctxRequest(fc),
|
|
2112
2156
|
urlFn: fc.url,
|
|
2113
2157
|
});
|
|
2114
2158
|
if (this._contextValues) Object.assign(wsCtx, this._contextValues);
|
|
@@ -2188,8 +2232,7 @@ export class Honey<
|
|
|
2188
2232
|
wsMatch: { handler: WSRouteHandler; params: Record<string, string> },
|
|
2189
2233
|
config: { handler: RealtimeRouteOpts["handler"]; middlewares?: RealtimeRouteOpts["use"]; reconnectBuffer?: number },
|
|
2190
2234
|
): Promise<Response> {
|
|
2191
|
-
const isUpgrade =
|
|
2192
|
-
fc.request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
2235
|
+
const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request);
|
|
2193
2236
|
if (!isUpgrade) {
|
|
2194
2237
|
return new Response(null, {
|
|
2195
2238
|
headers: { upgrade: "websocket" },
|
|
@@ -2217,7 +2260,7 @@ export class Honey<
|
|
|
2217
2260
|
env: fc.env,
|
|
2218
2261
|
executionCtx: fc.executionCtx,
|
|
2219
2262
|
params: wsMatch.params,
|
|
2220
|
-
req: fc
|
|
2263
|
+
req: ctxRequest(fc),
|
|
2221
2264
|
urlFn: fc.url,
|
|
2222
2265
|
});
|
|
2223
2266
|
if (this._contextValues) Object.assign(ctx, this._contextValues);
|
package/src/node-request.ts
CHANGED
|
@@ -121,7 +121,8 @@ export class NodeRequest {
|
|
|
121
121
|
get body(): ReadableStream<Uint8Array> | null {
|
|
122
122
|
if (!this.#hasBody) return null
|
|
123
123
|
if (this.#webBody === undefined) {
|
|
124
|
-
|
|
124
|
+
// Node's web stream and the DOM/Bun ReadableStream brands do not overlap.
|
|
125
|
+
this.#webBody = Readable.toWeb(this.#incoming) as unknown as ReadableStream<Uint8Array>
|
|
125
126
|
}
|
|
126
127
|
return this.#webBody
|
|
127
128
|
}
|
package/src/node.ts
CHANGED
|
@@ -16,6 +16,11 @@ type ServeOptions<TEnv> = {
|
|
|
16
16
|
const BUFFER_BODY_MAX = 256 * 1024
|
|
17
17
|
const RAW_BODY = Symbol.for("honey.rawBody")
|
|
18
18
|
|
|
19
|
+
// Node's `Readable.fromWeb` wants `node:stream/web` streams; DOM/Bun brands do not overlap.
|
|
20
|
+
function asNodeWebStream(stream: ReadableStream<Uint8Array>): import("node:stream/web").ReadableStream {
|
|
21
|
+
return stream as unknown as import("node:stream/web").ReadableStream
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
function incomingToRequest(req: IncomingMessage): Request {
|
|
20
25
|
return incomingToNodeRequest(req)
|
|
21
26
|
}
|
|
@@ -79,7 +84,7 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
|
|
|
79
84
|
}
|
|
80
85
|
res.writeHead(response.status, response.plainHeaders)
|
|
81
86
|
try {
|
|
82
|
-
await pipeline(Readable.fromWeb(
|
|
87
|
+
await pipeline(Readable.fromWeb(asNodeWebStream(stream)), res)
|
|
83
88
|
} catch {
|
|
84
89
|
if (!res.destroyed) res.destroy()
|
|
85
90
|
}
|
|
@@ -114,7 +119,7 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
|
|
|
114
119
|
|
|
115
120
|
res.writeHead(response.status, collectNodeHeaders(response))
|
|
116
121
|
try {
|
|
117
|
-
await pipeline(Readable.fromWeb(body
|
|
122
|
+
await pipeline(Readable.fromWeb(asNodeWebStream(body)), res)
|
|
118
123
|
} catch {
|
|
119
124
|
if (!res.destroyed) res.destroy()
|
|
120
125
|
}
|