@wooksjs/event-http 0.6.2 → 0.6.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/README.md +24 -0
- package/dist/index.cjs +132 -25
- package/dist/index.d.ts +110 -12
- package/dist/index.mjs +132 -25
- package/package.json +45 -37
- package/scripts/setup-skills.js +70 -0
- package/skills/wooksjs-event-http/SKILL.md +37 -0
- package/skills/wooksjs-event-http/addons.md +307 -0
- package/skills/wooksjs-event-http/core.md +297 -0
- package/skills/wooksjs-event-http/error-handling.md +253 -0
- package/skills/wooksjs-event-http/event-core.md +562 -0
- package/skills/wooksjs-event-http/request.md +220 -0
- package/skills/wooksjs-event-http/response.md +336 -0
- package/skills/wooksjs-event-http/routing.md +412 -0
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { WooksAdapterBase } from "wooks";
|
|
|
9
9
|
import { Readable as Readable$1 } from "stream";
|
|
10
10
|
|
|
11
11
|
//#region packages/event-http/src/event-http.ts
|
|
12
|
+
/** Creates an async event context for an incoming HTTP request/response pair. */
|
|
12
13
|
function createHttpContext(data, options) {
|
|
13
14
|
return createAsyncEventContext({
|
|
14
15
|
event: {
|
|
@@ -34,7 +35,7 @@ function escapeRegex(s) {
|
|
|
34
35
|
function safeDecode(f, v) {
|
|
35
36
|
try {
|
|
36
37
|
return f(v);
|
|
37
|
-
} catch
|
|
38
|
+
} catch {
|
|
38
39
|
return v;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -66,7 +67,12 @@ const units = {
|
|
|
66
67
|
|
|
67
68
|
//#endregion
|
|
68
69
|
//#region packages/event-http/src/utils/set-cookie.ts
|
|
70
|
+
const COOKIE_NAME_RE = /^[\w!#$%&'*+\-.^`|~]+$/;
|
|
71
|
+
function sanitizeCookieAttrValue(v) {
|
|
72
|
+
return v.replace(/[;\r\n]/g, "");
|
|
73
|
+
}
|
|
69
74
|
function renderCookie(key, data) {
|
|
75
|
+
if (!COOKIE_NAME_RE.test(key)) throw new TypeError(`Invalid cookie name "${key}"`);
|
|
70
76
|
let attrs = "";
|
|
71
77
|
for (const [a, v] of Object.entries(data.attrs)) {
|
|
72
78
|
const func = cookieAttrFunc[a];
|
|
@@ -80,8 +86,8 @@ function renderCookie(key, data) {
|
|
|
80
86
|
const cookieAttrFunc = {
|
|
81
87
|
expires: (v) => `Expires=${typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString()}`,
|
|
82
88
|
maxAge: (v) => `Max-Age=${convertTime(v, "s").toString()}`,
|
|
83
|
-
domain: (v) => `Domain=${v}`,
|
|
84
|
-
path: (v) => `Path=${v}`,
|
|
89
|
+
domain: (v) => `Domain=${sanitizeCookieAttrValue(String(v))}`,
|
|
90
|
+
path: (v) => `Path=${sanitizeCookieAttrValue(String(v))}`,
|
|
85
91
|
secure: (v) => v ? "Secure" : "",
|
|
86
92
|
httpOnly: (v) => v ? "HttpOnly" : "",
|
|
87
93
|
sameSite: (v) => v ? `SameSite=${typeof v === "string" ? v : "Strict"}` : ""
|
|
@@ -102,7 +108,7 @@ function encodingSupportsStream(encodings) {
|
|
|
102
108
|
}
|
|
103
109
|
async function uncompressBody(encodings, compressed) {
|
|
104
110
|
let buf = compressed;
|
|
105
|
-
for (const enc of encodings.slice().
|
|
111
|
+
for (const enc of encodings.slice().toReversed()) {
|
|
106
112
|
const c = compressors[enc];
|
|
107
113
|
if (!c) throw new Error(`Unsupported compression type "${enc}".`);
|
|
108
114
|
buf = await c.uncompress(buf);
|
|
@@ -112,7 +118,7 @@ async function uncompressBody(encodings, compressed) {
|
|
|
112
118
|
async function uncompressBodyStream(encodings, src) {
|
|
113
119
|
if (!encodingSupportsStream(encodings)) throw new Error("Some encodings lack a streaming decompressor");
|
|
114
120
|
let out = src;
|
|
115
|
-
for (const enc of Array.from(encodings).
|
|
121
|
+
for (const enc of Array.from(encodings).toReversed()) out = await compressors[enc].stream.uncompress(out);
|
|
116
122
|
return out;
|
|
117
123
|
}
|
|
118
124
|
|
|
@@ -432,8 +438,9 @@ function error_tl_default(ctx) {
|
|
|
432
438
|
<title>${statusCode} ${statusMessage}</title>
|
|
433
439
|
<style>
|
|
434
440
|
body {
|
|
435
|
-
font-family:
|
|
436
|
-
|
|
441
|
+
font-family:
|
|
442
|
+
-apple-system, BlinkMacMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
|
|
443
|
+
'Open Sans', 'Helvetica Neue', sans-serif;
|
|
437
444
|
display: flex;
|
|
438
445
|
justify-content: center;
|
|
439
446
|
align-items: flex-start;
|
|
@@ -667,11 +674,12 @@ function error_tl_default(ctx) {
|
|
|
667
674
|
//#endregion
|
|
668
675
|
//#region packages/event-http/src/errors/error-renderer.ts
|
|
669
676
|
let framework = {
|
|
670
|
-
version: "0.6.
|
|
677
|
+
version: "0.6.2",
|
|
671
678
|
poweredBy: `wooksjs`,
|
|
672
679
|
link: `https://wooks.moost.org/`,
|
|
673
680
|
image: `https://wooks.moost.org/wooks-full-logo.png`
|
|
674
681
|
};
|
|
682
|
+
/** Renders HTTP error responses in HTML, JSON, or plain text based on the Accept header. */
|
|
675
683
|
var HttpErrorRenderer = class extends BaseHttpResponseRenderer {
|
|
676
684
|
constructor(opts) {
|
|
677
685
|
super();
|
|
@@ -743,6 +751,7 @@ function escapeQuotes(s) {
|
|
|
743
751
|
|
|
744
752
|
//#endregion
|
|
745
753
|
//#region packages/event-http/src/errors/http-error.ts
|
|
754
|
+
/** Represents an HTTP error with a status code and optional structured body. */
|
|
746
755
|
var HttpError = class extends Error {
|
|
747
756
|
name = "HttpError";
|
|
748
757
|
constructor(code = 500, _body = "") {
|
|
@@ -774,15 +783,24 @@ var HttpError = class extends Error {
|
|
|
774
783
|
//#endregion
|
|
775
784
|
//#region packages/event-http/src/composables/request.ts
|
|
776
785
|
const xForwardedFor = "x-forwarded-for";
|
|
786
|
+
/** Default safety limits for request body reading (size, ratio, timeout). */
|
|
777
787
|
const DEFAULT_LIMITS = {
|
|
778
788
|
maxCompressed: 1 * 1024 * 1024,
|
|
779
789
|
maxInflated: 10 * 1024 * 1024,
|
|
780
790
|
maxRatio: 100,
|
|
781
791
|
readTimeoutMs: 1e4
|
|
782
792
|
};
|
|
793
|
+
/**
|
|
794
|
+
* Provides access to the incoming HTTP request (method, url, headers, body, IP).
|
|
795
|
+
* @example
|
|
796
|
+
* ```ts
|
|
797
|
+
* const { method, url, rawBody, getIp } = useRequest()
|
|
798
|
+
* const body = await rawBody()
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
783
801
|
function useRequest() {
|
|
784
802
|
const { store } = useHttpContext();
|
|
785
|
-
const { init
|
|
803
|
+
const { init } = store("request");
|
|
786
804
|
const event = store("event");
|
|
787
805
|
const req = event.get("req");
|
|
788
806
|
const contentEncoding = req.headers["content-encoding"];
|
|
@@ -796,18 +814,33 @@ function useRequest() {
|
|
|
796
814
|
].includes(p)) return true;
|
|
797
815
|
return false;
|
|
798
816
|
});
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
817
|
+
const limits = () => event.get("requestLimits");
|
|
818
|
+
const setLimit = (key, value) => {
|
|
819
|
+
let obj = limits();
|
|
820
|
+
if (!obj?.perRequest) {
|
|
821
|
+
obj = {
|
|
822
|
+
...obj,
|
|
823
|
+
perRequest: true
|
|
824
|
+
};
|
|
825
|
+
event.set("requestLimits", obj);
|
|
826
|
+
}
|
|
827
|
+
obj[key] = value;
|
|
828
|
+
};
|
|
829
|
+
const getMaxCompressed = () => limits()?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
|
|
830
|
+
const setMaxCompressed = (limit) => setLimit("maxCompressed", limit);
|
|
831
|
+
const getMaxInflated = () => limits()?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
|
|
832
|
+
const setMaxInflated = (limit) => setLimit("maxInflated", limit);
|
|
833
|
+
const getMaxRatio = () => limits()?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
|
|
834
|
+
const setMaxRatio = (limit) => setLimit("maxRatio", limit);
|
|
835
|
+
const getReadTimeoutMs = () => limits()?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
|
|
836
|
+
const setReadTimeoutMs = (limit) => setLimit("readTimeoutMs", limit);
|
|
805
837
|
const rawBody = () => init("rawBody", async () => {
|
|
806
838
|
const encs = contentEncodings();
|
|
807
839
|
const isZip = isCompressed();
|
|
808
840
|
const streamable = isZip && encodingSupportsStream(encs);
|
|
809
841
|
const maxCompressed = getMaxCompressed();
|
|
810
842
|
const maxInflated = getMaxInflated();
|
|
843
|
+
const maxRatio = getMaxRatio();
|
|
811
844
|
const timeoutMs = getReadTimeoutMs();
|
|
812
845
|
const cl = Number(req.headers["content-length"] ?? 0);
|
|
813
846
|
const upfrontLimit = isZip ? maxCompressed : maxInflated;
|
|
@@ -865,6 +898,7 @@ function useRequest() {
|
|
|
865
898
|
inflatedBytes = body.byteLength;
|
|
866
899
|
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
|
|
867
900
|
}
|
|
901
|
+
if (isZip && rawBytes > 0 && inflatedBytes / rawBytes > maxRatio) throw new HttpError(413, "Compression ratio too high");
|
|
868
902
|
return body;
|
|
869
903
|
});
|
|
870
904
|
const reqId = useEventId().getId;
|
|
@@ -896,15 +930,32 @@ function useRequest() {
|
|
|
896
930
|
getReadTimeoutMs,
|
|
897
931
|
setReadTimeoutMs,
|
|
898
932
|
getMaxInflated,
|
|
899
|
-
setMaxInflated
|
|
933
|
+
setMaxInflated,
|
|
934
|
+
getMaxRatio,
|
|
935
|
+
setMaxRatio
|
|
900
936
|
};
|
|
901
937
|
}
|
|
902
938
|
|
|
903
939
|
//#endregion
|
|
904
940
|
//#region packages/event-http/src/composables/headers.ts
|
|
941
|
+
/**
|
|
942
|
+
* Returns the incoming request headers.
|
|
943
|
+
* @example
|
|
944
|
+
* ```ts
|
|
945
|
+
* const { host, authorization } = useHeaders()
|
|
946
|
+
* ```
|
|
947
|
+
*/
|
|
905
948
|
function useHeaders() {
|
|
906
949
|
return useRequest().headers;
|
|
907
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Provides methods to set, get, and remove outgoing response headers.
|
|
953
|
+
* @example
|
|
954
|
+
* ```ts
|
|
955
|
+
* const { setHeader, setContentType, enableCors } = useSetHeaders()
|
|
956
|
+
* setHeader('x-request-id', '123')
|
|
957
|
+
* ```
|
|
958
|
+
*/
|
|
908
959
|
function useSetHeaders() {
|
|
909
960
|
const { store } = useHttpContext();
|
|
910
961
|
const setHeaderStore = store("setHeader");
|
|
@@ -926,6 +977,7 @@ function useSetHeaders() {
|
|
|
926
977
|
enableCors
|
|
927
978
|
};
|
|
928
979
|
}
|
|
980
|
+
/** Returns a hookable accessor for a single outgoing response header by name. */
|
|
929
981
|
function useSetHeader(name) {
|
|
930
982
|
const { store } = useHttpContext();
|
|
931
983
|
const { hook } = store("setHeader");
|
|
@@ -934,6 +986,14 @@ function useSetHeader(name) {
|
|
|
934
986
|
|
|
935
987
|
//#endregion
|
|
936
988
|
//#region packages/event-http/src/composables/cookies.ts
|
|
989
|
+
/**
|
|
990
|
+
* Provides access to parsed request cookies.
|
|
991
|
+
* @example
|
|
992
|
+
* ```ts
|
|
993
|
+
* const { getCookie, rawCookies } = useCookies()
|
|
994
|
+
* const sessionId = getCookie('session_id')
|
|
995
|
+
* ```
|
|
996
|
+
*/
|
|
937
997
|
function useCookies() {
|
|
938
998
|
const { store } = useHttpContext();
|
|
939
999
|
const { cookie } = useHeaders();
|
|
@@ -949,6 +1009,7 @@ function useCookies() {
|
|
|
949
1009
|
getCookie
|
|
950
1010
|
};
|
|
951
1011
|
}
|
|
1012
|
+
/** Provides methods to set, get, remove, and clear outgoing response cookies. */
|
|
952
1013
|
function useSetCookies() {
|
|
953
1014
|
const { store } = useHttpContext();
|
|
954
1015
|
const cookiesStore = store("setCookies");
|
|
@@ -969,6 +1030,7 @@ function useSetCookies() {
|
|
|
969
1030
|
cookies
|
|
970
1031
|
};
|
|
971
1032
|
}
|
|
1033
|
+
/** Returns a hookable accessor for a single outgoing cookie by name. */
|
|
972
1034
|
function useSetCookie(name) {
|
|
973
1035
|
const { setCookie, getCookie } = useSetCookies();
|
|
974
1036
|
const valueHook = attachHook({
|
|
@@ -990,6 +1052,7 @@ function useSetCookie(name) {
|
|
|
990
1052
|
|
|
991
1053
|
//#endregion
|
|
992
1054
|
//#region packages/event-http/src/composables/header-accept.ts
|
|
1055
|
+
/** Provides helpers to check the request's Accept header for supported MIME types. */
|
|
993
1056
|
function useAccept() {
|
|
994
1057
|
const { store } = useHttpContext();
|
|
995
1058
|
const { accept } = useHeaders();
|
|
@@ -1010,6 +1073,14 @@ function useAccept() {
|
|
|
1010
1073
|
|
|
1011
1074
|
//#endregion
|
|
1012
1075
|
//#region packages/event-http/src/composables/header-authorization.ts
|
|
1076
|
+
/**
|
|
1077
|
+
* Provides parsed access to the Authorization header (type, credentials, Basic decoding).
|
|
1078
|
+
* @example
|
|
1079
|
+
* ```ts
|
|
1080
|
+
* const { isBearer, authRawCredentials, basicCredentials } = useAuthorization()
|
|
1081
|
+
* if (isBearer()) { const token = authRawCredentials() }
|
|
1082
|
+
* ```
|
|
1083
|
+
*/
|
|
1013
1084
|
function useAuthorization() {
|
|
1014
1085
|
const { store } = useHttpContext();
|
|
1015
1086
|
const { authorization } = useHeaders();
|
|
@@ -1082,6 +1153,7 @@ const cacheControlFunc = {
|
|
|
1082
1153
|
const renderAge = (v) => convertTime(v, "s").toString();
|
|
1083
1154
|
const renderExpires = (v) => typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString();
|
|
1084
1155
|
const renderPragmaNoCache = (v) => v ? "no-cache" : "";
|
|
1156
|
+
/** Provides helpers to set cache-related response headers (Cache-Control, Expires, Age, Pragma). */
|
|
1085
1157
|
function useSetCacheControl() {
|
|
1086
1158
|
const { setHeader } = useSetHeaders();
|
|
1087
1159
|
const setAge = (value) => {
|
|
@@ -1106,6 +1178,14 @@ function useSetCacheControl() {
|
|
|
1106
1178
|
|
|
1107
1179
|
//#endregion
|
|
1108
1180
|
//#region packages/event-http/src/composables/response.ts
|
|
1181
|
+
/**
|
|
1182
|
+
* Provides access to the raw HTTP response and status code management.
|
|
1183
|
+
* @example
|
|
1184
|
+
* ```ts
|
|
1185
|
+
* const { status, rawResponse, hasResponded } = useResponse()
|
|
1186
|
+
* status(200)
|
|
1187
|
+
* ```
|
|
1188
|
+
*/
|
|
1109
1189
|
function useResponse() {
|
|
1110
1190
|
const { store } = useHttpContext();
|
|
1111
1191
|
const event = store("event");
|
|
@@ -1128,6 +1208,7 @@ function useResponse() {
|
|
|
1128
1208
|
})
|
|
1129
1209
|
};
|
|
1130
1210
|
}
|
|
1211
|
+
/** Returns a hookable accessor for the response status code. */
|
|
1131
1212
|
function useStatus() {
|
|
1132
1213
|
const { store } = useHttpContext();
|
|
1133
1214
|
return store("status").hook("code");
|
|
@@ -1135,6 +1216,11 @@ function useStatus() {
|
|
|
1135
1216
|
|
|
1136
1217
|
//#endregion
|
|
1137
1218
|
//#region packages/event-http/src/utils/url-search-params.ts
|
|
1219
|
+
const ILLEGAL_KEYS = new Set([
|
|
1220
|
+
"__proto__",
|
|
1221
|
+
"constructor",
|
|
1222
|
+
"prototype"
|
|
1223
|
+
]);
|
|
1138
1224
|
var WooksURLSearchParams = class extends URLSearchParams {
|
|
1139
1225
|
toJson() {
|
|
1140
1226
|
const json = Object.create(null);
|
|
@@ -1142,7 +1228,7 @@ var WooksURLSearchParams = class extends URLSearchParams {
|
|
|
1142
1228
|
const a = json[key] = json[key] || [];
|
|
1143
1229
|
a.push(value);
|
|
1144
1230
|
} else {
|
|
1145
|
-
if (key
|
|
1231
|
+
if (ILLEGAL_KEYS.has(key)) throw new HttpError(400, `Illegal key name "${key}"`);
|
|
1146
1232
|
if (key in json) throw new HttpError(400, `Duplicate key "${key}"`);
|
|
1147
1233
|
json[key] = value;
|
|
1148
1234
|
}
|
|
@@ -1155,6 +1241,14 @@ function isArrayParam(name) {
|
|
|
1155
1241
|
|
|
1156
1242
|
//#endregion
|
|
1157
1243
|
//#region packages/event-http/src/composables/search-params.ts
|
|
1244
|
+
/**
|
|
1245
|
+
* Provides access to URL search (query) parameters from the request.
|
|
1246
|
+
* @example
|
|
1247
|
+
* ```ts
|
|
1248
|
+
* const { urlSearchParams, jsonSearchParams } = useSearchParams()
|
|
1249
|
+
* const page = urlSearchParams().get('page')
|
|
1250
|
+
* ```
|
|
1251
|
+
*/
|
|
1158
1252
|
function useSearchParams() {
|
|
1159
1253
|
const { store } = useHttpContext();
|
|
1160
1254
|
const url = useRequest().url || "";
|
|
@@ -1329,7 +1423,7 @@ var BaseHttpResponse = class {
|
|
|
1329
1423
|
async function respondWithFetch(fetchBody, res) {
|
|
1330
1424
|
if (fetchBody) try {
|
|
1331
1425
|
for await (const chunk of fetchBody) res.write(chunk);
|
|
1332
|
-
} catch
|
|
1426
|
+
} catch {}
|
|
1333
1427
|
res.end();
|
|
1334
1428
|
}
|
|
1335
1429
|
|
|
@@ -1357,6 +1451,7 @@ function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRe
|
|
|
1357
1451
|
|
|
1358
1452
|
//#endregion
|
|
1359
1453
|
//#region packages/event-http/src/http-adapter.ts
|
|
1454
|
+
/** HTTP adapter for Wooks that provides route registration, server lifecycle, and request handling. */
|
|
1360
1455
|
var WooksHttp = class extends WooksAdapterBase {
|
|
1361
1456
|
logger;
|
|
1362
1457
|
constructor(opts, wooks) {
|
|
@@ -1364,27 +1459,35 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1364
1459
|
this.opts = opts;
|
|
1365
1460
|
this.logger = opts?.logger || this.getLogger(`[96m[wooks-http]`);
|
|
1366
1461
|
}
|
|
1462
|
+
/** Registers a handler for all HTTP methods on the given path. */
|
|
1367
1463
|
all(path, handler) {
|
|
1368
1464
|
return this.on("*", path, handler);
|
|
1369
1465
|
}
|
|
1466
|
+
/** Registers a GET route handler. */
|
|
1370
1467
|
get(path, handler) {
|
|
1371
1468
|
return this.on("GET", path, handler);
|
|
1372
1469
|
}
|
|
1470
|
+
/** Registers a POST route handler. */
|
|
1373
1471
|
post(path, handler) {
|
|
1374
1472
|
return this.on("POST", path, handler);
|
|
1375
1473
|
}
|
|
1474
|
+
/** Registers a PUT route handler. */
|
|
1376
1475
|
put(path, handler) {
|
|
1377
1476
|
return this.on("PUT", path, handler);
|
|
1378
1477
|
}
|
|
1478
|
+
/** Registers a PATCH route handler. */
|
|
1379
1479
|
patch(path, handler) {
|
|
1380
1480
|
return this.on("PATCH", path, handler);
|
|
1381
1481
|
}
|
|
1482
|
+
/** Registers a DELETE route handler. */
|
|
1382
1483
|
delete(path, handler) {
|
|
1383
1484
|
return this.on("DELETE", path, handler);
|
|
1384
1485
|
}
|
|
1486
|
+
/** Registers a HEAD route handler. */
|
|
1385
1487
|
head(path, handler) {
|
|
1386
1488
|
return this.on("HEAD", path, handler);
|
|
1387
1489
|
}
|
|
1490
|
+
/** Registers an OPTIONS route handler. */
|
|
1388
1491
|
options(path, handler) {
|
|
1389
1492
|
return this.on("OPTIONS", path, handler);
|
|
1390
1493
|
}
|
|
@@ -1442,8 +1545,8 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1442
1545
|
}
|
|
1443
1546
|
responder = createWooksResponder();
|
|
1444
1547
|
respond(data) {
|
|
1445
|
-
this.responder.respond(data)?.catch((
|
|
1446
|
-
this.logger.error("Uncaught response exception",
|
|
1548
|
+
this.responder.respond(data)?.catch((error) => {
|
|
1549
|
+
this.logger.error("Uncaught response exception", error);
|
|
1447
1550
|
});
|
|
1448
1551
|
}
|
|
1449
1552
|
/**
|
|
@@ -1462,7 +1565,8 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1462
1565
|
return (req, res) => {
|
|
1463
1566
|
const runInContext = createHttpContext({
|
|
1464
1567
|
req,
|
|
1465
|
-
res
|
|
1568
|
+
res,
|
|
1569
|
+
requestLimits: this.opts?.requestLimits
|
|
1466
1570
|
}, this.mergeEventOptions(this.opts?.eventOptions));
|
|
1467
1571
|
runInContext(async () => {
|
|
1468
1572
|
const { handlers } = this.wooks.lookup(req.method, req.url);
|
|
@@ -1502,10 +1606,13 @@ var WooksHttp = class extends WooksAdapterBase {
|
|
|
1502
1606
|
}
|
|
1503
1607
|
};
|
|
1504
1608
|
/**
|
|
1505
|
-
*
|
|
1506
|
-
* @
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1609
|
+
* Creates a new WooksHttp application instance.
|
|
1610
|
+
* @example
|
|
1611
|
+
* ```ts
|
|
1612
|
+
* const app = createHttpApp()
|
|
1613
|
+
* app.get('/hello', () => 'Hello World!')
|
|
1614
|
+
* app.listen(3000)
|
|
1615
|
+
* ```
|
|
1509
1616
|
*/
|
|
1510
1617
|
function createHttpApp(opts, wooks) {
|
|
1511
1618
|
return new WooksHttp(opts, wooks);
|
package/package.json
CHANGED
|
@@ -1,56 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wooksjs/event-http",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "@wooksjs/event-http",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"api",
|
|
7
|
+
"app",
|
|
8
|
+
"composables",
|
|
9
|
+
"framework",
|
|
10
|
+
"http",
|
|
11
|
+
"prostojs",
|
|
12
|
+
"rest",
|
|
13
|
+
"restful",
|
|
14
|
+
"web",
|
|
15
|
+
"wooks"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/wooksjs/wooksjs/tree/main/packages/event-http#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/wooksjs/wooksjs/issues"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Artem Maltsev",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/wooksjs/wooksjs.git",
|
|
26
|
+
"directory": "packages/event-http"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"setup-skills": "scripts/setup-skills.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"skills",
|
|
34
|
+
"scripts"
|
|
35
|
+
],
|
|
5
36
|
"main": "dist/index.cjs",
|
|
6
37
|
"module": "dist/index.mjs",
|
|
7
38
|
"types": "dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"dist"
|
|
10
|
-
],
|
|
11
39
|
"exports": {
|
|
12
40
|
"./package.json": "./package.json",
|
|
13
41
|
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
14
43
|
"require": "./dist/index.cjs",
|
|
15
|
-
"import": "./dist/index.mjs"
|
|
16
|
-
"types": "./dist/index.d.ts"
|
|
44
|
+
"import": "./dist/index.mjs"
|
|
17
45
|
}
|
|
18
46
|
},
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"keywords": [
|
|
25
|
-
"http",
|
|
26
|
-
"wooks",
|
|
27
|
-
"composables",
|
|
28
|
-
"web",
|
|
29
|
-
"framework",
|
|
30
|
-
"app",
|
|
31
|
-
"api",
|
|
32
|
-
"rest",
|
|
33
|
-
"restful",
|
|
34
|
-
"prostojs"
|
|
35
|
-
],
|
|
36
|
-
"author": "Artem Maltsev",
|
|
37
|
-
"license": "MIT",
|
|
38
|
-
"bugs": {
|
|
39
|
-
"url": "https://github.com/wooksjs/wooksjs/issues"
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "^5.9.3",
|
|
49
|
+
"vitest": "^3.2.4",
|
|
50
|
+
"@wooksjs/event-core": "^0.6.3",
|
|
51
|
+
"wooks": "^0.6.3"
|
|
40
52
|
},
|
|
41
53
|
"peerDependencies": {
|
|
42
|
-
"@prostojs/router": "^0.2.1",
|
|
43
54
|
"@prostojs/logger": "^0.4.3",
|
|
44
|
-
"@
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
"dependencies": {},
|
|
48
|
-
"homepage": "https://github.com/wooksjs/wooksjs/tree/main/packages/event-http#readme",
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"typescript": "^5.8.3",
|
|
51
|
-
"vitest": "^3.2.4"
|
|
55
|
+
"@prostojs/router": "^0.2.1",
|
|
56
|
+
"@wooksjs/event-core": "^0.6.3",
|
|
57
|
+
"wooks": "^0.6.3"
|
|
52
58
|
},
|
|
53
59
|
"scripts": {
|
|
54
|
-
"build": "rolldown -c ../../rolldown.config.mjs"
|
|
60
|
+
"build": "rolldown -c ../../rolldown.config.mjs",
|
|
61
|
+
"postinstall": "node scripts/setup-skills.js",
|
|
62
|
+
"setup-skills": "node scripts/setup-skills.js"
|
|
55
63
|
}
|
|
56
64
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
/**
|
|
4
|
+
* Copies the event-http skill files into the consuming project's
|
|
5
|
+
* agent-skills directory so that AI coding agents (Claude Code, Cursor,
|
|
6
|
+
* Windsurf, Codex, etc.) can discover and load them.
|
|
7
|
+
*
|
|
8
|
+
* Runs automatically on `postinstall` or manually via:
|
|
9
|
+
* npx @wooksjs/event-http setup-skills
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs')
|
|
13
|
+
const path = require('path')
|
|
14
|
+
|
|
15
|
+
const SKILL_NAME = 'wooksjs-event-http'
|
|
16
|
+
|
|
17
|
+
/* ── locate the project root (first dir above node_modules) ──────── */
|
|
18
|
+
function findProjectRoot() {
|
|
19
|
+
let dir = __dirname
|
|
20
|
+
while (dir !== path.dirname(dir)) {
|
|
21
|
+
dir = path.dirname(dir)
|
|
22
|
+
if (path.basename(dir) === 'node_modules') {
|
|
23
|
+
return path.dirname(dir)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return process.cwd()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const projectRoot = findProjectRoot()
|
|
30
|
+
const srcDir = path.join(__dirname, '..', 'skills', SKILL_NAME)
|
|
31
|
+
const agents = [
|
|
32
|
+
{ dir: '.claude', file: 'SKILL.md' },
|
|
33
|
+
{ dir: '.cursor', file: 'SKILL.md' },
|
|
34
|
+
{ dir: '.windsurf', file: 'SKILL.md' },
|
|
35
|
+
{ dir: '.codex', file: 'SKILL.md' },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
/* ── copy skill files ────────────────────────────────────────────── */
|
|
39
|
+
function copySkills() {
|
|
40
|
+
if (!fs.existsSync(srcDir)) {
|
|
41
|
+
console.log(`[${SKILL_NAME}] skills source not found, skipping.`)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const files = fs.readdirSync(srcDir)
|
|
46
|
+
let copied = 0
|
|
47
|
+
|
|
48
|
+
for (const agent of agents) {
|
|
49
|
+
const destDir = path.join(projectRoot, agent.dir, 'skills', SKILL_NAME)
|
|
50
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
51
|
+
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
const src = path.join(srcDir, file)
|
|
54
|
+
const dest = path.join(destDir, file)
|
|
55
|
+
if (fs.statSync(src).isFile()) {
|
|
56
|
+
fs.copyFileSync(src, dest)
|
|
57
|
+
copied++
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(`[${SKILL_NAME}] copied ${files.length} skill files to ${agents.length} agent dirs (${copied} total).`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
copySkills()
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// Non-fatal — don't break installs
|
|
69
|
+
console.log(`[${SKILL_NAME}] skill setup skipped: ${err.message}`)
|
|
70
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wooksjs-event-http
|
|
3
|
+
description: Wooks HTTP framework — composable, lazy-evaluated HTTP server for Node.js. Load when building HTTP apps or REST APIs with wooks; defining routes or using the wooks router; using use-composables (useRequest, useResponse, useCookies, useHeaders, useBody, useProxy, useSearchParams, useRouteParams, useAuthorization, useSetHeaders, useSetCookies, useStatus, useAccept, useSetCacheControl); creating custom event context composables; working with @wooksjs/event-core context store (init, get, set, hook); serving static files; proxying requests; handling HTTP errors; setting status codes, content types, or cache control.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @wooksjs/event-http
|
|
7
|
+
|
|
8
|
+
A composable HTTP framework for Node.js built on async context (AsyncLocalStorage). Instead of middleware chains and mutated `req`/`res` objects, you call composable functions (`useRequest()`, `useCookies()`, etc.) anywhere in your handler — values are computed on demand and cached per request.
|
|
9
|
+
|
|
10
|
+
## How to use this skill
|
|
11
|
+
|
|
12
|
+
Read the domain file that matches the task. Do not load all files — only what you need.
|
|
13
|
+
|
|
14
|
+
| Domain | File | Load when... |
|
|
15
|
+
|--------|------|------------|
|
|
16
|
+
| Event context (core machinery) | [event-core.md](event-core.md) | Understanding the context store API (`init`/`get`/`set`/`hook`), creating custom composables, lazy evaluation and caching, building your own `use*()` functions |
|
|
17
|
+
| HTTP app setup | [core.md](core.md) | Creating an HTTP app, server lifecycle, `createHttpApp`, `getServerCb`, testing with `prepareTestHttpContext`, logging |
|
|
18
|
+
| Routing | [routing.md](routing.md) | Defining routes, route params (`:id`), wildcards (`*`), regex constraints (`:id(\\d+)`), optional params (`:tab?`), repeated params, path builders, HTTP method shortcuts, handler return values, router config |
|
|
19
|
+
| Request utilities | [request.md](request.md) | `useRequest`, `useHeaders`, `useCookies`, `useSearchParams`, `useAuthorization`, `useAccept`, `useEventId`, reading IP, body limits |
|
|
20
|
+
| Response & status | [response.md](response.md) | `useResponse`, `useStatus`, `useSetHeaders`, `useSetHeader`, `useSetCookies`, `useSetCookie`, `useSetCacheControl`, content type, status hooks, cookie hooks |
|
|
21
|
+
| Error handling | [error-handling.md](error-handling.md) | `HttpError`, throwing errors, custom error bodies, error rendering, guard patterns |
|
|
22
|
+
| Addons (body, static, proxy) | [addons.md](addons.md) | `useBody` (body parsing), `serveFile` (static files), `useProxy` (request proxying) |
|
|
23
|
+
|
|
24
|
+
## Quick reference
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createHttpApp, useRouteParams } from '@wooksjs/event-http'
|
|
28
|
+
|
|
29
|
+
const app = createHttpApp()
|
|
30
|
+
app.get('/hello/:name', () => {
|
|
31
|
+
const { get } = useRouteParams<{ name: string }>()
|
|
32
|
+
return { greeting: `Hello ${get('name')}!` }
|
|
33
|
+
})
|
|
34
|
+
app.listen(3000)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Key composables: `useRequest()`, `useResponse()`, `useRouteParams()`, `useHeaders()`, `useSetHeaders()`, `useCookies()`, `useSetCookies()`, `useSearchParams()`, `useAuthorization()`, `useAccept()`, `useSetCacheControl()`, `useStatus()`, `useBody()`, `useProxy()`.
|