@wooksjs/event-http 0.6.2 → 0.6.4

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/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 (error) {
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().reverse()) {
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).reverse()) out = await compressors[enc].stream.uncompress(out);
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: -apple-system, BlinkMacMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
436
- Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
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.1",
677
+ version: "0.6.3",
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, get, set } = store("request");
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 getMaxCompressed = () => get("maxCompressed") ?? DEFAULT_LIMITS.maxCompressed;
800
- const setMaxCompressed = (limit) => set("maxCompressed", limit);
801
- const getReadTimeoutMs = () => get("readTimeoutMs") ?? DEFAULT_LIMITS.readTimeoutMs;
802
- const setReadTimeoutMs = (limit) => set("readTimeoutMs", limit);
803
- const getMaxInflated = () => get("maxInflated") ?? DEFAULT_LIMITS.maxInflated;
804
- const setMaxInflated = (limit) => set("maxInflated", limit);
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 === "__proto__") throw new HttpError(400, `Illegal key name "${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 (error) {}
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(`[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((e) => {
1446
- this.logger.error("Uncaught response exception", e);
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
- * Factory for WooksHttp App
1506
- * @param opts TWooksHttpOptions
1507
- * @param wooks Wooks | WooksAdapterBase
1508
- * @returns WooksHttp
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.2",
3
+ "version": "0.6.4",
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
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/wooksjs/wooksjs.git",
22
- "directory": "packages/event-http"
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.4",
51
+ "wooks": "^0.6.4"
40
52
  },
41
53
  "peerDependencies": {
42
- "@prostojs/router": "^0.2.1",
43
54
  "@prostojs/logger": "^0.4.3",
44
- "@wooksjs/event-core": "^0.6.2",
45
- "wooks": "^0.6.2"
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.4",
57
+ "wooks": "^0.6.4"
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,77 @@
1
+ #!/usr/bin/env node
2
+ /* prettier-ignore */
3
+ 'use strict'
4
+
5
+ const fs = require('fs')
6
+ const path = require('path')
7
+ const os = require('os')
8
+
9
+ const SKILL_NAME = 'wooksjs-event-http'
10
+ const SKILL_SRC = path.join(__dirname, '..', 'skills', SKILL_NAME)
11
+
12
+ if (!fs.existsSync(SKILL_SRC)) {
13
+ console.error(`No skills found at ${SKILL_SRC}`)
14
+ console.error('Add your SKILL.md files to the skills/' + SKILL_NAME + '/ directory first.')
15
+ process.exit(1)
16
+ }
17
+
18
+ const AGENTS = {
19
+ 'Claude Code': { dir: '.claude/skills', global: path.join(os.homedir(), '.claude', 'skills') },
20
+ 'Cursor': { dir: '.cursor/skills', global: path.join(os.homedir(), '.cursor', 'skills') },
21
+ 'Windsurf': { dir: '.windsurf/skills', global: path.join(os.homedir(), '.windsurf', 'skills') },
22
+ 'Codex': { dir: '.codex/skills', global: path.join(os.homedir(), '.codex', 'skills') },
23
+ 'OpenCode': { dir: '.opencode/skills', global: path.join(os.homedir(), '.opencode', 'skills') },
24
+ }
25
+
26
+ const args = process.argv.slice(2)
27
+ const isGlobal = args.includes('--global') || args.includes('-g')
28
+ const isPostinstall = args.includes('--postinstall')
29
+ let installed = 0, skipped = 0
30
+ const installedDirs = []
31
+
32
+ for (const [agentName, cfg] of Object.entries(AGENTS)) {
33
+ const targetBase = isGlobal ? cfg.global : path.join(process.cwd(), cfg.dir)
34
+ const agentRootDir = path.dirname(cfg.global) // Check if the agent has ever been installed globally
35
+
36
+ // In postinstall mode: silently skip agents that aren't set up globally
37
+ if (isPostinstall || isGlobal) {
38
+ if (!fs.existsSync(agentRootDir)) { skipped++; continue }
39
+ }
40
+
41
+ const dest = path.join(targetBase, SKILL_NAME)
42
+ try {
43
+ fs.mkdirSync(dest, { recursive: true })
44
+ fs.cpSync(SKILL_SRC, dest, { recursive: true })
45
+ console.log(`[${SKILL_NAME}] installed to ${dest}`)
46
+ installed++
47
+ if (!isGlobal) installedDirs.push(cfg.dir + '/' + SKILL_NAME)
48
+ } catch (err) {
49
+ console.warn(`[${SKILL_NAME}] failed — ${err.message}`)
50
+ }
51
+ }
52
+
53
+ // Add locally-installed skill dirs to .gitignore
54
+ if (!isGlobal && installedDirs.length > 0) {
55
+ const gitignorePath = path.join(process.cwd(), '.gitignore')
56
+ let gitignoreContent = ''
57
+ try { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8') } catch {}
58
+ const linesToAdd = installedDirs.filter(d => !gitignoreContent.includes(d))
59
+ if (linesToAdd.length > 0) {
60
+ const hasHeader = gitignoreContent.includes('# AI agent skills')
61
+ const block = (gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : '')
62
+ + (hasHeader ? '' : '\n# AI agent skills (auto-generated by setup-skills)\n')
63
+ + linesToAdd.join('\n') + '\n'
64
+ fs.appendFileSync(gitignorePath, block)
65
+ console.log(`[${SKILL_NAME}] added entries to .gitignore`)
66
+ }
67
+ }
68
+
69
+ if (installed === 0 && isPostinstall) {
70
+ // Silence is fine — no agents present, nothing to do
71
+ } else if (installed === 0 && skipped === Object.keys(AGENTS).length) {
72
+ console.log('No agent directories detected. Try --global or run without it for project-local install.')
73
+ } else if (installed === 0) {
74
+ console.log('Nothing installed. Run without --global to install project-locally.')
75
+ } else {
76
+ console.log(`Done! Restart your AI agent to pick up the "${SKILL_NAME}" skill.`)
77
+ }
@@ -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()`.