shokupan 0.7.0 → 0.9.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 +53 -0
- package/dist/context.d.ts +50 -15
- package/dist/{http-server-DFhwlK8e.cjs → http-server-BEMPIs33.cjs} +4 -2
- package/dist/http-server-BEMPIs33.cjs.map +1 -0
- package/dist/{http-server-0xH174zz.js → http-server-CCeagTyU.js} +4 -2
- package/dist/http-server-CCeagTyU.js.map +1 -0
- package/dist/index.cjs +998 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +996 -135
- package/dist/index.js.map +1 -1
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +12 -0
- package/dist/plugins/application/dashboard/plugin.d.ts +14 -8
- package/dist/plugins/application/dashboard/static/charts.js +328 -0
- package/dist/plugins/application/dashboard/static/failures.js +85 -0
- package/dist/plugins/application/dashboard/static/graph.mjs +523 -0
- package/dist/plugins/application/dashboard/static/poll.js +146 -0
- package/dist/plugins/application/dashboard/static/reactflow.css +18 -0
- package/dist/plugins/application/dashboard/static/registry.css +131 -0
- package/dist/plugins/application/dashboard/static/registry.js +269 -0
- package/dist/plugins/application/dashboard/static/requests.js +118 -0
- package/dist/plugins/application/dashboard/static/scrollbar.css +24 -0
- package/dist/plugins/application/dashboard/static/styles.css +175 -0
- package/dist/plugins/application/dashboard/static/tables.js +92 -0
- package/dist/plugins/application/dashboard/static/tabs.js +113 -0
- package/dist/plugins/application/dashboard/static/tabulator.css +66 -0
- package/dist/plugins/application/dashboard/template.eta +246 -0
- package/dist/plugins/application/socket-io.d.ts +14 -0
- package/dist/router.d.ts +12 -0
- package/dist/shokupan.d.ts +21 -1
- package/dist/util/datastore.d.ts +4 -3
- package/dist/util/decorators.d.ts +5 -0
- package/dist/util/http-error.d.ts +38 -0
- package/dist/util/http-status.d.ts +30 -0
- package/dist/util/request.d.ts +1 -1
- package/dist/util/symbol.d.ts +19 -0
- package/dist/util/types.d.ts +30 -1
- package/package.json +6 -3
- package/dist/http-server-0xH174zz.js.map +0 -1
- package/dist/http-server-DFhwlK8e.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -22,9 +22,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
mod
|
|
23
23
|
));
|
|
24
24
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
25
|
+
const nanoid = require("nanoid");
|
|
25
26
|
const promises = require("node:fs/promises");
|
|
26
27
|
const api = require("@opentelemetry/api");
|
|
27
28
|
const node_async_hooks = require("node:async_hooks");
|
|
29
|
+
const surrealdb = require("surrealdb");
|
|
28
30
|
const eta$2 = require("eta");
|
|
29
31
|
const promises$1 = require("fs/promises");
|
|
30
32
|
const path = require("path");
|
|
@@ -33,12 +35,16 @@ const arctic = require("arctic");
|
|
|
33
35
|
const jose = require("jose");
|
|
34
36
|
const cluster = require("node:cluster");
|
|
35
37
|
const net = require("node:net");
|
|
38
|
+
const path$1 = require("node:path");
|
|
39
|
+
const node_url = require("node:url");
|
|
40
|
+
const node_perf_hooks = require("node:perf_hooks");
|
|
36
41
|
const analyzer = require("./analyzer-Bei1sVWp.cjs");
|
|
37
42
|
const zlib = require("node:zlib");
|
|
38
43
|
const Ajv = require("ajv");
|
|
39
44
|
const addFormats = require("ajv-formats");
|
|
40
45
|
const crypto = require("crypto");
|
|
41
46
|
const events = require("events");
|
|
47
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
42
48
|
function _interopNamespaceDefault(e) {
|
|
43
49
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
44
50
|
if (e) {
|
|
@@ -58,6 +64,36 @@ function _interopNamespaceDefault(e) {
|
|
|
58
64
|
const os__namespace = /* @__PURE__ */ _interopNamespaceDefault(os);
|
|
59
65
|
const jose__namespace = /* @__PURE__ */ _interopNamespaceDefault(jose);
|
|
60
66
|
const zlib__namespace = /* @__PURE__ */ _interopNamespaceDefault(zlib);
|
|
67
|
+
const HTTP_STATUS = {
|
|
68
|
+
// 2xx Success
|
|
69
|
+
OK: 200,
|
|
70
|
+
CREATED: 201,
|
|
71
|
+
ACCEPTED: 202,
|
|
72
|
+
NO_CONTENT: 204,
|
|
73
|
+
// 3xx Redirection
|
|
74
|
+
MOVED_PERMANENTLY: 301,
|
|
75
|
+
FOUND: 302,
|
|
76
|
+
SEE_OTHER: 303,
|
|
77
|
+
NOT_MODIFIED: 304,
|
|
78
|
+
TEMPORARY_REDIRECT: 307,
|
|
79
|
+
PERMANENT_REDIRECT: 308,
|
|
80
|
+
// 4xx Client Errors
|
|
81
|
+
BAD_REQUEST: 400,
|
|
82
|
+
UNAUTHORIZED: 401,
|
|
83
|
+
FORBIDDEN: 403,
|
|
84
|
+
NOT_FOUND: 404,
|
|
85
|
+
METHOD_NOT_ALLOWED: 405,
|
|
86
|
+
REQUEST_TIMEOUT: 408,
|
|
87
|
+
CONFLICT: 409,
|
|
88
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
89
|
+
TOO_MANY_REQUESTS: 429,
|
|
90
|
+
// 5xx Server Errors
|
|
91
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
92
|
+
NOT_IMPLEMENTED: 501,
|
|
93
|
+
BAD_GATEWAY: 502,
|
|
94
|
+
SERVICE_UNAVAILABLE: 503,
|
|
95
|
+
GATEWAY_TIMEOUT: 504
|
|
96
|
+
};
|
|
61
97
|
const VALID_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
62
98
|
100,
|
|
63
99
|
101,
|
|
@@ -187,6 +223,40 @@ class ShokupanResponse {
|
|
|
187
223
|
return this._headers !== null;
|
|
188
224
|
}
|
|
189
225
|
}
|
|
226
|
+
const $isApplication = /* @__PURE__ */ Symbol.for("Shokupan.app");
|
|
227
|
+
const $appRoot = /* @__PURE__ */ Symbol.for("Shokupan.app-root");
|
|
228
|
+
const $isMounted = /* @__PURE__ */ Symbol("Shokupan.isMounted");
|
|
229
|
+
const $routeMethods = /* @__PURE__ */ Symbol("Shokupan.routeMethods");
|
|
230
|
+
const $eventMethods = /* @__PURE__ */ Symbol("Shokupan.eventMethods");
|
|
231
|
+
const $routeArgs = /* @__PURE__ */ Symbol("Shokupan.routeArgs");
|
|
232
|
+
const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
|
|
233
|
+
const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
|
|
234
|
+
const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
|
|
235
|
+
const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
|
|
236
|
+
const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
|
|
237
|
+
const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
|
|
238
|
+
const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
|
|
239
|
+
const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
|
|
240
|
+
const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
|
|
241
|
+
const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
|
|
242
|
+
const $url = /* @__PURE__ */ Symbol.for("Shokupan.ctx.url");
|
|
243
|
+
const $requestId = /* @__PURE__ */ Symbol.for("Shokupan.ctx.requestId");
|
|
244
|
+
const $debug = /* @__PURE__ */ Symbol.for("Shokupan.ctx.debug");
|
|
245
|
+
const $finalResponse = /* @__PURE__ */ Symbol.for("Shokupan.ctx.finalResponse");
|
|
246
|
+
const $rawBody = /* @__PURE__ */ Symbol.for("Shokupan.ctx.rawBody");
|
|
247
|
+
const $cachedBody = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedBody");
|
|
248
|
+
const $bodyType = /* @__PURE__ */ Symbol.for("Shokupan.ctx.bodyType");
|
|
249
|
+
const $bodyParsed = /* @__PURE__ */ Symbol.for("Shokupan.ctx.bodyParsed");
|
|
250
|
+
const $bodyParseError = /* @__PURE__ */ Symbol.for("Shokupan.ctx.bodyParseError");
|
|
251
|
+
const $routeMatched = /* @__PURE__ */ Symbol.for("Shokupan.ctx.routeMatched");
|
|
252
|
+
const $cachedHostname = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedHostname");
|
|
253
|
+
const $cachedProtocol = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedProtocol");
|
|
254
|
+
const $cachedHost = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedHost");
|
|
255
|
+
const $cachedOrigin = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedOrigin");
|
|
256
|
+
const $cachedQuery = /* @__PURE__ */ Symbol.for("Shokupan.ctx.cachedQuery");
|
|
257
|
+
const $ws = /* @__PURE__ */ Symbol.for("Shokupan.ctx.ws");
|
|
258
|
+
const $socket = /* @__PURE__ */ Symbol.for("Shokupan.ctx.socket");
|
|
259
|
+
const $io = /* @__PURE__ */ Symbol.for("Shokupan.ctx.io");
|
|
190
260
|
function isValidCookieDomain(domain, currentHost) {
|
|
191
261
|
const hostWithoutPort = currentHost.split(":")[0];
|
|
192
262
|
if (domain === hostWithoutPort) return true;
|
|
@@ -224,23 +294,26 @@ class ShokupanContext {
|
|
|
224
294
|
state;
|
|
225
295
|
handlerStack = [];
|
|
226
296
|
response;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
297
|
+
[$debug];
|
|
298
|
+
[$finalResponse];
|
|
299
|
+
[$rawBody];
|
|
230
300
|
// Raw body for compression optimization
|
|
231
301
|
// Body caching to avoid double parsing
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
302
|
+
[$url];
|
|
303
|
+
[$cachedBody];
|
|
304
|
+
[$bodyType];
|
|
305
|
+
[$bodyParsed] = false;
|
|
306
|
+
[$bodyParseError];
|
|
307
|
+
[$routeMatched] = false;
|
|
238
308
|
// Cached URL properties to avoid repeated parsing
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
309
|
+
[$cachedHostname];
|
|
310
|
+
[$cachedProtocol];
|
|
311
|
+
[$cachedHost];
|
|
312
|
+
[$cachedOrigin];
|
|
313
|
+
[$cachedQuery];
|
|
314
|
+
[$ws];
|
|
315
|
+
[$socket];
|
|
316
|
+
[$io];
|
|
244
317
|
/**
|
|
245
318
|
* JSX Rendering Function
|
|
246
319
|
*/
|
|
@@ -248,12 +321,16 @@ class ShokupanContext {
|
|
|
248
321
|
setRenderer(renderer) {
|
|
249
322
|
this.renderer = renderer;
|
|
250
323
|
}
|
|
324
|
+
[$requestId];
|
|
325
|
+
get requestId() {
|
|
326
|
+
return this[$requestId] ??= this.app?.applicationConfig?.idGenerator?.() ?? nanoid.nanoid();
|
|
327
|
+
}
|
|
251
328
|
get url() {
|
|
252
|
-
if (!this
|
|
329
|
+
if (!this[$url]) {
|
|
253
330
|
const urlString = this.request.url || "http://localhost/";
|
|
254
|
-
this
|
|
331
|
+
this[$url] = new URL(urlString);
|
|
255
332
|
}
|
|
256
|
-
return this
|
|
333
|
+
return this[$url];
|
|
257
334
|
}
|
|
258
335
|
/**
|
|
259
336
|
* Base request
|
|
@@ -271,7 +348,7 @@ class ShokupanContext {
|
|
|
271
348
|
* Request path
|
|
272
349
|
*/
|
|
273
350
|
get path() {
|
|
274
|
-
if (this
|
|
351
|
+
if (this[$url]) return this[$url].pathname;
|
|
275
352
|
const url = this.request.url;
|
|
276
353
|
let queryIndex = url.indexOf("?");
|
|
277
354
|
const end = queryIndex === -1 ? url.length : queryIndex;
|
|
@@ -296,7 +373,7 @@ class ShokupanContext {
|
|
|
296
373
|
* Request query params
|
|
297
374
|
*/
|
|
298
375
|
get query() {
|
|
299
|
-
if (this
|
|
376
|
+
if (this[$cachedQuery]) return this[$cachedQuery];
|
|
300
377
|
const q = /* @__PURE__ */ Object.create(null);
|
|
301
378
|
const blocklist = ["__proto__", "constructor", "prototype"];
|
|
302
379
|
const entries = Object.entries(this.url.searchParams);
|
|
@@ -313,7 +390,7 @@ class ShokupanContext {
|
|
|
313
390
|
q[key] = value;
|
|
314
391
|
}
|
|
315
392
|
}
|
|
316
|
-
this
|
|
393
|
+
this[$cachedQuery] = q;
|
|
317
394
|
return q;
|
|
318
395
|
}
|
|
319
396
|
/**
|
|
@@ -326,19 +403,19 @@ class ShokupanContext {
|
|
|
326
403
|
* Request hostname (e.g. "localhost")
|
|
327
404
|
*/
|
|
328
405
|
get hostname() {
|
|
329
|
-
return this
|
|
406
|
+
return this[$cachedHostname] ??= this.url.hostname;
|
|
330
407
|
}
|
|
331
408
|
/**
|
|
332
409
|
* Request host (e.g. "localhost:3000")
|
|
333
410
|
*/
|
|
334
411
|
get host() {
|
|
335
|
-
return this
|
|
412
|
+
return this[$cachedHost] ??= this.url.host;
|
|
336
413
|
}
|
|
337
414
|
/**
|
|
338
415
|
* Request protocol (e.g. "http:", "https:")
|
|
339
416
|
*/
|
|
340
417
|
get protocol() {
|
|
341
|
-
return this
|
|
418
|
+
return this[$cachedProtocol] ??= this.url.protocol;
|
|
342
419
|
}
|
|
343
420
|
/**
|
|
344
421
|
* Whether request is secure (https)
|
|
@@ -350,7 +427,7 @@ class ShokupanContext {
|
|
|
350
427
|
* Request origin (e.g. "http://localhost:3000")
|
|
351
428
|
*/
|
|
352
429
|
get origin() {
|
|
353
|
-
return this
|
|
430
|
+
return this[$cachedOrigin] ??= this.url.origin;
|
|
354
431
|
}
|
|
355
432
|
/**
|
|
356
433
|
* Request headers
|
|
@@ -371,6 +448,24 @@ class ShokupanContext {
|
|
|
371
448
|
get res() {
|
|
372
449
|
return this.response;
|
|
373
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Raw WebSocket connection
|
|
453
|
+
*/
|
|
454
|
+
get ws() {
|
|
455
|
+
return this[$ws];
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Socket.io socket
|
|
459
|
+
*/
|
|
460
|
+
get socket() {
|
|
461
|
+
return this[$socket];
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Socket.io server
|
|
465
|
+
*/
|
|
466
|
+
get io() {
|
|
467
|
+
return this[$io];
|
|
468
|
+
}
|
|
374
469
|
/**
|
|
375
470
|
* Helper to set a header on the response
|
|
376
471
|
* @param key Header key
|
|
@@ -380,6 +475,20 @@ class ShokupanContext {
|
|
|
380
475
|
this.response.set(key, value);
|
|
381
476
|
return this;
|
|
382
477
|
}
|
|
478
|
+
isUpgraded = false;
|
|
479
|
+
/**
|
|
480
|
+
* Upgrades the request to a WebSocket connection.
|
|
481
|
+
* @param options Upgrade options
|
|
482
|
+
* @returns true if upgraded, false otherwise
|
|
483
|
+
*/
|
|
484
|
+
upgrade(options) {
|
|
485
|
+
if (!this.server) return false;
|
|
486
|
+
const success = this.server.upgrade(this.req, options);
|
|
487
|
+
if (success) {
|
|
488
|
+
this.isUpgraded = true;
|
|
489
|
+
}
|
|
490
|
+
return success;
|
|
491
|
+
}
|
|
383
492
|
/**
|
|
384
493
|
* Set a cookie
|
|
385
494
|
* @param name Cookie name
|
|
@@ -457,18 +566,18 @@ class ShokupanContext {
|
|
|
457
566
|
* The body is only parsed once and cached for subsequent reads.
|
|
458
567
|
*/
|
|
459
568
|
async body() {
|
|
460
|
-
if (this
|
|
461
|
-
throw this
|
|
569
|
+
if (this[$bodyParseError]) {
|
|
570
|
+
throw this[$bodyParseError];
|
|
462
571
|
}
|
|
463
|
-
if (this
|
|
464
|
-
return this
|
|
572
|
+
if (this[$bodyParsed]) {
|
|
573
|
+
return this[$cachedBody];
|
|
465
574
|
}
|
|
466
575
|
const contentType = this.request.headers.get("content-type") || "";
|
|
467
576
|
if (contentType.includes("application/json") || contentType.includes("+json")) {
|
|
468
577
|
const parserType = this.app?.applicationConfig?.jsonParser || "native";
|
|
469
578
|
if (parserType === "native") {
|
|
470
579
|
try {
|
|
471
|
-
this
|
|
580
|
+
this[$cachedBody] = await this.request.json();
|
|
472
581
|
} catch (e) {
|
|
473
582
|
throw e;
|
|
474
583
|
}
|
|
@@ -476,18 +585,18 @@ class ShokupanContext {
|
|
|
476
585
|
const rawText = await this.request.text();
|
|
477
586
|
const { getJSONParser } = await Promise.resolve().then(() => require("./json-parser-COdZ0fqY.cjs"));
|
|
478
587
|
const parser = getJSONParser(parserType);
|
|
479
|
-
this
|
|
588
|
+
this[$cachedBody] = parser(rawText);
|
|
480
589
|
}
|
|
481
|
-
this
|
|
590
|
+
this[$bodyType] = "json";
|
|
482
591
|
} else if (contentType.includes("multipart/form-data") || contentType.includes("application/x-www-form-urlencoded")) {
|
|
483
|
-
this
|
|
484
|
-
this
|
|
592
|
+
this[$cachedBody] = await this.request.formData();
|
|
593
|
+
this[$bodyType] = "formData";
|
|
485
594
|
} else {
|
|
486
|
-
this
|
|
487
|
-
this
|
|
595
|
+
this[$cachedBody] = await this.request.text();
|
|
596
|
+
this[$bodyType] = "text";
|
|
488
597
|
}
|
|
489
|
-
this
|
|
490
|
-
return this
|
|
598
|
+
this[$bodyParsed] = true;
|
|
599
|
+
return this[$cachedBody];
|
|
491
600
|
}
|
|
492
601
|
/**
|
|
493
602
|
* Pre-parse the request body before handler execution.
|
|
@@ -495,7 +604,7 @@ class ShokupanContext {
|
|
|
495
604
|
* Errors are deferred until the body is actually accessed in the handler.
|
|
496
605
|
*/
|
|
497
606
|
async parseBody() {
|
|
498
|
-
if (this
|
|
607
|
+
if (this[$bodyParsed]) {
|
|
499
608
|
return;
|
|
500
609
|
}
|
|
501
610
|
if (this.request.method === "GET" || this.request.method === "HEAD") {
|
|
@@ -504,7 +613,7 @@ class ShokupanContext {
|
|
|
504
613
|
try {
|
|
505
614
|
await this.body();
|
|
506
615
|
} catch (error) {
|
|
507
|
-
this
|
|
616
|
+
this[$bodyParseError] = error;
|
|
508
617
|
}
|
|
509
618
|
}
|
|
510
619
|
/**
|
|
@@ -554,10 +663,21 @@ class ShokupanContext {
|
|
|
554
663
|
throw new Error(`Invalid HTTP status code: ${status}`);
|
|
555
664
|
}
|
|
556
665
|
if (typeof body === "string" || body instanceof ArrayBuffer || body instanceof Uint8Array) {
|
|
557
|
-
this
|
|
666
|
+
this[$rawBody] = body;
|
|
667
|
+
}
|
|
668
|
+
return this[$finalResponse] ??= new Response(body, { status, headers });
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Emit an event to the client (WebSocket only)
|
|
672
|
+
* @param event Event name
|
|
673
|
+
* @param data Event data (Must be JSON serializable)
|
|
674
|
+
*/
|
|
675
|
+
emit(event, data) {
|
|
676
|
+
if (this[$ws]) {
|
|
677
|
+
this[$ws].send(JSON.stringify({ event, data }));
|
|
678
|
+
} else if (this[$socket]) {
|
|
679
|
+
this[$socket].emit(event, data);
|
|
558
680
|
}
|
|
559
|
-
this._finalResponse = new Response(body, { status, headers });
|
|
560
|
-
return this._finalResponse;
|
|
561
681
|
}
|
|
562
682
|
/**
|
|
563
683
|
* Respond with a JSON object
|
|
@@ -568,18 +688,18 @@ class ShokupanContext {
|
|
|
568
688
|
throw new Error(`Invalid HTTP status code: ${finalStatus}`);
|
|
569
689
|
}
|
|
570
690
|
const jsonString = JSON.stringify(data);
|
|
571
|
-
this
|
|
691
|
+
this[$rawBody] = jsonString;
|
|
572
692
|
if (!headers && !this.response.hasPopulatedHeaders) {
|
|
573
|
-
this
|
|
693
|
+
this[$finalResponse] = new Response(jsonString, {
|
|
574
694
|
status: finalStatus,
|
|
575
695
|
headers: { "content-type": "application/json" }
|
|
576
696
|
});
|
|
577
|
-
return this
|
|
697
|
+
return this[$finalResponse];
|
|
578
698
|
}
|
|
579
699
|
const finalHeaders = this.mergeHeaders(headers);
|
|
580
700
|
finalHeaders.set("content-type", "application/json");
|
|
581
|
-
this
|
|
582
|
-
return this
|
|
701
|
+
this[$finalResponse] = new Response(jsonString, { status: finalStatus, headers: finalHeaders });
|
|
702
|
+
return this[$finalResponse];
|
|
583
703
|
}
|
|
584
704
|
/**
|
|
585
705
|
* Respond with a text string
|
|
@@ -589,18 +709,18 @@ class ShokupanContext {
|
|
|
589
709
|
if (this.app.applicationConfig.validateStatusCodes && !VALID_HTTP_STATUSES.has(finalStatus)) {
|
|
590
710
|
throw new Error(`Invalid HTTP status code: ${finalStatus}`);
|
|
591
711
|
}
|
|
592
|
-
this
|
|
712
|
+
this[$rawBody] = data;
|
|
593
713
|
if (!headers && !this.response.hasPopulatedHeaders) {
|
|
594
|
-
this
|
|
714
|
+
this[$finalResponse] = new Response(data, {
|
|
595
715
|
status: finalStatus,
|
|
596
716
|
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
597
717
|
});
|
|
598
|
-
return this
|
|
718
|
+
return this[$finalResponse];
|
|
599
719
|
}
|
|
600
720
|
const finalHeaders = this.mergeHeaders(headers);
|
|
601
721
|
finalHeaders.set("content-type", "text/plain; charset=utf-8");
|
|
602
|
-
this
|
|
603
|
-
return this
|
|
722
|
+
this[$finalResponse] = new Response(data, { status: finalStatus, headers: finalHeaders });
|
|
723
|
+
return this[$finalResponse];
|
|
604
724
|
}
|
|
605
725
|
/**
|
|
606
726
|
* Respond with HTML content
|
|
@@ -612,9 +732,9 @@ class ShokupanContext {
|
|
|
612
732
|
}
|
|
613
733
|
const finalHeaders = this.mergeHeaders(headers);
|
|
614
734
|
finalHeaders.set("content-type", "text/html; charset=utf-8");
|
|
615
|
-
this
|
|
616
|
-
this
|
|
617
|
-
return this
|
|
735
|
+
this[$rawBody] = html;
|
|
736
|
+
this[$finalResponse] = new Response(html, { status: finalStatus, headers: finalHeaders });
|
|
737
|
+
return this[$finalResponse];
|
|
618
738
|
}
|
|
619
739
|
/**
|
|
620
740
|
* Respond with a redirect
|
|
@@ -625,8 +745,8 @@ class ShokupanContext {
|
|
|
625
745
|
}
|
|
626
746
|
const headers = this.mergeHeaders();
|
|
627
747
|
headers.set("Location", url);
|
|
628
|
-
this
|
|
629
|
-
return this
|
|
748
|
+
this[$finalResponse] = new Response(null, { status, headers });
|
|
749
|
+
return this[$finalResponse];
|
|
630
750
|
}
|
|
631
751
|
/**
|
|
632
752
|
* Respond with a status code
|
|
@@ -637,8 +757,8 @@ class ShokupanContext {
|
|
|
637
757
|
throw new Error(`Invalid HTTP status code: ${status}`);
|
|
638
758
|
}
|
|
639
759
|
const headers = this.mergeHeaders();
|
|
640
|
-
this
|
|
641
|
-
return this
|
|
760
|
+
this[$finalResponse] = new Response(null, { status, headers });
|
|
761
|
+
return this[$finalResponse];
|
|
642
762
|
}
|
|
643
763
|
/**
|
|
644
764
|
* Respond with a file
|
|
@@ -650,15 +770,15 @@ class ShokupanContext {
|
|
|
650
770
|
throw new Error(`Invalid HTTP status code: ${status}`);
|
|
651
771
|
}
|
|
652
772
|
if (typeof Bun !== "undefined") {
|
|
653
|
-
this
|
|
654
|
-
return this
|
|
773
|
+
this[$finalResponse] = new Response(Bun.file(path2, fileOptions), { status, headers });
|
|
774
|
+
return this[$finalResponse];
|
|
655
775
|
} else {
|
|
656
776
|
const fileBuffer = await promises.readFile(path2);
|
|
657
777
|
if (fileOptions?.type) {
|
|
658
778
|
headers.set("content-type", fileOptions.type);
|
|
659
779
|
}
|
|
660
|
-
this
|
|
661
|
-
return this
|
|
780
|
+
this[$finalResponse] = new Response(fileBuffer, { status, headers });
|
|
781
|
+
return this[$finalResponse];
|
|
662
782
|
}
|
|
663
783
|
}
|
|
664
784
|
/**
|
|
@@ -694,10 +814,10 @@ const compose = (middleware) => {
|
|
|
694
814
|
return next ? next() : Promise.resolve();
|
|
695
815
|
}
|
|
696
816
|
const fn = middleware[i];
|
|
697
|
-
if (!context
|
|
817
|
+
if (!context[$debug]) {
|
|
698
818
|
return fn(context, () => runner(i + 1));
|
|
699
819
|
}
|
|
700
|
-
const debug = context
|
|
820
|
+
const debug = context[$debug];
|
|
701
821
|
const debugId = fn._debugId || fn.name || "anonymous";
|
|
702
822
|
const previousNode = debug.getCurrentNode();
|
|
703
823
|
debug.trackEdge(previousNode, debugId);
|
|
@@ -773,21 +893,6 @@ function deepMerge(target, ...sources) {
|
|
|
773
893
|
}
|
|
774
894
|
return deepMerge(target, ...sources);
|
|
775
895
|
}
|
|
776
|
-
const $isApplication = /* @__PURE__ */ Symbol.for("Shokupan.app");
|
|
777
|
-
const $appRoot = /* @__PURE__ */ Symbol.for("Shokupan.app-root");
|
|
778
|
-
const $isMounted = /* @__PURE__ */ Symbol("Shokupan.isMounted");
|
|
779
|
-
const $routeMethods = /* @__PURE__ */ Symbol("Shokupan.routeMethods");
|
|
780
|
-
const $routeArgs = /* @__PURE__ */ Symbol("Shokupan.routeArgs");
|
|
781
|
-
const $controllerPath = /* @__PURE__ */ Symbol("Shokupan.controllerPath");
|
|
782
|
-
const $middleware = /* @__PURE__ */ Symbol("Shokupan.middleware");
|
|
783
|
-
const $isRouter = /* @__PURE__ */ Symbol.for("Shokupan.router");
|
|
784
|
-
const $parent = /* @__PURE__ */ Symbol.for("Shokupan.parent");
|
|
785
|
-
const $childRouters = /* @__PURE__ */ Symbol.for("Shokupan.child-routers");
|
|
786
|
-
const $childControllers = /* @__PURE__ */ Symbol.for("Shokupan.child-controllers");
|
|
787
|
-
const $mountPath = /* @__PURE__ */ Symbol.for("Shokupan.mount-path");
|
|
788
|
-
const $dispatch = /* @__PURE__ */ Symbol.for("Shokupan.dispatch");
|
|
789
|
-
const $routes = /* @__PURE__ */ Symbol.for("Shokupan.routes");
|
|
790
|
-
const $routeSpec = /* @__PURE__ */ Symbol.for("Shokupan.routeSpec");
|
|
791
896
|
const REGEX_PATTERNS = {
|
|
792
897
|
QUERY_INT: /parseInt\(ctx\.query\.(\w+)\)/g,
|
|
793
898
|
QUERY_FLOAT: /parseFloat\(ctx\.query\.(\w+)\)/g,
|
|
@@ -1174,6 +1279,35 @@ class RequestContextStore {
|
|
|
1174
1279
|
span;
|
|
1175
1280
|
}
|
|
1176
1281
|
const asyncContext = new node_async_hooks.AsyncLocalStorage();
|
|
1282
|
+
class HttpError extends Error {
|
|
1283
|
+
status;
|
|
1284
|
+
constructor(message, status) {
|
|
1285
|
+
super(message);
|
|
1286
|
+
this.name = "HttpError";
|
|
1287
|
+
this.status = status;
|
|
1288
|
+
if (Error.captureStackTrace) {
|
|
1289
|
+
Error.captureStackTrace(this, HttpError);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function getErrorStatus(err) {
|
|
1294
|
+
if (!err || typeof err !== "object") {
|
|
1295
|
+
return 500;
|
|
1296
|
+
}
|
|
1297
|
+
if (typeof err.status === "number") {
|
|
1298
|
+
return err.status;
|
|
1299
|
+
}
|
|
1300
|
+
if (typeof err.statusCode === "number") {
|
|
1301
|
+
return err.statusCode;
|
|
1302
|
+
}
|
|
1303
|
+
return 500;
|
|
1304
|
+
}
|
|
1305
|
+
class EventError extends HttpError {
|
|
1306
|
+
constructor(message = "Event Error") {
|
|
1307
|
+
super(message, 500);
|
|
1308
|
+
this.name = "EventError";
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1177
1311
|
const eta$1 = new eta$2.Eta();
|
|
1178
1312
|
function serveStatic(config, prefix) {
|
|
1179
1313
|
const rootPath = path.resolve(config.root || ".");
|
|
@@ -1326,20 +1460,18 @@ function serveStatic(config, prefix) {
|
|
|
1326
1460
|
serveStaticMiddleware.pluginName = "ServeStatic";
|
|
1327
1461
|
return serveStaticMiddleware;
|
|
1328
1462
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1463
|
+
const G = globalThis;
|
|
1464
|
+
G.__shokupan_db = G.__shokupan_db || null;
|
|
1465
|
+
G.__shokupan_db_promise = G.__shokupan_db_promise || null;
|
|
1332
1466
|
async function ensureDb() {
|
|
1333
|
-
if (
|
|
1334
|
-
if (
|
|
1335
|
-
|
|
1467
|
+
if (G.__shokupan_db) return G.__shokupan_db;
|
|
1468
|
+
if (G.__shokupan_db_promise) return G.__shokupan_db_promise;
|
|
1469
|
+
G.__shokupan_db_promise = (async () => {
|
|
1336
1470
|
try {
|
|
1337
1471
|
const { createNodeEngines } = await import("@surrealdb/node");
|
|
1338
1472
|
const surreal = await import("surrealdb");
|
|
1339
|
-
const Surreal = surreal.Surreal;
|
|
1340
|
-
RecordId = surreal.RecordId;
|
|
1341
1473
|
const engine = process.env["SHOKUPAN_DB_ENGINE"] === "memory" ? "mem://" : "rocksdb://database";
|
|
1342
|
-
const _db = new Surreal({
|
|
1474
|
+
const _db = new surrealdb.Surreal({
|
|
1343
1475
|
engines: createNodeEngines()
|
|
1344
1476
|
});
|
|
1345
1477
|
await _db.connect(engine, { namespace: "vendor", database: "shokupan" });
|
|
@@ -1350,33 +1482,33 @@ async function ensureDb() {
|
|
|
1350
1482
|
DEFINE TABLE OVERWRITE idempotency_keys SCHEMALESS COMMENT "Created by Shokupan";
|
|
1351
1483
|
DEFINE TABLE OVERWRITE middleware_tracking SCHEMALESS COMMENT "Created by Shokupan";
|
|
1352
1484
|
DEFINE TABLE OVERWRITE requests SCHEMALESS COMMENT "Created by Shokupan";
|
|
1485
|
+
DEFINE TABLE OVERWRITE metrics SCHEMALESS COMMENT "Created by Shokupan";
|
|
1353
1486
|
`);
|
|
1354
|
-
|
|
1355
|
-
return
|
|
1487
|
+
G.__shokupan_db = _db;
|
|
1488
|
+
return _db;
|
|
1356
1489
|
} catch (e) {
|
|
1357
|
-
|
|
1490
|
+
G.__shokupan_db_promise = null;
|
|
1358
1491
|
if (e.code === "ERR_MODULE_NOT_FOUND" || e.message.includes("Cannot find module")) {
|
|
1359
1492
|
throw new Error("SurrealDB dependencies not found. To use the datastore, please install 'surrealdb' and '@surrealdb/node'.");
|
|
1360
1493
|
}
|
|
1361
1494
|
throw e;
|
|
1362
1495
|
}
|
|
1363
1496
|
})();
|
|
1364
|
-
return
|
|
1497
|
+
return G.__shokupan_db_promise;
|
|
1365
1498
|
}
|
|
1366
1499
|
const datastore = {
|
|
1367
|
-
async get(
|
|
1500
|
+
async get(recordId) {
|
|
1368
1501
|
await ensureDb();
|
|
1369
|
-
return
|
|
1502
|
+
return G.__shokupan_db.select(recordId);
|
|
1370
1503
|
},
|
|
1371
|
-
async set(
|
|
1504
|
+
async set(recordId, value) {
|
|
1372
1505
|
await ensureDb();
|
|
1373
|
-
return
|
|
1506
|
+
return G.__shokupan_db.upsert(recordId).content(value);
|
|
1374
1507
|
},
|
|
1375
1508
|
async query(query, vars) {
|
|
1376
1509
|
await ensureDb();
|
|
1377
1510
|
try {
|
|
1378
|
-
|
|
1379
|
-
return Array.isArray(r) ? r : r?.collect ? await r.collect() : r;
|
|
1511
|
+
return G.__shokupan_db.query(query, vars).collect();
|
|
1380
1512
|
} catch (e) {
|
|
1381
1513
|
console.error("DS ERROR:", e);
|
|
1382
1514
|
throw e;
|
|
@@ -1387,7 +1519,7 @@ const datastore = {
|
|
|
1387
1519
|
}
|
|
1388
1520
|
};
|
|
1389
1521
|
process.on("exit", async () => {
|
|
1390
|
-
if (
|
|
1522
|
+
if (G.__shokupan_db) await G.__shokupan_db.close();
|
|
1391
1523
|
});
|
|
1392
1524
|
class Container {
|
|
1393
1525
|
static services = /* @__PURE__ */ new Map();
|
|
@@ -1618,6 +1750,7 @@ class ShokupanRouter {
|
|
|
1618
1750
|
metadata;
|
|
1619
1751
|
// Metadata for the router itself
|
|
1620
1752
|
currentGuards = [];
|
|
1753
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
1621
1754
|
// Registry Accessor
|
|
1622
1755
|
getComponentRegistry() {
|
|
1623
1756
|
const controllerRoutesMap = /* @__PURE__ */ new Map();
|
|
@@ -1678,6 +1811,34 @@ class ShokupanRouter {
|
|
|
1678
1811
|
isRouterInstance(target) {
|
|
1679
1812
|
return typeof target === "object" && target !== null && $isRouter in target;
|
|
1680
1813
|
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Registers an event handler for WebSocket.
|
|
1816
|
+
*/
|
|
1817
|
+
event(name, handler) {
|
|
1818
|
+
if (this.eventHandlers.has(name)) {
|
|
1819
|
+
const err = new EventError(`Event handler \`${name}\` already exists.`);
|
|
1820
|
+
console.warn(err);
|
|
1821
|
+
const handlers = this.eventHandlers.get(name);
|
|
1822
|
+
handlers.push(handler);
|
|
1823
|
+
this.eventHandlers.set(name, handlers);
|
|
1824
|
+
} else {
|
|
1825
|
+
this.eventHandlers.set(name, [handler]);
|
|
1826
|
+
}
|
|
1827
|
+
return this;
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Finds an event handler(s) by name.
|
|
1831
|
+
*/
|
|
1832
|
+
findEvent(name) {
|
|
1833
|
+
if (this.eventHandlers.has(name)) {
|
|
1834
|
+
return this.eventHandlers.get(name);
|
|
1835
|
+
}
|
|
1836
|
+
for (const child of this[$childRouters]) {
|
|
1837
|
+
const handler = child.findEvent(name);
|
|
1838
|
+
if (handler) return handler;
|
|
1839
|
+
}
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1681
1842
|
/**
|
|
1682
1843
|
* Mounts a controller instance to a path prefix.
|
|
1683
1844
|
*
|
|
@@ -1778,7 +1939,7 @@ class ShokupanRouter {
|
|
|
1778
1939
|
});
|
|
1779
1940
|
const ctx = new ShokupanContext(req);
|
|
1780
1941
|
let result = null;
|
|
1781
|
-
let status =
|
|
1942
|
+
let status = HTTP_STATUS.OK;
|
|
1782
1943
|
const headers = {};
|
|
1783
1944
|
const match = this.find(req.method, ctx.path);
|
|
1784
1945
|
if (match) {
|
|
@@ -1787,12 +1948,12 @@ class ShokupanRouter {
|
|
|
1787
1948
|
result = await match.handler(ctx);
|
|
1788
1949
|
} catch (err) {
|
|
1789
1950
|
console.error(err);
|
|
1790
|
-
status = err
|
|
1951
|
+
status = getErrorStatus(err);
|
|
1791
1952
|
result = { error: err.message || "Internal Server Error" };
|
|
1792
1953
|
if (err.errors) result.errors = err.errors;
|
|
1793
1954
|
}
|
|
1794
1955
|
} else {
|
|
1795
|
-
status =
|
|
1956
|
+
status = HTTP_STATUS.NOT_FOUND;
|
|
1796
1957
|
result = "Not Found";
|
|
1797
1958
|
}
|
|
1798
1959
|
if (result instanceof Response) {
|
|
@@ -1821,7 +1982,7 @@ class ShokupanRouter {
|
|
|
1821
1982
|
const originalHandler = handler;
|
|
1822
1983
|
const wrapped = async (ctx) => {
|
|
1823
1984
|
await this.runHooks("onRequestStart", ctx);
|
|
1824
|
-
const debug = ctx
|
|
1985
|
+
const debug = ctx[$debug];
|
|
1825
1986
|
let debugId;
|
|
1826
1987
|
let previousNode;
|
|
1827
1988
|
if (debug) {
|
|
@@ -1911,6 +2072,7 @@ class ShokupanRouter {
|
|
|
1911
2072
|
const decoratedRoutes = instance[$routeMethods] || proto && proto[$routeMethods];
|
|
1912
2073
|
const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
|
|
1913
2074
|
const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
|
|
2075
|
+
const decoratedEvents = instance[$eventMethods] || proto && proto[$eventMethods];
|
|
1914
2076
|
let routesAttached = 0;
|
|
1915
2077
|
for (let i = 0; i < Array.from(methods).length; i++) {
|
|
1916
2078
|
const name = Array.from(methods)[i];
|
|
@@ -2055,6 +2217,39 @@ class ShokupanRouter {
|
|
|
2055
2217
|
const spec = { tags: [tagName], ...userSpec };
|
|
2056
2218
|
this.add({ method, path: normalizedPath, handler: finalHandler, spec, controller: instance });
|
|
2057
2219
|
}
|
|
2220
|
+
if (decoratedEvents?.has(name)) {
|
|
2221
|
+
routesAttached++;
|
|
2222
|
+
const config = decoratedEvents.get(name);
|
|
2223
|
+
const routeArgs = decoratedArgs?.get(name);
|
|
2224
|
+
const wrappedHandler = async (ctx) => {
|
|
2225
|
+
let args = [ctx];
|
|
2226
|
+
if (routeArgs?.length > 0) {
|
|
2227
|
+
args = [];
|
|
2228
|
+
const sortedArgs = [...routeArgs].sort((a, b) => a.index - b.index);
|
|
2229
|
+
for (let k = 0; k < sortedArgs.length; k++) {
|
|
2230
|
+
const arg = sortedArgs[k];
|
|
2231
|
+
switch (arg.type) {
|
|
2232
|
+
case RouteParamType.BODY:
|
|
2233
|
+
args[arg.index] = await ctx.body();
|
|
2234
|
+
break;
|
|
2235
|
+
case RouteParamType.CONTEXT:
|
|
2236
|
+
args[arg.index] = ctx;
|
|
2237
|
+
break;
|
|
2238
|
+
case RouteParamType.REQUEST:
|
|
2239
|
+
args[arg.index] = ctx.req;
|
|
2240
|
+
break;
|
|
2241
|
+
case RouteParamType.HEADER:
|
|
2242
|
+
args[arg.index] = arg.name ? ctx.req.headers.get(arg.name) : ctx.req.headers;
|
|
2243
|
+
break;
|
|
2244
|
+
default:
|
|
2245
|
+
args[arg.index] = void 0;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return originalHandler.apply(instance, args);
|
|
2250
|
+
};
|
|
2251
|
+
this.event(config.eventName, wrappedHandler);
|
|
2252
|
+
}
|
|
2058
2253
|
}
|
|
2059
2254
|
if (routesAttached === 0) {
|
|
2060
2255
|
console.warn(`No routes attached to controller ${instance.constructor.name}`);
|
|
@@ -2218,8 +2413,10 @@ class ShokupanRouter {
|
|
|
2218
2413
|
Promise.resolve().then(async () => {
|
|
2219
2414
|
try {
|
|
2220
2415
|
const timestamp = Date.now();
|
|
2221
|
-
|
|
2222
|
-
|
|
2416
|
+
await datastore.set(new surrealdb.RecordId("middleware_tracking", {
|
|
2417
|
+
timestamp,
|
|
2418
|
+
name: handler.name || "anonymous"
|
|
2419
|
+
}), {
|
|
2223
2420
|
name: handler.name || "anonymous",
|
|
2224
2421
|
path: ctx.path,
|
|
2225
2422
|
timestamp,
|
|
@@ -2237,7 +2434,7 @@ class ShokupanRouter {
|
|
|
2237
2434
|
const cutoff = Date.now() - ttl;
|
|
2238
2435
|
await datastore.query(`DELETE middleware_tracking WHERE timestamp < ${cutoff}`);
|
|
2239
2436
|
const results = await datastore.query("SELECT count() FROM middleware_tracking GROUP ALL");
|
|
2240
|
-
if (results
|
|
2437
|
+
if (results?.[0]?.count > maxCapacity) {
|
|
2241
2438
|
const toDelete = results[0].count - maxCapacity;
|
|
2242
2439
|
await datastore.query(`DELETE middleware_tracking ORDER BY timestamp ASC LIMIT ${toDelete}`);
|
|
2243
2440
|
}
|
|
@@ -2314,7 +2511,7 @@ class ShokupanRouter {
|
|
|
2314
2511
|
(l) => l.includes(":") && !l.includes("router.ts") && !l.includes("shokupan.ts") && !l.includes("node_modules") && !l.includes("bun:main")
|
|
2315
2512
|
);
|
|
2316
2513
|
if (callerLine) {
|
|
2317
|
-
const match = callerLine.match(/\((
|
|
2514
|
+
const match = callerLine.match(/\((.{0,1000}):(\d{1,10}):(?:\d{1,10})\)/) || callerLine.match(/at (.{0,1000}):(\d{1,10}):(?:\d{1,10})/);
|
|
2318
2515
|
if (match) {
|
|
2319
2516
|
file = match[1];
|
|
2320
2517
|
line = parseInt(match[2], 10);
|
|
@@ -2332,7 +2529,7 @@ class ShokupanRouter {
|
|
|
2332
2529
|
}
|
|
2333
2530
|
return guardHandler(ctx, next);
|
|
2334
2531
|
};
|
|
2335
|
-
trackedGuard.originalHandler = guardHandler.originalHandler
|
|
2532
|
+
trackedGuard.originalHandler = guardHandler.originalHandler ?? guardHandler;
|
|
2336
2533
|
this.currentGuards.push({ handler: trackedGuard, spec });
|
|
2337
2534
|
return this;
|
|
2338
2535
|
}
|
|
@@ -2445,7 +2642,7 @@ class ShokupanRouter {
|
|
|
2445
2642
|
const fns = this.hookCache.get(name);
|
|
2446
2643
|
if (!fns) return;
|
|
2447
2644
|
const ctx = args?.[0] instanceof ShokupanContext ? args[0] : void 0;
|
|
2448
|
-
const debug = ctx?.
|
|
2645
|
+
const debug = ctx?.[$debug];
|
|
2449
2646
|
if (debug) {
|
|
2450
2647
|
await Promise.all(fns.map(async (fn, index) => {
|
|
2451
2648
|
const hookId = `hook_${name}_${fn.name || index}`;
|
|
@@ -2518,6 +2715,7 @@ const defaults = {
|
|
|
2518
2715
|
hostname: "localhost",
|
|
2519
2716
|
development: process.env.NODE_ENV !== "production",
|
|
2520
2717
|
enableAsyncLocalStorage: false,
|
|
2718
|
+
enableHttpBridge: false,
|
|
2521
2719
|
reusePort: false
|
|
2522
2720
|
};
|
|
2523
2721
|
api.trace.getTracer("shokupan.application");
|
|
@@ -2526,6 +2724,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2526
2724
|
openApiSpec;
|
|
2527
2725
|
composedMiddleware;
|
|
2528
2726
|
cpuMonitor;
|
|
2727
|
+
server;
|
|
2529
2728
|
get logger() {
|
|
2530
2729
|
return this.applicationConfig.logger;
|
|
2531
2730
|
}
|
|
@@ -2634,6 +2833,7 @@ class Shokupan extends ShokupanRouter {
|
|
|
2634
2833
|
this.cpuMonitor = new SystemCpuMonitor();
|
|
2635
2834
|
this.cpuMonitor.start();
|
|
2636
2835
|
}
|
|
2836
|
+
const self = this;
|
|
2637
2837
|
const serveOptions = {
|
|
2638
2838
|
port: finalPort,
|
|
2639
2839
|
hostname: this.applicationConfig.hostname,
|
|
@@ -2645,8 +2845,61 @@ class Shokupan extends ShokupanRouter {
|
|
|
2645
2845
|
open(ws) {
|
|
2646
2846
|
ws.data?.handler?.open?.(ws);
|
|
2647
2847
|
},
|
|
2648
|
-
message(ws, message) {
|
|
2649
|
-
ws.data?.handler?.message
|
|
2848
|
+
async message(ws, message) {
|
|
2849
|
+
if (ws.data?.handler?.message) {
|
|
2850
|
+
return ws.data.handler.message(ws, message);
|
|
2851
|
+
}
|
|
2852
|
+
if (typeof message !== "string") return;
|
|
2853
|
+
try {
|
|
2854
|
+
const payload = JSON.parse(message);
|
|
2855
|
+
if (self.applicationConfig["enableHttpBridge"] && payload.type === "HTTP") {
|
|
2856
|
+
const { id, method, path: path2, headers, body } = payload;
|
|
2857
|
+
const url = new URL(path2, `http://${self.applicationConfig.hostname || "localhost"}:${finalPort}`);
|
|
2858
|
+
const req = new Request(url.toString(), {
|
|
2859
|
+
method,
|
|
2860
|
+
headers,
|
|
2861
|
+
body: typeof body === "object" ? JSON.stringify(body) : body
|
|
2862
|
+
});
|
|
2863
|
+
const res = await self.fetch(req);
|
|
2864
|
+
const resBody = await res.json().catch((err) => res.text());
|
|
2865
|
+
const resHeaders = {};
|
|
2866
|
+
res.headers.forEach((v, k) => resHeaders[k] = v);
|
|
2867
|
+
ws.send(JSON.stringify({
|
|
2868
|
+
type: "RESPONSE",
|
|
2869
|
+
id,
|
|
2870
|
+
status: res.status,
|
|
2871
|
+
headers: resHeaders,
|
|
2872
|
+
body: resBody
|
|
2873
|
+
}));
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
const eventName = payload.event || (payload.type === "EVENT" ? payload.name : void 0);
|
|
2877
|
+
if (eventName) {
|
|
2878
|
+
const handlers = self.findEvent(eventName);
|
|
2879
|
+
const handler = handlers?.length == 1 ? handlers[0] : compose(handlers);
|
|
2880
|
+
if (handler) {
|
|
2881
|
+
const data = payload.data || payload.payload;
|
|
2882
|
+
const req = new ShokupanRequest({
|
|
2883
|
+
url: `http://${self.applicationConfig.hostname || "localhost"}/event/${eventName}`,
|
|
2884
|
+
method: "POST",
|
|
2885
|
+
headers: new Headers({ "content-type": "application/json" }),
|
|
2886
|
+
body: JSON.stringify(data)
|
|
2887
|
+
});
|
|
2888
|
+
const ctx = new ShokupanContext(req, self.server);
|
|
2889
|
+
ctx[$ws] = ws;
|
|
2890
|
+
try {
|
|
2891
|
+
await handler(ctx);
|
|
2892
|
+
} catch (err) {
|
|
2893
|
+
if (self.applicationConfig["websocketErrorHandler"]) {
|
|
2894
|
+
await self.applicationConfig["websocketErrorHandler"](err, ctx);
|
|
2895
|
+
} else {
|
|
2896
|
+
console.error(`Error in event ${eventName}:`, err);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
} catch (e) {
|
|
2902
|
+
}
|
|
2650
2903
|
},
|
|
2651
2904
|
drain(ws) {
|
|
2652
2905
|
ws.data?.handler?.drain?.(ws);
|
|
@@ -2658,12 +2911,40 @@ class Shokupan extends ShokupanRouter {
|
|
|
2658
2911
|
};
|
|
2659
2912
|
let factory = this.applicationConfig.serverFactory;
|
|
2660
2913
|
if (!factory && typeof Bun === "undefined") {
|
|
2661
|
-
const { createHttpServer } = await Promise.resolve().then(() => require("./http-server-
|
|
2914
|
+
const { createHttpServer } = await Promise.resolve().then(() => require("./http-server-BEMPIs33.cjs"));
|
|
2662
2915
|
factory = createHttpServer();
|
|
2663
2916
|
}
|
|
2664
|
-
|
|
2917
|
+
this.server = factory ? await factory(serveOptions) : Bun.serve(serveOptions);
|
|
2665
2918
|
console.log(`Shokupan server listening on http://${serveOptions.hostname}:${serveOptions.port}`);
|
|
2666
|
-
return server;
|
|
2919
|
+
return this.server;
|
|
2920
|
+
}
|
|
2921
|
+
/**
|
|
2922
|
+
* Stops the application server.
|
|
2923
|
+
*
|
|
2924
|
+
* This method gracefully shuts down the server and stops any running monitors.
|
|
2925
|
+
* Works transparently in both Bun and Node.js runtimes.
|
|
2926
|
+
*
|
|
2927
|
+
* @returns A promise that resolves when the server has been stopped.
|
|
2928
|
+
*
|
|
2929
|
+
* @example
|
|
2930
|
+
* ```typescript
|
|
2931
|
+
* const app = new Shokupan();
|
|
2932
|
+
* const server = await app.listen(3000);
|
|
2933
|
+
*
|
|
2934
|
+
* // Later, when you want to stop the server
|
|
2935
|
+
* await app.stop();
|
|
2936
|
+
* ```
|
|
2937
|
+
* @param closeActiveConnections — Immediately terminate in-flight requests, websockets, and stop accepting new connections.
|
|
2938
|
+
*/
|
|
2939
|
+
async stop(closeActiveConnections) {
|
|
2940
|
+
if (this.cpuMonitor) {
|
|
2941
|
+
this.cpuMonitor.stop();
|
|
2942
|
+
this.cpuMonitor = void 0;
|
|
2943
|
+
}
|
|
2944
|
+
if (this.server) {
|
|
2945
|
+
await this.server.stop(closeActiveConnections);
|
|
2946
|
+
this.server = void 0;
|
|
2947
|
+
}
|
|
2667
2948
|
}
|
|
2668
2949
|
[$dispatch](req) {
|
|
2669
2950
|
return this.fetch(req);
|
|
@@ -2761,28 +3042,33 @@ class Shokupan extends ShokupanRouter {
|
|
|
2761
3042
|
const bodyParsing = ["POST", "PUT", "PATCH", "DELETE"].includes(req.method) ? ctx.parseBody() : Promise.resolve();
|
|
2762
3043
|
const match = this.find(req.method, ctx.path);
|
|
2763
3044
|
if (match) {
|
|
2764
|
-
ctx
|
|
3045
|
+
ctx[$routeMatched] = true;
|
|
2765
3046
|
ctx.params = match.params;
|
|
2766
3047
|
await bodyParsing;
|
|
2767
3048
|
return match.handler(ctx);
|
|
2768
3049
|
}
|
|
3050
|
+
if (ctx.upgrade()) {
|
|
3051
|
+
return void 0;
|
|
3052
|
+
}
|
|
2769
3053
|
return null;
|
|
2770
3054
|
});
|
|
2771
3055
|
let response;
|
|
2772
3056
|
if (result instanceof Response) {
|
|
2773
3057
|
response = result;
|
|
2774
|
-
} else if ((result === null || result === void 0) && ctx
|
|
2775
|
-
response = ctx
|
|
3058
|
+
} else if ((result === null || result === void 0) && ctx[$finalResponse] instanceof Response) {
|
|
3059
|
+
response = ctx[$finalResponse];
|
|
2776
3060
|
} else if (result === null || result === void 0) {
|
|
2777
|
-
if (ctx
|
|
2778
|
-
response = ctx
|
|
2779
|
-
} else if (ctx.
|
|
3061
|
+
if (ctx[$finalResponse] instanceof Response) {
|
|
3062
|
+
response = ctx[$finalResponse];
|
|
3063
|
+
} else if (ctx.isUpgraded) {
|
|
3064
|
+
return void 0;
|
|
3065
|
+
} else if (ctx[$routeMatched]) {
|
|
2780
3066
|
response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
|
|
2781
3067
|
} else {
|
|
2782
|
-
if (ctx.response.status !==
|
|
3068
|
+
if (ctx.response.status !== HTTP_STATUS.OK) {
|
|
2783
3069
|
response = ctx.send(null, { status: ctx.response.status, headers: ctx.response.headers });
|
|
2784
3070
|
} else {
|
|
2785
|
-
response = ctx.text("Not Found",
|
|
3071
|
+
response = ctx.text("Not Found", HTTP_STATUS.NOT_FOUND);
|
|
2786
3072
|
}
|
|
2787
3073
|
}
|
|
2788
3074
|
} else if (typeof result === "object") {
|
|
@@ -2794,10 +3080,9 @@ class Shokupan extends ShokupanRouter {
|
|
|
2794
3080
|
await this.runHooks("onResponseStart", ctx, response);
|
|
2795
3081
|
return response;
|
|
2796
3082
|
} catch (err) {
|
|
2797
|
-
console.error(err);
|
|
2798
3083
|
const span = asyncContext.getStore()?.span;
|
|
2799
3084
|
if (span) span.setStatus({ code: 2 });
|
|
2800
|
-
const status = err
|
|
3085
|
+
const status = getErrorStatus(err);
|
|
2801
3086
|
const body = { error: err.message || "Internal Server Error" };
|
|
2802
3087
|
if (err.errors) body.errors = err.errors;
|
|
2803
3088
|
await this.runHooks("onError", ctx, err);
|
|
@@ -2819,10 +3104,10 @@ class Shokupan extends ShokupanRouter {
|
|
|
2819
3104
|
}
|
|
2820
3105
|
return executionPromise.catch((err) => {
|
|
2821
3106
|
if (err.message === "Request Timeout") {
|
|
2822
|
-
return ctx.text("Request Timeout",
|
|
3107
|
+
return ctx.text("Request Timeout", HTTP_STATUS.REQUEST_TIMEOUT);
|
|
2823
3108
|
}
|
|
2824
3109
|
console.error("Unexpected error in request execution:", err);
|
|
2825
|
-
return ctx.text("Internal Server Error",
|
|
3110
|
+
return ctx.text("Internal Server Error", HTTP_STATUS.INTERNAL_SERVER_ERROR);
|
|
2826
3111
|
}).then(async (res) => {
|
|
2827
3112
|
await this.runHooks("onResponseEnd", ctx, res);
|
|
2828
3113
|
return res;
|
|
@@ -2990,6 +3275,14 @@ const Patch = createMethodDecorator("PATCH");
|
|
|
2990
3275
|
const Options = createMethodDecorator("OPTIONS");
|
|
2991
3276
|
const Head = createMethodDecorator("HEAD");
|
|
2992
3277
|
const All = createMethodDecorator("ALL");
|
|
3278
|
+
function Event(eventName) {
|
|
3279
|
+
return (target, propertyKey, descriptor) => {
|
|
3280
|
+
target[$eventMethods] ??= /* @__PURE__ */ new Map();
|
|
3281
|
+
target[$eventMethods].set(propertyKey, {
|
|
3282
|
+
eventName
|
|
3283
|
+
});
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
2993
3286
|
function RateLimit(options) {
|
|
2994
3287
|
return Use(RateLimitMiddleware(options));
|
|
2995
3288
|
}
|
|
@@ -3333,6 +3626,554 @@ class ClusterPlugin {
|
|
|
3333
3626
|
}
|
|
3334
3627
|
}
|
|
3335
3628
|
}
|
|
3629
|
+
const INTERVALS = [
|
|
3630
|
+
{ label: "10s", ms: 10 * 1e3 },
|
|
3631
|
+
{ label: "1m", ms: 60 * 1e3 },
|
|
3632
|
+
{ label: "5m", ms: 5 * 60 * 1e3 },
|
|
3633
|
+
{ label: "1h", ms: 60 * 60 * 1e3 },
|
|
3634
|
+
{ label: "2h", ms: 2 * 60 * 60 * 1e3 },
|
|
3635
|
+
{ label: "6h", ms: 6 * 60 * 60 * 1e3 },
|
|
3636
|
+
{ label: "12h", ms: 12 * 60 * 60 * 1e3 },
|
|
3637
|
+
{ label: "1d", ms: 24 * 60 * 60 * 1e3 },
|
|
3638
|
+
{ label: "3d", ms: 3 * 24 * 60 * 60 * 1e3 },
|
|
3639
|
+
{ label: "7d", ms: 7 * 24 * 60 * 60 * 1e3 },
|
|
3640
|
+
{ label: "30d", ms: 30 * 24 * 60 * 60 * 1e3 }
|
|
3641
|
+
];
|
|
3642
|
+
class MetricsCollector {
|
|
3643
|
+
currentIntervalStart = {};
|
|
3644
|
+
pendingDetails = {};
|
|
3645
|
+
eventLoopHistogram = node_perf_hooks.monitorEventLoopDelay({ resolution: 10 });
|
|
3646
|
+
timer = null;
|
|
3647
|
+
constructor() {
|
|
3648
|
+
this.eventLoopHistogram.enable();
|
|
3649
|
+
const now = Date.now();
|
|
3650
|
+
INTERVALS.forEach((int) => {
|
|
3651
|
+
this.currentIntervalStart[int.label] = this.alignTimestamp(now, int.ms);
|
|
3652
|
+
this.pendingDetails[int.label] = [];
|
|
3653
|
+
});
|
|
3654
|
+
this.timer = setInterval(() => this.collect(), 1e4);
|
|
3655
|
+
}
|
|
3656
|
+
recordRequest(duration, isError) {
|
|
3657
|
+
INTERVALS.forEach((int) => {
|
|
3658
|
+
this.pendingDetails[int.label].push({ duration, isError });
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
alignTimestamp(ts, intervalMs) {
|
|
3662
|
+
return Math.floor(ts / intervalMs) * intervalMs;
|
|
3663
|
+
}
|
|
3664
|
+
async collect() {
|
|
3665
|
+
try {
|
|
3666
|
+
const now = Date.now();
|
|
3667
|
+
console.log("[MetricsCollector] collect() called at", new Date(now).toISOString());
|
|
3668
|
+
for (const int of INTERVALS) {
|
|
3669
|
+
const start = this.currentIntervalStart[int.label];
|
|
3670
|
+
if (now >= start + int.ms) {
|
|
3671
|
+
console.log(`[MetricsCollector] Flushing ${int.label} interval (boundary crossed)`);
|
|
3672
|
+
await this.flushInterval(int.label, start, int.ms);
|
|
3673
|
+
this.currentIntervalStart[int.label] = this.alignTimestamp(now, int.ms);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
} catch (error) {
|
|
3677
|
+
console.error("[MetricsCollector] Error in collect():", error);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
async flushInterval(label, timestamp, durationMs) {
|
|
3681
|
+
const reqs = this.pendingDetails[label];
|
|
3682
|
+
console.log(`[MetricsCollector] flushInterval(${label}) - ${reqs.length} requests pending`);
|
|
3683
|
+
this.pendingDetails[label] = [];
|
|
3684
|
+
if (reqs.length === 0) {
|
|
3685
|
+
console.log(`[MetricsCollector] No requests for ${label}, skipping persist`);
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
const totalReqs = reqs.length;
|
|
3689
|
+
const errorReqs = reqs.filter((r) => r.isError).length;
|
|
3690
|
+
const successReqs = totalReqs - errorReqs;
|
|
3691
|
+
const duratons = reqs.map((r) => r.duration).sort((a, b) => a - b);
|
|
3692
|
+
const rps = totalReqs / (durationMs / 1e3);
|
|
3693
|
+
const sum = duratons.reduce((a, b) => a + b, 0);
|
|
3694
|
+
const avg = totalReqs > 0 ? sum / totalReqs : 0;
|
|
3695
|
+
const getP = (p) => {
|
|
3696
|
+
if (duratons.length === 0) return 0;
|
|
3697
|
+
const idx = Math.floor(duratons.length * p);
|
|
3698
|
+
return duratons[idx];
|
|
3699
|
+
};
|
|
3700
|
+
const metric = {
|
|
3701
|
+
timestamp,
|
|
3702
|
+
interval: label,
|
|
3703
|
+
cpu: os__namespace.loadavg()[0],
|
|
3704
|
+
// Using load avg for simplicity as per requirements (Load)
|
|
3705
|
+
load: os__namespace.loadavg(),
|
|
3706
|
+
memory: {
|
|
3707
|
+
used: process.memoryUsage().rss,
|
|
3708
|
+
total: os__namespace.totalmem(),
|
|
3709
|
+
heapUsed: process.memoryUsage().heapUsed,
|
|
3710
|
+
heapTotal: process.memoryUsage().heapTotal
|
|
3711
|
+
},
|
|
3712
|
+
eventLoopLatency: {
|
|
3713
|
+
min: this.eventLoopHistogram.min / 1e6,
|
|
3714
|
+
max: this.eventLoopHistogram.max / 1e6,
|
|
3715
|
+
mean: this.eventLoopHistogram.mean / 1e6,
|
|
3716
|
+
p50: this.eventLoopHistogram.percentile(50) / 1e6,
|
|
3717
|
+
p95: this.eventLoopHistogram.percentile(95) / 1e6,
|
|
3718
|
+
p99: this.eventLoopHistogram.percentile(99) / 1e6
|
|
3719
|
+
},
|
|
3720
|
+
requests: {
|
|
3721
|
+
total: totalReqs,
|
|
3722
|
+
rps,
|
|
3723
|
+
success: successReqs,
|
|
3724
|
+
error: errorReqs
|
|
3725
|
+
},
|
|
3726
|
+
responseTime: {
|
|
3727
|
+
min: duratons[0] || 0,
|
|
3728
|
+
max: duratons[duratons.length - 1] || 0,
|
|
3729
|
+
avg,
|
|
3730
|
+
p50: getP(0.5),
|
|
3731
|
+
p95: getP(0.95),
|
|
3732
|
+
p99: getP(0.99)
|
|
3733
|
+
}
|
|
3734
|
+
};
|
|
3735
|
+
console.log(`[MetricsCollector] Persisting ${label} metric at timestamp ${timestamp}`);
|
|
3736
|
+
try {
|
|
3737
|
+
const recordId = new surrealdb.RecordId("metrics", timestamp);
|
|
3738
|
+
await datastore.set(recordId, metric);
|
|
3739
|
+
console.log(`[MetricsCollector] ✓ Successfully saved ${label} metric to datastore`);
|
|
3740
|
+
const test = await datastore.get(recordId);
|
|
3741
|
+
console.log(`[MetricsCollector] DEBUG: Immediate .get() returned:`, test ? "DATA" : "NULL");
|
|
3742
|
+
const queryTest = await datastore.query("SELECT * FROM metrics WHERE id = $id", { id: recordId });
|
|
3743
|
+
console.log(`[MetricsCollector] DEBUG: Query by id returned ${queryTest[0]?.length || 0} records`);
|
|
3744
|
+
} catch (e) {
|
|
3745
|
+
console.error(`[MetricsCollector] ✗ Failed to save metrics for ${label}:`, e);
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
// Cleanup if needed
|
|
3749
|
+
stop() {
|
|
3750
|
+
if (this.timer) clearInterval(this.timer);
|
|
3751
|
+
this.eventLoopHistogram.disable();
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
class Collector {
|
|
3755
|
+
constructor(dashboard) {
|
|
3756
|
+
this.dashboard = dashboard;
|
|
3757
|
+
}
|
|
3758
|
+
currentNode;
|
|
3759
|
+
trackStep(id, type, duration, status, error) {
|
|
3760
|
+
if (!id) return;
|
|
3761
|
+
this.dashboard.recordNodeMetric(id, type, duration, status === "error");
|
|
3762
|
+
}
|
|
3763
|
+
trackEdge(fromId, toId) {
|
|
3764
|
+
if (!fromId || !toId) return;
|
|
3765
|
+
this.dashboard.recordEdgeMetric(fromId, toId);
|
|
3766
|
+
}
|
|
3767
|
+
setNode(id) {
|
|
3768
|
+
this.currentNode = id;
|
|
3769
|
+
}
|
|
3770
|
+
getCurrentNode() {
|
|
3771
|
+
return this.currentNode;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
class Dashboard {
|
|
3775
|
+
constructor(dashboardConfig = {}) {
|
|
3776
|
+
this.dashboardConfig = dashboardConfig;
|
|
3777
|
+
}
|
|
3778
|
+
static __dirname = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
|
|
3779
|
+
// Get base path for dashboard files - works in both dev (src/) and production (dist/)
|
|
3780
|
+
static getBasePath() {
|
|
3781
|
+
const dir = path$1.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href));
|
|
3782
|
+
if (dir.endsWith("dist")) {
|
|
3783
|
+
return dir + "/plugins/application/dashboard";
|
|
3784
|
+
}
|
|
3785
|
+
return dir;
|
|
3786
|
+
}
|
|
3787
|
+
router = new ShokupanRouter();
|
|
3788
|
+
metrics = {
|
|
3789
|
+
totalRequests: 0,
|
|
3790
|
+
successfulRequests: 0,
|
|
3791
|
+
failedRequests: 0,
|
|
3792
|
+
activeRequests: 0,
|
|
3793
|
+
averageTotalTime_ms: 0,
|
|
3794
|
+
recentTimings: [],
|
|
3795
|
+
logs: [],
|
|
3796
|
+
rateLimitedCounts: {},
|
|
3797
|
+
nodeMetrics: {},
|
|
3798
|
+
edgeMetrics: {}
|
|
3799
|
+
};
|
|
3800
|
+
eta = new eta$2.Eta({
|
|
3801
|
+
views: Dashboard.getBasePath() + "/static",
|
|
3802
|
+
cache: false
|
|
3803
|
+
});
|
|
3804
|
+
startTime = Date.now();
|
|
3805
|
+
instrumented = false;
|
|
3806
|
+
metricsCollector = new MetricsCollector();
|
|
3807
|
+
// ShokupanPlugin interface implementation
|
|
3808
|
+
onInit(app, options) {
|
|
3809
|
+
this[$appRoot] = app;
|
|
3810
|
+
const mountPath = options?.path || this.dashboardConfig.path || "/dashboard";
|
|
3811
|
+
const hooks = this.getHooks();
|
|
3812
|
+
if (!app.middleware) {
|
|
3813
|
+
app.middleware = [];
|
|
3814
|
+
}
|
|
3815
|
+
const hooksMiddleware = async (ctx, next) => {
|
|
3816
|
+
if (hooks.onRequestStart) {
|
|
3817
|
+
await hooks.onRequestStart(ctx);
|
|
3818
|
+
}
|
|
3819
|
+
await next();
|
|
3820
|
+
if (hooks.onResponseEnd) {
|
|
3821
|
+
const effectiveResponse = ctx._finalResponse || ctx.response || {};
|
|
3822
|
+
await hooks.onResponseEnd(ctx, effectiveResponse);
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
3825
|
+
app.use(hooksMiddleware);
|
|
3826
|
+
app.mount(mountPath, this.router);
|
|
3827
|
+
this.setupRoutes();
|
|
3828
|
+
}
|
|
3829
|
+
setupRoutes() {
|
|
3830
|
+
this.router.get("/metrics", async (ctx) => {
|
|
3831
|
+
const uptimeSeconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
3832
|
+
const uptime = `${Math.floor(uptimeSeconds / 3600)}h ${Math.floor(uptimeSeconds % 3600 / 60)}m ${uptimeSeconds % 60}s`;
|
|
3833
|
+
const interval = ctx.query["interval"];
|
|
3834
|
+
if (interval) {
|
|
3835
|
+
const intervalMap = {
|
|
3836
|
+
"10s": 10 * 1e3,
|
|
3837
|
+
"1m": 60 * 1e3,
|
|
3838
|
+
"5m": 5 * 60 * 1e3,
|
|
3839
|
+
"30m": 30 * 60 * 1e3,
|
|
3840
|
+
"1h": 60 * 60 * 1e3,
|
|
3841
|
+
"2h": 2 * 60 * 60 * 1e3,
|
|
3842
|
+
"6h": 6 * 60 * 60 * 1e3,
|
|
3843
|
+
"12h": 12 * 60 * 60 * 1e3,
|
|
3844
|
+
"1d": 24 * 60 * 60 * 1e3,
|
|
3845
|
+
"3d": 3 * 24 * 60 * 60 * 1e3,
|
|
3846
|
+
"7d": 7 * 24 * 60 * 60 * 1e3,
|
|
3847
|
+
"30d": 30 * 24 * 60 * 60 * 1e3
|
|
3848
|
+
};
|
|
3849
|
+
const ms = intervalMap[interval] || 60 * 1e3;
|
|
3850
|
+
const startTime = Date.now() - ms;
|
|
3851
|
+
let stats;
|
|
3852
|
+
try {
|
|
3853
|
+
stats = await datastore.query(`
|
|
3854
|
+
SELECT
|
|
3855
|
+
count() as total,
|
|
3856
|
+
count(IF status < 400 THEN 1 END) as success,
|
|
3857
|
+
count(IF status >= 400 THEN 1 END) as failed,
|
|
3858
|
+
math::mean(duration) as avg_latency
|
|
3859
|
+
FROM requests
|
|
3860
|
+
WHERE timestamp >= $start
|
|
3861
|
+
GROUP ALL
|
|
3862
|
+
`, { start: startTime });
|
|
3863
|
+
} catch (error) {
|
|
3864
|
+
console.error("[Dashboard] Query failed at plugin.ts:180-191", {
|
|
3865
|
+
error,
|
|
3866
|
+
interval,
|
|
3867
|
+
startTime,
|
|
3868
|
+
query: "metrics interval stats",
|
|
3869
|
+
stack: new Error().stack
|
|
3870
|
+
});
|
|
3871
|
+
throw error;
|
|
3872
|
+
}
|
|
3873
|
+
const s = stats[0] || { total: 0, success: 0, failed: 0, avg_latency: 0 };
|
|
3874
|
+
return ctx.json({
|
|
3875
|
+
metrics: {
|
|
3876
|
+
totalRequests: s.total || 0,
|
|
3877
|
+
successfulRequests: s.success || 0,
|
|
3878
|
+
failedRequests: s.failed || 0,
|
|
3879
|
+
activeRequests: this.metrics.activeRequests,
|
|
3880
|
+
averageTotalTime_ms: s.avg_latency || 0,
|
|
3881
|
+
recentTimings: this.metrics.recentTimings,
|
|
3882
|
+
logs: [],
|
|
3883
|
+
rateLimitedCounts: this.metrics.rateLimitedCounts,
|
|
3884
|
+
nodeMetrics: this.metrics.nodeMetrics,
|
|
3885
|
+
edgeMetrics: this.metrics.edgeMetrics
|
|
3886
|
+
},
|
|
3887
|
+
uptime
|
|
3888
|
+
});
|
|
3889
|
+
}
|
|
3890
|
+
return ctx.json({
|
|
3891
|
+
metrics: this.metrics,
|
|
3892
|
+
uptime
|
|
3893
|
+
});
|
|
3894
|
+
});
|
|
3895
|
+
this.router.get("/metrics/history", async (ctx) => {
|
|
3896
|
+
const interval = ctx.query["interval"] || "1m";
|
|
3897
|
+
const intervalMap = {
|
|
3898
|
+
"10s": 10 * 1e3,
|
|
3899
|
+
"1m": 60 * 1e3,
|
|
3900
|
+
"5m": 5 * 60 * 1e3,
|
|
3901
|
+
"30m": 30 * 60 * 1e3,
|
|
3902
|
+
"1h": 60 * 60 * 1e3,
|
|
3903
|
+
"2h": 2 * 60 * 60 * 1e3,
|
|
3904
|
+
"6h": 6 * 60 * 60 * 1e3,
|
|
3905
|
+
"12h": 12 * 60 * 60 * 1e3,
|
|
3906
|
+
"1d": 24 * 60 * 60 * 1e3,
|
|
3907
|
+
"3d": 3 * 24 * 60 * 60 * 1e3,
|
|
3908
|
+
"7d": 7 * 24 * 60 * 60 * 1e3,
|
|
3909
|
+
"30d": 30 * 24 * 60 * 60 * 1e3
|
|
3910
|
+
};
|
|
3911
|
+
const periodMs = intervalMap[interval] || 60 * 1e3;
|
|
3912
|
+
const startTime = Date.now() - periodMs * 3;
|
|
3913
|
+
const endTime = Date.now();
|
|
3914
|
+
const result = await datastore.query(
|
|
3915
|
+
"SELECT * FROM metrics WHERE timestamp >= $start AND timestamp <= $end AND interval = $interval ORDER BY timestamp ASC",
|
|
3916
|
+
{ start: startTime, end: endTime, interval }
|
|
3917
|
+
);
|
|
3918
|
+
return ctx.json({
|
|
3919
|
+
metrics: result[0] || []
|
|
3920
|
+
});
|
|
3921
|
+
});
|
|
3922
|
+
const getIntervalStartTime = (interval) => {
|
|
3923
|
+
if (!interval) return 0;
|
|
3924
|
+
const intervalMap = {
|
|
3925
|
+
"10s": 10 * 1e3,
|
|
3926
|
+
"1m": 60 * 1e3,
|
|
3927
|
+
"5m": 5 * 60 * 1e3,
|
|
3928
|
+
"30m": 30 * 60 * 1e3,
|
|
3929
|
+
"1h": 60 * 60 * 1e3,
|
|
3930
|
+
"2h": 2 * 60 * 60 * 1e3,
|
|
3931
|
+
"6h": 6 * 60 * 60 * 1e3,
|
|
3932
|
+
"12h": 12 * 60 * 60 * 1e3,
|
|
3933
|
+
"1d": 24 * 60 * 60 * 1e3,
|
|
3934
|
+
"3d": 3 * 24 * 60 * 60 * 1e3,
|
|
3935
|
+
"7d": 7 * 24 * 60 * 60 * 1e3,
|
|
3936
|
+
"30d": 30 * 24 * 60 * 60 * 1e3
|
|
3937
|
+
};
|
|
3938
|
+
const ms = intervalMap[interval] || 0;
|
|
3939
|
+
return ms ? Date.now() - ms : 0;
|
|
3940
|
+
};
|
|
3941
|
+
this.router.get("/requests/top", async (ctx) => {
|
|
3942
|
+
const startTime = getIntervalStartTime(ctx.query["interval"]);
|
|
3943
|
+
const result = await datastore.query(
|
|
3944
|
+
"SELECT method, url, count() as count FROM requests WHERE timestamp >= $start GROUP BY method, url ORDER BY count DESC LIMIT 10",
|
|
3945
|
+
{ start: startTime }
|
|
3946
|
+
);
|
|
3947
|
+
return ctx.json({ top: result[0] || [] });
|
|
3948
|
+
});
|
|
3949
|
+
this.router.get("/errors/top", async (ctx) => {
|
|
3950
|
+
const startTime = getIntervalStartTime(ctx.query["interval"]);
|
|
3951
|
+
const result = await datastore.query(
|
|
3952
|
+
"SELECT status, count() as count FROM failed_requests WHERE timestamp >= $start GROUP BY status ORDER BY count DESC LIMIT 10",
|
|
3953
|
+
{ start: startTime }
|
|
3954
|
+
);
|
|
3955
|
+
return ctx.json({ top: result[0] || [] });
|
|
3956
|
+
});
|
|
3957
|
+
this.router.get("/requests/failing", async (ctx) => {
|
|
3958
|
+
const startTime = getIntervalStartTime(ctx.query["interval"]);
|
|
3959
|
+
const result = await datastore.query(
|
|
3960
|
+
"SELECT method, url, count() as count FROM failed_requests WHERE timestamp >= $start GROUP BY method, url ORDER BY count DESC LIMIT 10",
|
|
3961
|
+
{ start: startTime }
|
|
3962
|
+
);
|
|
3963
|
+
return ctx.json({ top: result[0] || [] });
|
|
3964
|
+
});
|
|
3965
|
+
this.router.get("/requests/slowest", async (ctx) => {
|
|
3966
|
+
const startTime = getIntervalStartTime(ctx.query["interval"]);
|
|
3967
|
+
const result = await datastore.query(
|
|
3968
|
+
"SELECT method, url, duration, status, timestamp FROM requests WHERE timestamp >= $start ORDER BY duration DESC LIMIT 10",
|
|
3969
|
+
{ start: startTime }
|
|
3970
|
+
);
|
|
3971
|
+
return ctx.json({ slowest: result[0] || [] });
|
|
3972
|
+
});
|
|
3973
|
+
this.router.get("/registry", (ctx) => {
|
|
3974
|
+
const app = this[$appRoot];
|
|
3975
|
+
if (!this.instrumented && app) {
|
|
3976
|
+
this.instrumentApp(app);
|
|
3977
|
+
}
|
|
3978
|
+
const registry = app?.getComponentRegistry?.();
|
|
3979
|
+
if (registry) {
|
|
3980
|
+
this.assignIdsToRegistry(registry, "root");
|
|
3981
|
+
}
|
|
3982
|
+
return ctx.json({ registry: registry || {} });
|
|
3983
|
+
});
|
|
3984
|
+
this.router.get("/requests", async (ctx) => {
|
|
3985
|
+
const result = await datastore.query("SELECT * FROM requests ORDER BY timestamp DESC LIMIT 100");
|
|
3986
|
+
return ctx.json({ requests: result[0] || [] });
|
|
3987
|
+
});
|
|
3988
|
+
this.router.get("/requests/:id", async (ctx) => {
|
|
3989
|
+
const result = await datastore.query("SELECT * FROM requests WHERE id = $id", { id: ctx.params["id"] });
|
|
3990
|
+
return ctx.json({ request: result[0]?.[0] });
|
|
3991
|
+
});
|
|
3992
|
+
this.router.get("/failures", async (ctx) => {
|
|
3993
|
+
const result = await datastore.query("SELECT * FROM failed_requests ORDER BY timestamp DESC LIMIT 50");
|
|
3994
|
+
return ctx.json({ failures: result[0] });
|
|
3995
|
+
});
|
|
3996
|
+
this.router.post("/replay", async (ctx) => {
|
|
3997
|
+
const body = await ctx.body();
|
|
3998
|
+
const app = this[$appRoot];
|
|
3999
|
+
if (!app) return unknownError(ctx);
|
|
4000
|
+
try {
|
|
4001
|
+
const result = await app.processRequest({
|
|
4002
|
+
method: body.method,
|
|
4003
|
+
path: body.url,
|
|
4004
|
+
// or path
|
|
4005
|
+
headers: body.headers,
|
|
4006
|
+
body: body.body
|
|
4007
|
+
});
|
|
4008
|
+
return ctx.json({
|
|
4009
|
+
status: result.status,
|
|
4010
|
+
headers: result.headers,
|
|
4011
|
+
data: result.data
|
|
4012
|
+
});
|
|
4013
|
+
} catch (e) {
|
|
4014
|
+
return ctx.json({ error: String(e) }, 500);
|
|
4015
|
+
}
|
|
4016
|
+
});
|
|
4017
|
+
this.router.get("/", async (ctx) => {
|
|
4018
|
+
const uptimeSeconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
4019
|
+
const uptime = `${Math.floor(uptimeSeconds / 3600)}h ${Math.floor(uptimeSeconds % 3600 / 60)}m ${uptimeSeconds % 60}s`;
|
|
4020
|
+
const linkPattern = this.getLinkPattern();
|
|
4021
|
+
const template = await promises.readFile(Dashboard.getBasePath() + "/template.eta", "utf8");
|
|
4022
|
+
return ctx.html(this.eta.renderString(template, {
|
|
4023
|
+
metrics: this.metrics,
|
|
4024
|
+
uptime,
|
|
4025
|
+
rootPath: process.cwd(),
|
|
4026
|
+
linkPattern,
|
|
4027
|
+
headers: this.dashboardConfig.getRequestHeaders?.()
|
|
4028
|
+
}));
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
instrumentApp(app) {
|
|
4032
|
+
if (!app.getComponentRegistry) return;
|
|
4033
|
+
const registry = app.getComponentRegistry();
|
|
4034
|
+
this.assignIdsToRegistry(registry, "root");
|
|
4035
|
+
this.instrumented = true;
|
|
4036
|
+
}
|
|
4037
|
+
// Traverses registry, generates IDs, and attaches them to the actual function objects
|
|
4038
|
+
assignIdsToRegistry(node, parentId) {
|
|
4039
|
+
if (!node) return;
|
|
4040
|
+
const makeId = (type, parent, idx, name) => `${type}_${parent}_${idx}_${name.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
4041
|
+
node.middleware?.forEach((mw, idx) => {
|
|
4042
|
+
const id = makeId("mw", parentId, idx, mw.name);
|
|
4043
|
+
mw.id = id;
|
|
4044
|
+
if (mw._fn) mw._fn._debugId = id;
|
|
4045
|
+
});
|
|
4046
|
+
node.controllers?.forEach((ctrl, idx) => {
|
|
4047
|
+
const id = makeId("ctrl", parentId, idx, ctrl.name);
|
|
4048
|
+
ctrl.id = id;
|
|
4049
|
+
});
|
|
4050
|
+
node.routes?.forEach((r, idx) => {
|
|
4051
|
+
const id = makeId("route", parentId, idx, r.handlerName || "handler");
|
|
4052
|
+
r.id = id;
|
|
4053
|
+
if (r._fn) r._fn._debugId = id;
|
|
4054
|
+
});
|
|
4055
|
+
node.routers?.forEach((r, idx) => {
|
|
4056
|
+
const id = makeId("router", parentId, idx, r.path);
|
|
4057
|
+
r.id = id;
|
|
4058
|
+
this.assignIdsToRegistry(r.children, id);
|
|
4059
|
+
});
|
|
4060
|
+
}
|
|
4061
|
+
recordNodeMetric(id, type, duration, isError) {
|
|
4062
|
+
if (!this.metrics.nodeMetrics[id]) {
|
|
4063
|
+
this.metrics.nodeMetrics[id] = {
|
|
4064
|
+
id,
|
|
4065
|
+
type,
|
|
4066
|
+
requests: 0,
|
|
4067
|
+
totalTime: 0,
|
|
4068
|
+
failures: 0,
|
|
4069
|
+
name: id
|
|
4070
|
+
// simplify
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
const m = this.metrics.nodeMetrics[id];
|
|
4074
|
+
m.requests++;
|
|
4075
|
+
m.totalTime += duration;
|
|
4076
|
+
if (isError) m.failures++;
|
|
4077
|
+
}
|
|
4078
|
+
recordEdgeMetric(from, to) {
|
|
4079
|
+
const key = `${from}|${to}`;
|
|
4080
|
+
this.metrics.edgeMetrics[key] = (this.metrics.edgeMetrics[key] || 0) + 1;
|
|
4081
|
+
}
|
|
4082
|
+
getLinkPattern() {
|
|
4083
|
+
const term = process.env["TERM_PROGRAM"] || "";
|
|
4084
|
+
if (["vscode", "cursor", "antigravity"].some((t) => term.includes(t))) {
|
|
4085
|
+
return "vscode://file/{{absolute}}:{{line}}";
|
|
4086
|
+
}
|
|
4087
|
+
return "file:///{{absolute}}:{{line}}";
|
|
4088
|
+
}
|
|
4089
|
+
getHooks() {
|
|
4090
|
+
return {
|
|
4091
|
+
onRequestStart: (ctx) => {
|
|
4092
|
+
const app = this[$appRoot];
|
|
4093
|
+
if (!this.instrumented && app) {
|
|
4094
|
+
this.instrumentApp(app);
|
|
4095
|
+
}
|
|
4096
|
+
this.metrics.totalRequests++;
|
|
4097
|
+
this.metrics.activeRequests++;
|
|
4098
|
+
ctx._debugStartTime = performance.now();
|
|
4099
|
+
ctx[$debug] = new Collector(this);
|
|
4100
|
+
},
|
|
4101
|
+
onResponseEnd: async (ctx, response) => {
|
|
4102
|
+
this.metrics.activeRequests = Math.max(0, this.metrics.activeRequests - 1);
|
|
4103
|
+
const start = ctx._debugStartTime;
|
|
4104
|
+
let duration = 0;
|
|
4105
|
+
if (start) {
|
|
4106
|
+
duration = performance.now() - start;
|
|
4107
|
+
this.updateTiming(duration);
|
|
4108
|
+
}
|
|
4109
|
+
const isError = response.status >= 400;
|
|
4110
|
+
this.metricsCollector.recordRequest(duration, isError);
|
|
4111
|
+
if (response.status >= 400) {
|
|
4112
|
+
this.metrics.failedRequests++;
|
|
4113
|
+
if (response.status === 429) {
|
|
4114
|
+
const path2 = ctx.path;
|
|
4115
|
+
this.metrics.rateLimitedCounts[path2] = (this.metrics.rateLimitedCounts[path2] || 0) + 1;
|
|
4116
|
+
}
|
|
4117
|
+
try {
|
|
4118
|
+
const headers = {};
|
|
4119
|
+
if (ctx.request.headers && typeof ctx.request.headers.forEach === "function") {
|
|
4120
|
+
ctx.request.headers.forEach((v, k) => {
|
|
4121
|
+
headers[k] = v;
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
await datastore.set(new surrealdb.RecordId("failed_requests", ctx.requestId), {
|
|
4125
|
+
method: ctx.method,
|
|
4126
|
+
url: ctx.url.toString(),
|
|
4127
|
+
headers,
|
|
4128
|
+
status: response.status,
|
|
4129
|
+
timestamp: Date.now(),
|
|
4130
|
+
state: ctx.state
|
|
4131
|
+
// body?
|
|
4132
|
+
});
|
|
4133
|
+
} catch (e) {
|
|
4134
|
+
console.error("Failed to record failed request", e);
|
|
4135
|
+
}
|
|
4136
|
+
} else {
|
|
4137
|
+
this.metrics.successfulRequests++;
|
|
4138
|
+
}
|
|
4139
|
+
const logEntry = {
|
|
4140
|
+
method: ctx.method,
|
|
4141
|
+
url: ctx.url.toString(),
|
|
4142
|
+
status: response.status,
|
|
4143
|
+
duration,
|
|
4144
|
+
timestamp: Date.now(),
|
|
4145
|
+
handlerStack: ctx.handlerStack
|
|
4146
|
+
};
|
|
4147
|
+
this.metrics.logs.push(logEntry);
|
|
4148
|
+
try {
|
|
4149
|
+
await datastore.set(new surrealdb.RecordId("requests", ctx.requestId), logEntry);
|
|
4150
|
+
} catch (e) {
|
|
4151
|
+
console.error("Failed to record request log", e);
|
|
4152
|
+
}
|
|
4153
|
+
const retention = this.dashboardConfig.retentionMs ?? 72e5;
|
|
4154
|
+
const cutoff = Date.now() - retention;
|
|
4155
|
+
if (this.metrics.logs.length > 0 && this.metrics.logs[0].timestamp < cutoff) {
|
|
4156
|
+
this.metrics.logs = this.metrics.logs.filter((log) => log.timestamp >= cutoff);
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
4161
|
+
updateTiming(duration) {
|
|
4162
|
+
const alpha = 0.1;
|
|
4163
|
+
if (this.metrics.averageTotalTime_ms === 0) {
|
|
4164
|
+
this.metrics.averageTotalTime_ms = duration;
|
|
4165
|
+
} else {
|
|
4166
|
+
this.metrics.averageTotalTime_ms = alpha * duration + (1 - alpha) * this.metrics.averageTotalTime_ms;
|
|
4167
|
+
}
|
|
4168
|
+
this.metrics.recentTimings.push(duration);
|
|
4169
|
+
if (this.metrics.recentTimings.length > 50) {
|
|
4170
|
+
this.metrics.recentTimings.shift();
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
function unknownError(ctx) {
|
|
4175
|
+
return ctx.json({ error: "Unknown Error" }, 500);
|
|
4176
|
+
}
|
|
3336
4177
|
const eta = new eta$2.Eta();
|
|
3337
4178
|
class ScalarPlugin extends ShokupanRouter {
|
|
3338
4179
|
constructor(pluginOptions = {}) {
|
|
@@ -3431,23 +4272,23 @@ function Compression(options = {}) {
|
|
|
3431
4272
|
return next();
|
|
3432
4273
|
}
|
|
3433
4274
|
let response = await next();
|
|
3434
|
-
if (!(response instanceof Response) && ctx
|
|
3435
|
-
response = ctx
|
|
4275
|
+
if (!(response instanceof Response) && ctx[$finalResponse] instanceof Response) {
|
|
4276
|
+
response = ctx[$finalResponse];
|
|
3436
4277
|
}
|
|
3437
4278
|
if (response instanceof Response) {
|
|
3438
4279
|
if (response.headers.has("Content-Encoding")) return response;
|
|
3439
4280
|
let body;
|
|
3440
4281
|
let bodySize;
|
|
3441
|
-
if (ctx
|
|
3442
|
-
if (typeof ctx
|
|
3443
|
-
const encoded = new TextEncoder().encode(ctx
|
|
4282
|
+
if (ctx[$rawBody] !== void 0) {
|
|
4283
|
+
if (typeof ctx[$rawBody] === "string") {
|
|
4284
|
+
const encoded = new TextEncoder().encode(ctx[$rawBody]);
|
|
3444
4285
|
body = encoded;
|
|
3445
4286
|
bodySize = encoded.byteLength;
|
|
3446
|
-
} else if (ctx
|
|
3447
|
-
body = ctx
|
|
3448
|
-
bodySize = ctx.
|
|
4287
|
+
} else if (ctx[$rawBody] instanceof Uint8Array) {
|
|
4288
|
+
body = ctx[$rawBody];
|
|
4289
|
+
bodySize = ctx[$rawBody].byteLength;
|
|
3449
4290
|
} else {
|
|
3450
|
-
body = ctx
|
|
4291
|
+
body = ctx[$rawBody];
|
|
3451
4292
|
bodySize = body.byteLength;
|
|
3452
4293
|
}
|
|
3453
4294
|
} else {
|
|
@@ -4312,20 +5153,39 @@ function Session(options) {
|
|
|
4312
5153
|
return sessionMiddleware;
|
|
4313
5154
|
}
|
|
4314
5155
|
exports.$appRoot = $appRoot;
|
|
5156
|
+
exports.$bodyParseError = $bodyParseError;
|
|
5157
|
+
exports.$bodyParsed = $bodyParsed;
|
|
5158
|
+
exports.$bodyType = $bodyType;
|
|
5159
|
+
exports.$cachedBody = $cachedBody;
|
|
5160
|
+
exports.$cachedHost = $cachedHost;
|
|
5161
|
+
exports.$cachedHostname = $cachedHostname;
|
|
5162
|
+
exports.$cachedOrigin = $cachedOrigin;
|
|
5163
|
+
exports.$cachedProtocol = $cachedProtocol;
|
|
5164
|
+
exports.$cachedQuery = $cachedQuery;
|
|
4315
5165
|
exports.$childControllers = $childControllers;
|
|
4316
5166
|
exports.$childRouters = $childRouters;
|
|
4317
5167
|
exports.$controllerPath = $controllerPath;
|
|
5168
|
+
exports.$debug = $debug;
|
|
4318
5169
|
exports.$dispatch = $dispatch;
|
|
5170
|
+
exports.$eventMethods = $eventMethods;
|
|
5171
|
+
exports.$finalResponse = $finalResponse;
|
|
5172
|
+
exports.$io = $io;
|
|
4319
5173
|
exports.$isApplication = $isApplication;
|
|
4320
5174
|
exports.$isMounted = $isMounted;
|
|
4321
5175
|
exports.$isRouter = $isRouter;
|
|
4322
5176
|
exports.$middleware = $middleware;
|
|
4323
5177
|
exports.$mountPath = $mountPath;
|
|
4324
5178
|
exports.$parent = $parent;
|
|
5179
|
+
exports.$rawBody = $rawBody;
|
|
5180
|
+
exports.$requestId = $requestId;
|
|
4325
5181
|
exports.$routeArgs = $routeArgs;
|
|
5182
|
+
exports.$routeMatched = $routeMatched;
|
|
4326
5183
|
exports.$routeMethods = $routeMethods;
|
|
4327
5184
|
exports.$routeSpec = $routeSpec;
|
|
4328
5185
|
exports.$routes = $routes;
|
|
5186
|
+
exports.$socket = $socket;
|
|
5187
|
+
exports.$url = $url;
|
|
5188
|
+
exports.$ws = $ws;
|
|
4329
5189
|
exports.All = All;
|
|
4330
5190
|
exports.AuthPlugin = AuthPlugin;
|
|
4331
5191
|
exports.Body = Body;
|
|
@@ -4335,7 +5195,9 @@ exports.Container = Container;
|
|
|
4335
5195
|
exports.Controller = Controller;
|
|
4336
5196
|
exports.Cors = Cors;
|
|
4337
5197
|
exports.Ctx = Ctx;
|
|
5198
|
+
exports.Dashboard = Dashboard;
|
|
4338
5199
|
exports.Delete = Delete;
|
|
5200
|
+
exports.Event = Event;
|
|
4339
5201
|
exports.Get = Get;
|
|
4340
5202
|
exports.HTTPMethods = HTTPMethods;
|
|
4341
5203
|
exports.Head = Head;
|