shokupan 0.3.0 → 0.4.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/README.md +197 -24
- package/dist/context.d.ts +3 -1
- package/dist/index.cjs +63 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +63 -25
- package/dist/index.js.map +1 -1
- package/dist/plugins/debugview/plugin.d.ts +1 -0
- package/dist/router.d.ts +19 -8
- package/dist/shokupan.d.ts +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
# Shokupan 🍞
|
|
2
2
|
|
|
3
|
-
> A
|
|
3
|
+
> A delightful, type-safe web framework for Bun
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Built for Developer Experience**
|
|
6
|
+
Shokupan is designed to make building APIs delightful again. With zero-config defaults, instant startup times, and full type safety out of the box, you can focus on building your product, not configuring your framework.
|
|
6
7
|
|
|
7
8
|
### Note: Shokupan is still in alpha and is not guaranteed to be stable. Please use with caution. We will be adding more features and APIs in the future. Please file an issue if you find any bugs or have suggestions for improvement.
|
|
8
9
|
|
|
9
10
|
## ✨ Features
|
|
10
11
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
12
|
+
- 🎯 **TypeScript First** - End-to-end type safety with decorators and generics. No manual types needed.
|
|
13
|
+
- 🛠️ **Zero Config** - Works effectively out of the box. No complex setup or boilerplate.
|
|
14
|
+
- 🚀 **Built for Bun** - Native [Bun](https://bun.sh/) performance with instant startup.
|
|
15
|
+
- 🔍 **Debug Dashboard** - Visual inspector for your routes, middleware, and request flow.
|
|
16
|
+
- 📝 **Auto OpenAPI** - Generate [OpenAPI](https://www.openapis.org/) specs automatically from routes.
|
|
17
|
+
- 🔌 **Rich Plugin System** - CORS, Sessions, Auth, Validation, Rate Limiting, and more.
|
|
18
|
+
- 🌐 **Flexible Routing** - Express-style routes or decorator-based controllers.
|
|
19
|
+
- 🔀 **Express Compatible** - Works with [Express](https://expressjs.com/) middleware patterns.
|
|
20
|
+
- 📊 **Built-in Telemetry** - [OpenTelemetry](https://opentelemetry.io/) instrumentation out of the box.
|
|
21
|
+
- 🔐 **OAuth2 Support** - GitHub, Google, Microsoft, Apple, Auth0, Okta.
|
|
22
|
+
- ✅ **Multi-validator Support** - Zod, Ajv, TypeBox, Valibot.
|
|
23
|
+
- 📚 **OpenAPI Docs** - Beautiful OpenAPI documentation with [Scalar](https://scalar.dev/).
|
|
24
|
+
- ⏩ **Short shift** - Very simple migration from [Express](https://expressjs.com/) or [NestJS](https://nestjs.com/) to Shokupan.
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+

|
|
24
27
|
|
|
25
|
-
```bash
|
|
26
|
-
bun add shokupan
|
|
27
|
-
```
|
|
28
28
|
|
|
29
29
|
## 🚀 Quick Start
|
|
30
30
|
|
|
31
|
+
> Bun and TypeScript are recommended for Shokupan, though it also supports Node.js and standard JavaScript.
|
|
32
|
+
|
|
31
33
|
```typescript
|
|
32
34
|
import { Shokupan } from 'shokupan';
|
|
33
35
|
|
|
@@ -829,6 +831,119 @@ app.mount('/docs', new ScalarPlugin({
|
|
|
829
831
|
|
|
830
832
|
The Scalar plugin automatically generates OpenAPI documentation from your routes and controllers!
|
|
831
833
|
|
|
834
|
+
### Proxy
|
|
835
|
+
|
|
836
|
+
Create a reverse proxy to forward requests to another server:
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
import { Proxy } from 'shokupan';
|
|
840
|
+
|
|
841
|
+
app.use('/api/v1', Proxy({
|
|
842
|
+
target: 'https://api.example.com',
|
|
843
|
+
changeOrigin: true,
|
|
844
|
+
pathRewrite: (path) => path.replace('/api/v1', ''),
|
|
845
|
+
headers: {
|
|
846
|
+
'X-Custom-Header': 'Proxy'
|
|
847
|
+
}
|
|
848
|
+
}));
|
|
849
|
+
|
|
850
|
+
// Proxy WebSockets
|
|
851
|
+
app.use('/socket', Proxy({
|
|
852
|
+
target: 'ws://ws.example.com',
|
|
853
|
+
ws: true
|
|
854
|
+
}));
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### OpenAPI Validator
|
|
858
|
+
|
|
859
|
+
Validate incoming requests against your generated OpenAPI specification:
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
import { enableOpenApiValidation } from 'shokupan';
|
|
863
|
+
|
|
864
|
+
const app = new Shokupan({ enableOpenApiGen: true });
|
|
865
|
+
|
|
866
|
+
// Enable validation middleware
|
|
867
|
+
// This validates Body, Query, Params, and Headers against your OpenAPI definitions
|
|
868
|
+
enableOpenApiValidation(app);
|
|
869
|
+
|
|
870
|
+
app.post('/users', {
|
|
871
|
+
parameters: [
|
|
872
|
+
{ name: 'apiKey', in: 'header', required: true, schema: { type: 'string' } }
|
|
873
|
+
],
|
|
874
|
+
requestBody: {
|
|
875
|
+
content: {
|
|
876
|
+
'application/json': {
|
|
877
|
+
schema: {
|
|
878
|
+
type: 'object',
|
|
879
|
+
required: ['name'],
|
|
880
|
+
properties: {
|
|
881
|
+
name: { type: 'string', minLength: 3 }
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}, (ctx) => {
|
|
888
|
+
return { success: true };
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// Invalid requests will throw a ValidationError (400 Bad Request)
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### Idempotency
|
|
895
|
+
|
|
896
|
+
Ensure that multiple identical requests do not result in different outcomes (e.g., duplicate payments). This middleware caches the response of the first request and returns it for subsequent requests with the same idempotency key.
|
|
897
|
+
|
|
898
|
+
```typescript
|
|
899
|
+
import { Idempotency } from 'shokupan';
|
|
900
|
+
|
|
901
|
+
app.post('/payments',
|
|
902
|
+
Idempotency({
|
|
903
|
+
header: 'Idempotency-Key', // default
|
|
904
|
+
ttl: 24 * 60 * 60 * 1000 // default 24h
|
|
905
|
+
}),
|
|
906
|
+
async (ctx) => {
|
|
907
|
+
// Process payment...
|
|
908
|
+
return { status: 'charged' };
|
|
909
|
+
}
|
|
910
|
+
);
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
### Failed Request Recorder
|
|
914
|
+
|
|
915
|
+
Automatically record failed requests (500s) for debugging and replay purposes.
|
|
916
|
+
|
|
917
|
+
```typescript
|
|
918
|
+
import { FailedRequestRecorder } from 'shokupan';
|
|
919
|
+
|
|
920
|
+
app.use(FailedRequestRecorder({
|
|
921
|
+
maxCapacity: 1000,
|
|
922
|
+
ttl: 86400000 // 1 day
|
|
923
|
+
}));
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
This works great when combined with the Debug Dashboard.
|
|
927
|
+
|
|
928
|
+
### Debug Dashboard
|
|
929
|
+
|
|
930
|
+
A visual dashboard to inspect your application, view metrics, analyze the middleware graph, and replay failed requests.
|
|
931
|
+
|
|
932
|
+
```typescript
|
|
933
|
+
import { DebugDashboard } from 'shokupan/plugins/debugview';
|
|
934
|
+
|
|
935
|
+
// Mount the dashboard
|
|
936
|
+
app.mount('/debug', new DebugDashboard({
|
|
937
|
+
retentionMs: 2 * 60 * 60 * 1000, // Keep 2 hours of logs
|
|
938
|
+
getRequestHeaders: () => ({
|
|
939
|
+
'Authorization': 'Bearer ...' // Headers to using when replaying requests and accessing data APIs
|
|
940
|
+
})
|
|
941
|
+
}));
|
|
942
|
+
|
|
943
|
+
// Available at http://localhost:3000/debug
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
|
|
832
947
|
## 🚀 Advanced Features
|
|
833
948
|
|
|
834
949
|
### Dependency Injection
|
|
@@ -999,8 +1114,40 @@ provider.addSpanProcessor(
|
|
|
999
1114
|
provider.register();
|
|
1000
1115
|
```
|
|
1001
1116
|
|
|
1117
|
+
### Server Factory (Node.js & Deno)
|
|
1118
|
+
|
|
1119
|
+
Run Shokupan on Node.js or Deno using the server adapter:
|
|
1120
|
+
|
|
1121
|
+
```typescript
|
|
1122
|
+
import { Shokupan, createHttpServer } from 'shokupan';
|
|
1123
|
+
|
|
1124
|
+
const app = new Shokupan({
|
|
1125
|
+
// Use Node.js http module
|
|
1126
|
+
serverFactory: createHttpServer()
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
app.get('/', () => ({ message: 'Running on Node!' }));
|
|
1130
|
+
|
|
1131
|
+
app.listen(3000);
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
### Automatic Backpressure
|
|
1135
|
+
|
|
1136
|
+
Protect your server from overload by shedding load when CPU usage is high:
|
|
1137
|
+
|
|
1138
|
+
```typescript
|
|
1139
|
+
const app = new Shokupan({
|
|
1140
|
+
// Monitor CPU and reject requests when usage > 80%
|
|
1141
|
+
autoBackpressureFeedback: true,
|
|
1142
|
+
autoBackpressureLevel: 80
|
|
1143
|
+
});
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
When the threshold is reached, the server will return `429 Too Many Requests`.
|
|
1147
|
+
|
|
1002
1148
|
## 📦 Migration Guides
|
|
1003
1149
|
|
|
1150
|
+
|
|
1004
1151
|
### From Express
|
|
1005
1152
|
|
|
1006
1153
|
Shokupan is designed to feel familiar to Express developers. Here's how to migrate:
|
|
@@ -1397,6 +1544,10 @@ app.use(useExpress(compression()));
|
|
|
1397
1544
|
## 🧪 Testing
|
|
1398
1545
|
|
|
1399
1546
|
Shokupan applications are easy to test using Bun's built-in test runner.
|
|
1547
|
+
It can directly pass requests to the application or a router without requiring the
|
|
1548
|
+
server to fully start, bind to a port or listen for connections. This makes mocking
|
|
1549
|
+
the server unnecessary and allows for faster and more reliable testing. Additionally
|
|
1550
|
+
you can directly test authenticated endpoints without the need for a session or cookie.
|
|
1400
1551
|
|
|
1401
1552
|
```typescript
|
|
1402
1553
|
import { describe, it, expect } from 'bun:test';
|
|
@@ -1427,13 +1578,13 @@ Since Shokupan is built on Bun, deployment is straightforward.
|
|
|
1427
1578
|
### Using Bun
|
|
1428
1579
|
|
|
1429
1580
|
```bash
|
|
1430
|
-
bun run src/
|
|
1581
|
+
bun run src/main.ts
|
|
1431
1582
|
```
|
|
1432
1583
|
|
|
1433
1584
|
### Docker
|
|
1434
1585
|
|
|
1435
1586
|
```dockerfile
|
|
1436
|
-
FROM oven/bun:1
|
|
1587
|
+
FROM oven/bun:1-alpine
|
|
1437
1588
|
|
|
1438
1589
|
WORKDIR /app
|
|
1439
1590
|
|
|
@@ -1442,7 +1593,7 @@ RUN bun install --production
|
|
|
1442
1593
|
|
|
1443
1594
|
EXPOSE 3000
|
|
1444
1595
|
|
|
1445
|
-
CMD ["bun", "run", "src/
|
|
1596
|
+
CMD ["bun", "run", "src/main.ts"]
|
|
1446
1597
|
```
|
|
1447
1598
|
|
|
1448
1599
|
## 🛠️ CLI Tools
|
|
@@ -1530,6 +1681,14 @@ const app = new Shokupan(config?: ShokupanConfig);
|
|
|
1530
1681
|
- `hostname?: string` - Hostname (default: "localhost")
|
|
1531
1682
|
- `development?: boolean` - Development mode (default: auto-detect)
|
|
1532
1683
|
- `enableAsyncLocalStorage?: boolean` - Enable async context tracking
|
|
1684
|
+
- `enableTracing?: boolean` - Enable OpenTelemetry tracing
|
|
1685
|
+
- `enableOpenApiGen?: boolean` - Enable OpenAPI spec generation (default: true)
|
|
1686
|
+
- `controllersOnly?: boolean` - If true, only allows controllers, disabling app.get/post/etc (default: false)
|
|
1687
|
+
- `requestTimeout?: number` - Global request timeout (ms)
|
|
1688
|
+
- `readTimeout?: number` - Request body read timeout (ms)
|
|
1689
|
+
- `serverFactory?: ServerFactory` - Custom server factory (for Node.js/Deno support)
|
|
1690
|
+
- `autoBackpressureFeedback?: boolean` - Enable automatic load shedding based on CPU usage
|
|
1691
|
+
- `autoBackpressureLevel?: number` - CPU usage % threshold for backpressure (default: 60)
|
|
1533
1692
|
- `logger?: Logger` - Custom logger instance
|
|
1534
1693
|
|
|
1535
1694
|
**Methods:**
|
|
@@ -1551,7 +1710,13 @@ const app = new Shokupan(config?: ShokupanConfig);
|
|
|
1551
1710
|
|
|
1552
1711
|
### ShokupanRouter Class
|
|
1553
1712
|
|
|
1554
|
-
Router for grouping
|
|
1713
|
+
Router for grouping operations, applying middleware, and mounting controllers. Additionally
|
|
1714
|
+
they are effective for creating sub-applications that are independently tested. Routers can
|
|
1715
|
+
have OpenAPI spec applied to all endpoints of the router. Additionally they can be mounted
|
|
1716
|
+
onto the main application or other routers.
|
|
1717
|
+
|
|
1718
|
+
When a router is mounted to an app, if you are using the DebugView plugin you will be able to
|
|
1719
|
+
see it under the Registry tab and the Graph tab.
|
|
1555
1720
|
|
|
1556
1721
|
```typescript
|
|
1557
1722
|
const router = new ShokupanRouter(config?: ShokupanRouteConfig);
|
|
@@ -1592,6 +1757,13 @@ Request context object.
|
|
|
1592
1757
|
- `state: Record<string, any>` - Shared state object
|
|
1593
1758
|
- `session: any` - Session data (with session plugin)
|
|
1594
1759
|
- `response: ShokupanResponse` - Response builder
|
|
1760
|
+
- `ip: string` - Client IP address
|
|
1761
|
+
- `hostname: string` - Hostname (e.g. "localhost")
|
|
1762
|
+
- `host: string` - Host (e.g. "localhost:3000")
|
|
1763
|
+
- `protocol: string` - Protocol (http/https)
|
|
1764
|
+
- `secure: boolean` - Whether the request is secure over HTTPS
|
|
1765
|
+
- `origin: string` - Origin URL
|
|
1766
|
+
- `signal: AbortSignal` - Request abort signal (for standard fetch requests)
|
|
1595
1767
|
|
|
1596
1768
|
**Methods:**
|
|
1597
1769
|
- `set(name: string, value: string): ShokupanContext` - Set a response header
|
|
@@ -1602,6 +1774,7 @@ Request context object.
|
|
|
1602
1774
|
- `json(data: any, status?: number): ShokupanContext` - Return JSON response
|
|
1603
1775
|
- `text(data: string, status?: number): ShokupanContext` - Return text response
|
|
1604
1776
|
- `html(data: string, status?: number): ShokupanContext` - Return HTML response
|
|
1777
|
+
- `jsx(element: any): ShokupanContext` - Render a JSX element
|
|
1605
1778
|
- `redirect(url: string, status?: number): ShokupanContext` - Redirect response
|
|
1606
1779
|
- `file(path: string, fileOptions?: BlobPropertyBag, responseOptions?: ResponseInit): Response` - Return file response
|
|
1607
1780
|
|
|
@@ -1639,7 +1812,6 @@ Container.clear();
|
|
|
1639
1812
|
- 🚧 **Framework Plugins** - Drop-in adapters for [Express](https://expressjs.com/), [Koa](https://koajs.com/), and [Elysia](https://elysiajs.com/)
|
|
1640
1813
|
- 🚧 **Enhanced WebSockets** - Event support and HTTP simulation
|
|
1641
1814
|
- 🚧 **Benchmarks** - Comprehensive performance comparisons
|
|
1642
|
-
- 🚧 **Scaling** - Automatic clustering support
|
|
1643
1815
|
- 🚧 **RPC Support** - [tRPC](https://trpc.io/) and [gRPC](https://grpc.io/) integration
|
|
1644
1816
|
- 🚧 **Binary Formats** - [Protobuf](https://protobuf.dev/) and [MessagePack](https://msgpack.org/) support
|
|
1645
1817
|
- 🚧 **Reliability** - Circuit breaker pattern for resilience
|
|
@@ -1652,7 +1824,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
1652
1824
|
1. Fork the repository
|
|
1653
1825
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1654
1826
|
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
1655
|
-
4.
|
|
1827
|
+
4. Publish the branch (`git push origin feature/amazing-feature`)
|
|
1656
1828
|
5. Open a Pull Request
|
|
1657
1829
|
|
|
1658
1830
|
## 📝 License
|
|
@@ -1664,7 +1836,8 @@ MIT License - see the [LICENSE](LICENSE) file for details.
|
|
|
1664
1836
|
- Inspired by [Express](https://expressjs.com/), [Koa](https://koajs.com/), [NestJS](https://nestjs.com/), and [Elysia](https://elysiajs.com/)
|
|
1665
1837
|
- Built for the amazing [Bun](https://bun.sh/) runtime
|
|
1666
1838
|
- Powered by [Arctic](https://github.com/pilcrowonpaper/arctic) for OAuth2 support
|
|
1839
|
+
- Tests and Benchmarks created with Antigravity
|
|
1667
1840
|
|
|
1668
1841
|
---
|
|
1669
1842
|
|
|
1670
|
-
**Made with
|
|
1843
|
+
**Made with ❤️ by the Shokupan team**
|
package/dist/context.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface HandlerStackItem {
|
|
|
9
9
|
file: string;
|
|
10
10
|
line: number;
|
|
11
11
|
stateChanges?: Record<string, any>;
|
|
12
|
+
startTime?: number;
|
|
13
|
+
duration?: number;
|
|
12
14
|
}
|
|
13
15
|
export interface DebugCollector {
|
|
14
16
|
trackStep(id: string | undefined, type: string, duration: number, status: 'success' | 'error', error?: any): void;
|
|
@@ -51,7 +53,7 @@ export declare class ShokupanContext<State extends Record<string, any> = Record<
|
|
|
51
53
|
/**
|
|
52
54
|
* Client IP address
|
|
53
55
|
*/
|
|
54
|
-
get ip():
|
|
56
|
+
get ip(): any;
|
|
55
57
|
/**
|
|
56
58
|
* Request hostname (e.g. "localhost")
|
|
57
59
|
*/
|
package/dist/index.cjs
CHANGED
|
@@ -1397,6 +1397,7 @@ const ready = db.connect(engine, { namespace: "vendor", database: "shokupan" }).
|
|
|
1397
1397
|
DEFINE TABLE OVERWRITE users SCHEMALESS COMMENT "Created by Shokupan";
|
|
1398
1398
|
DEFINE TABLE OVERWRITE idempotency_keys SCHEMALESS COMMENT "Created by Shokupan";
|
|
1399
1399
|
DEFINE TABLE OVERWRITE middleware_tracking SCHEMALESS COMMENT "Created by Shokupan";
|
|
1400
|
+
DEFINE TABLE OVERWRITE requests SCHEMALESS COMMENT "Created by Shokupan";
|
|
1400
1401
|
`);
|
|
1401
1402
|
});
|
|
1402
1403
|
const datastore = {
|
|
@@ -1506,17 +1507,28 @@ class ShokupanRouter {
|
|
|
1506
1507
|
currentGuards = [];
|
|
1507
1508
|
// Registry Accessor
|
|
1508
1509
|
getComponentRegistry() {
|
|
1509
|
-
const
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1510
|
+
const controllerRoutesMap = /* @__PURE__ */ new Map();
|
|
1511
|
+
const localRoutes = [];
|
|
1512
|
+
for (const r of this[$routes]) {
|
|
1513
|
+
const entry = {
|
|
1514
|
+
type: "route",
|
|
1515
|
+
path: r.path,
|
|
1516
|
+
method: r.method,
|
|
1517
|
+
metadata: r.metadata,
|
|
1518
|
+
handlerName: r.handler.name,
|
|
1519
|
+
tags: r.handlerSpec?.tags,
|
|
1520
|
+
order: r.order,
|
|
1521
|
+
_fn: r.handler
|
|
1522
|
+
};
|
|
1523
|
+
if (r.controller) {
|
|
1524
|
+
if (!controllerRoutesMap.has(r.controller)) {
|
|
1525
|
+
controllerRoutesMap.set(r.controller, []);
|
|
1526
|
+
}
|
|
1527
|
+
controllerRoutesMap.get(r.controller).push(entry);
|
|
1528
|
+
} else {
|
|
1529
|
+
localRoutes.push(entry);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1520
1532
|
const mw = this.middleware;
|
|
1521
1533
|
const middleware = mw ? mw.map((m) => ({
|
|
1522
1534
|
name: m.name || "middleware",
|
|
@@ -1532,18 +1544,19 @@ class ShokupanRouter {
|
|
|
1532
1544
|
children: r.getComponentRegistry()
|
|
1533
1545
|
}));
|
|
1534
1546
|
const controllers = this[$childControllers].map((c) => {
|
|
1547
|
+
const routes = controllerRoutesMap.get(c) || [];
|
|
1535
1548
|
return {
|
|
1536
1549
|
type: "controller",
|
|
1537
1550
|
path: c[$mountPath] || "/",
|
|
1538
1551
|
name: c.constructor.name,
|
|
1539
|
-
metadata: c.metadata
|
|
1540
|
-
|
|
1552
|
+
metadata: c.metadata,
|
|
1553
|
+
children: { routes }
|
|
1541
1554
|
};
|
|
1542
1555
|
});
|
|
1543
1556
|
return {
|
|
1544
1557
|
metadata: this.metadata,
|
|
1545
1558
|
middleware,
|
|
1546
|
-
routes,
|
|
1559
|
+
routes: localRoutes,
|
|
1547
1560
|
routers,
|
|
1548
1561
|
controllers
|
|
1549
1562
|
};
|
|
@@ -1633,7 +1646,7 @@ class ShokupanRouter {
|
|
|
1633
1646
|
const decoratedArgs = instance[$routeArgs] || proto && proto[$routeArgs];
|
|
1634
1647
|
const methodMiddlewareMap = instance[$middleware] || proto && proto[$middleware];
|
|
1635
1648
|
let routesAttached = 0;
|
|
1636
|
-
for (const name of methods) {
|
|
1649
|
+
for (const name of Array.from(methods)) {
|
|
1637
1650
|
if (name === "constructor") continue;
|
|
1638
1651
|
if (["arguments", "caller", "callee"].includes(name)) continue;
|
|
1639
1652
|
const originalHandler = instance[name];
|
|
@@ -1767,7 +1780,7 @@ class ShokupanRouter {
|
|
|
1767
1780
|
const decoratedSpecs = instance[$routeSpec] || proto && proto[$routeSpec];
|
|
1768
1781
|
const userSpec = decoratedSpecs && decoratedSpecs.get(name);
|
|
1769
1782
|
const spec = { tags: [tagName], ...userSpec };
|
|
1770
|
-
this.add({ method, path: normalizedPath, handler: finalHandler, spec });
|
|
1783
|
+
this.add({ method, path: normalizedPath, handler: finalHandler, spec, controller: instance });
|
|
1771
1784
|
}
|
|
1772
1785
|
}
|
|
1773
1786
|
if (routesAttached === 0) {
|
|
@@ -1988,8 +2001,23 @@ class ShokupanRouter {
|
|
|
1988
2001
|
* @param handler - Route handler function
|
|
1989
2002
|
* @param requestTimeout - Timeout for this route in milliseconds
|
|
1990
2003
|
*/
|
|
1991
|
-
add({ method, path: path2, spec, handler, regex: customRegex, group, requestTimeout, renderer }) {
|
|
2004
|
+
add({ method, path: path2, spec, handler, regex: customRegex, group, requestTimeout, renderer, controller }) {
|
|
1992
2005
|
const { regex, keys } = customRegex ? { regex: customRegex, keys: [] } : this.parsePath(path2);
|
|
2006
|
+
if (this.currentGuards.length > 0) {
|
|
2007
|
+
spec = spec || {};
|
|
2008
|
+
for (const guard of this.currentGuards) {
|
|
2009
|
+
if (guard.spec) {
|
|
2010
|
+
if (guard.spec.responses) {
|
|
2011
|
+
spec.responses = spec.responses || {};
|
|
2012
|
+
Object.assign(spec.responses, guard.spec.responses);
|
|
2013
|
+
}
|
|
2014
|
+
if (guard.spec.security) {
|
|
2015
|
+
spec.security = spec.security || [];
|
|
2016
|
+
spec.security.push(...guard.spec.security);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
1993
2021
|
let wrappedHandler = handler;
|
|
1994
2022
|
const routeGuards = [...this.currentGuards];
|
|
1995
2023
|
const effectiveTimeout = requestTimeout ?? this.requestTimeout ?? this.rootConfig?.requestTimeout;
|
|
@@ -2115,7 +2143,8 @@ class ShokupanRouter {
|
|
|
2115
2143
|
metadata: {
|
|
2116
2144
|
file,
|
|
2117
2145
|
line
|
|
2118
|
-
}
|
|
2146
|
+
},
|
|
2147
|
+
controller
|
|
2119
2148
|
});
|
|
2120
2149
|
this.trie.insert(method, path2, bakedHandler);
|
|
2121
2150
|
return this;
|
|
@@ -2349,12 +2378,21 @@ class Shokupan extends ShokupanRouter {
|
|
|
2349
2378
|
const c = ctx;
|
|
2350
2379
|
if (c.handlerStack && c.app?.applicationConfig.enableMiddlewareTracking) {
|
|
2351
2380
|
const metadata = middleware.metadata || {};
|
|
2352
|
-
|
|
2381
|
+
const start = performance.now();
|
|
2382
|
+
const item = {
|
|
2353
2383
|
name: metadata.pluginName ? `${metadata.pluginName} (${metadata.name})` : metadata.name || middleware.name || "middleware",
|
|
2354
2384
|
file: metadata.file || file,
|
|
2355
2385
|
line: metadata.line || line,
|
|
2356
|
-
isBuiltin: metadata.isBuiltin
|
|
2357
|
-
|
|
2386
|
+
isBuiltin: metadata.isBuiltin,
|
|
2387
|
+
startTime: start,
|
|
2388
|
+
duration: -1
|
|
2389
|
+
};
|
|
2390
|
+
c.handlerStack.push(item);
|
|
2391
|
+
try {
|
|
2392
|
+
return await middleware(ctx, next);
|
|
2393
|
+
} finally {
|
|
2394
|
+
item.duration = performance.now() - start;
|
|
2395
|
+
}
|
|
2358
2396
|
}
|
|
2359
2397
|
return middleware(ctx, next);
|
|
2360
2398
|
};
|
|
@@ -2895,14 +2933,14 @@ function Compression(options = {}) {
|
|
|
2895
2933
|
if (ctx._rawBody !== void 0) {
|
|
2896
2934
|
if (typeof ctx._rawBody === "string") {
|
|
2897
2935
|
const encoded = new TextEncoder().encode(ctx._rawBody);
|
|
2898
|
-
body = encoded
|
|
2936
|
+
body = encoded;
|
|
2899
2937
|
bodySize = encoded.byteLength;
|
|
2900
2938
|
} else if (ctx._rawBody instanceof Uint8Array) {
|
|
2901
|
-
body = ctx._rawBody
|
|
2939
|
+
body = ctx._rawBody;
|
|
2902
2940
|
bodySize = ctx._rawBody.byteLength;
|
|
2903
2941
|
} else {
|
|
2904
2942
|
body = ctx._rawBody;
|
|
2905
|
-
bodySize =
|
|
2943
|
+
bodySize = body.byteLength;
|
|
2906
2944
|
}
|
|
2907
2945
|
} else {
|
|
2908
2946
|
body = await response.arrayBuffer();
|
|
@@ -2912,7 +2950,7 @@ function Compression(options = {}) {
|
|
|
2912
2950
|
return new Response(body, {
|
|
2913
2951
|
status: response.status,
|
|
2914
2952
|
statusText: response.statusText,
|
|
2915
|
-
headers: response.headers
|
|
2953
|
+
headers: new Headers(response.headers)
|
|
2916
2954
|
});
|
|
2917
2955
|
}
|
|
2918
2956
|
let compressed;
|