shokupan 0.3.0 → 0.4.5
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 +203 -31
- package/dist/context.d.ts +3 -1
- package/dist/index.cjs +65 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +65 -26
- package/dist/index.js.map +1 -1
- package/dist/plugins/debugview/plugin.d.ts +1 -0
- package/dist/plugins/scalar.d.ts +2 -2
- 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,44 +1,45 @@
|
|
|
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';
|
|
35
|
+
const app = new Shokupan();
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
development: true
|
|
37
|
-
});
|
|
37
|
+
app.get('/', (ctx) => ({ message: 'Hello, World!' }));
|
|
38
|
+
app.get('/hello', (ctx) => "world");
|
|
38
39
|
|
|
39
|
-
app.
|
|
40
|
-
|
|
41
|
-
});
|
|
40
|
+
app.mount('/scalar', new ScalarPlugin({
|
|
41
|
+
enableStaticAnalysis: true
|
|
42
|
+
}));
|
|
42
43
|
|
|
43
44
|
app.listen();
|
|
44
45
|
```
|
|
@@ -829,6 +830,119 @@ app.mount('/docs', new ScalarPlugin({
|
|
|
829
830
|
|
|
830
831
|
The Scalar plugin automatically generates OpenAPI documentation from your routes and controllers!
|
|
831
832
|
|
|
833
|
+
### Proxy
|
|
834
|
+
|
|
835
|
+
Create a reverse proxy to forward requests to another server:
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
import { Proxy } from 'shokupan';
|
|
839
|
+
|
|
840
|
+
app.use('/api/v1', Proxy({
|
|
841
|
+
target: 'https://api.example.com',
|
|
842
|
+
changeOrigin: true,
|
|
843
|
+
pathRewrite: (path) => path.replace('/api/v1', ''),
|
|
844
|
+
headers: {
|
|
845
|
+
'X-Custom-Header': 'Proxy'
|
|
846
|
+
}
|
|
847
|
+
}));
|
|
848
|
+
|
|
849
|
+
// Proxy WebSockets
|
|
850
|
+
app.use('/socket', Proxy({
|
|
851
|
+
target: 'ws://ws.example.com',
|
|
852
|
+
ws: true
|
|
853
|
+
}));
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
### OpenAPI Validator
|
|
857
|
+
|
|
858
|
+
Validate incoming requests against your generated OpenAPI specification:
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
import { enableOpenApiValidation } from 'shokupan';
|
|
862
|
+
|
|
863
|
+
const app = new Shokupan({ enableOpenApiGen: true });
|
|
864
|
+
|
|
865
|
+
// Enable validation middleware
|
|
866
|
+
// This validates Body, Query, Params, and Headers against your OpenAPI definitions
|
|
867
|
+
enableOpenApiValidation(app);
|
|
868
|
+
|
|
869
|
+
app.post('/users', {
|
|
870
|
+
parameters: [
|
|
871
|
+
{ name: 'apiKey', in: 'header', required: true, schema: { type: 'string' } }
|
|
872
|
+
],
|
|
873
|
+
requestBody: {
|
|
874
|
+
content: {
|
|
875
|
+
'application/json': {
|
|
876
|
+
schema: {
|
|
877
|
+
type: 'object',
|
|
878
|
+
required: ['name'],
|
|
879
|
+
properties: {
|
|
880
|
+
name: { type: 'string', minLength: 3 }
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}, (ctx) => {
|
|
887
|
+
return { success: true };
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// Invalid requests will throw a ValidationError (400 Bad Request)
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### Idempotency
|
|
894
|
+
|
|
895
|
+
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.
|
|
896
|
+
|
|
897
|
+
```typescript
|
|
898
|
+
import { Idempotency } from 'shokupan';
|
|
899
|
+
|
|
900
|
+
app.post('/payments',
|
|
901
|
+
Idempotency({
|
|
902
|
+
header: 'Idempotency-Key', // default
|
|
903
|
+
ttl: 24 * 60 * 60 * 1000 // default 24h
|
|
904
|
+
}),
|
|
905
|
+
async (ctx) => {
|
|
906
|
+
// Process payment...
|
|
907
|
+
return { status: 'charged' };
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
### Failed Request Recorder
|
|
913
|
+
|
|
914
|
+
Automatically record failed requests (500s) for debugging and replay purposes.
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
import { FailedRequestRecorder } from 'shokupan';
|
|
918
|
+
|
|
919
|
+
app.use(FailedRequestRecorder({
|
|
920
|
+
maxCapacity: 1000,
|
|
921
|
+
ttl: 86400000 // 1 day
|
|
922
|
+
}));
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
This works great when combined with the Debug Dashboard.
|
|
926
|
+
|
|
927
|
+
### Debug Dashboard
|
|
928
|
+
|
|
929
|
+
A visual dashboard to inspect your application, view metrics, analyze the middleware graph, and replay failed requests.
|
|
930
|
+
|
|
931
|
+
```typescript
|
|
932
|
+
import { DebugDashboard } from 'shokupan/plugins/debugview';
|
|
933
|
+
|
|
934
|
+
// Mount the dashboard
|
|
935
|
+
app.mount('/debug', new DebugDashboard({
|
|
936
|
+
retentionMs: 2 * 60 * 60 * 1000, // Keep 2 hours of logs
|
|
937
|
+
getRequestHeaders: () => ({
|
|
938
|
+
'Authorization': 'Bearer ...' // Headers to using when replaying requests and accessing data APIs
|
|
939
|
+
})
|
|
940
|
+
}));
|
|
941
|
+
|
|
942
|
+
// Available at http://localhost:3000/debug
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
|
|
832
946
|
## 🚀 Advanced Features
|
|
833
947
|
|
|
834
948
|
### Dependency Injection
|
|
@@ -999,8 +1113,40 @@ provider.addSpanProcessor(
|
|
|
999
1113
|
provider.register();
|
|
1000
1114
|
```
|
|
1001
1115
|
|
|
1116
|
+
### Server Factory (Node.js & Deno)
|
|
1117
|
+
|
|
1118
|
+
Run Shokupan on Node.js or Deno using the server adapter:
|
|
1119
|
+
|
|
1120
|
+
```typescript
|
|
1121
|
+
import { Shokupan, createHttpServer } from 'shokupan';
|
|
1122
|
+
|
|
1123
|
+
const app = new Shokupan({
|
|
1124
|
+
// Use Node.js http module
|
|
1125
|
+
serverFactory: createHttpServer()
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
app.get('/', () => ({ message: 'Running on Node!' }));
|
|
1129
|
+
|
|
1130
|
+
app.listen(3000);
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
### Automatic Backpressure
|
|
1134
|
+
|
|
1135
|
+
Protect your server from overload by shedding load when CPU usage is high:
|
|
1136
|
+
|
|
1137
|
+
```typescript
|
|
1138
|
+
const app = new Shokupan({
|
|
1139
|
+
// Monitor CPU and reject requests when usage > 80%
|
|
1140
|
+
autoBackpressureFeedback: true,
|
|
1141
|
+
autoBackpressureLevel: 80
|
|
1142
|
+
});
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
When the threshold is reached, the server will return `429 Too Many Requests`.
|
|
1146
|
+
|
|
1002
1147
|
## 📦 Migration Guides
|
|
1003
1148
|
|
|
1149
|
+
|
|
1004
1150
|
### From Express
|
|
1005
1151
|
|
|
1006
1152
|
Shokupan is designed to feel familiar to Express developers. Here's how to migrate:
|
|
@@ -1397,6 +1543,10 @@ app.use(useExpress(compression()));
|
|
|
1397
1543
|
## 🧪 Testing
|
|
1398
1544
|
|
|
1399
1545
|
Shokupan applications are easy to test using Bun's built-in test runner.
|
|
1546
|
+
It can directly pass requests to the application or a router without requiring the
|
|
1547
|
+
server to fully start, bind to a port or listen for connections. This makes mocking
|
|
1548
|
+
the server unnecessary and allows for faster and more reliable testing. Additionally
|
|
1549
|
+
you can directly test authenticated endpoints without the need for a session or cookie.
|
|
1400
1550
|
|
|
1401
1551
|
```typescript
|
|
1402
1552
|
import { describe, it, expect } from 'bun:test';
|
|
@@ -1427,13 +1577,13 @@ Since Shokupan is built on Bun, deployment is straightforward.
|
|
|
1427
1577
|
### Using Bun
|
|
1428
1578
|
|
|
1429
1579
|
```bash
|
|
1430
|
-
bun run src/
|
|
1580
|
+
bun run src/main.ts
|
|
1431
1581
|
```
|
|
1432
1582
|
|
|
1433
1583
|
### Docker
|
|
1434
1584
|
|
|
1435
1585
|
```dockerfile
|
|
1436
|
-
FROM oven/bun:1
|
|
1586
|
+
FROM oven/bun:1-alpine
|
|
1437
1587
|
|
|
1438
1588
|
WORKDIR /app
|
|
1439
1589
|
|
|
@@ -1442,7 +1592,7 @@ RUN bun install --production
|
|
|
1442
1592
|
|
|
1443
1593
|
EXPOSE 3000
|
|
1444
1594
|
|
|
1445
|
-
CMD ["bun", "run", "src/
|
|
1595
|
+
CMD ["bun", "run", "src/main.ts"]
|
|
1446
1596
|
```
|
|
1447
1597
|
|
|
1448
1598
|
## 🛠️ CLI Tools
|
|
@@ -1530,6 +1680,14 @@ const app = new Shokupan(config?: ShokupanConfig);
|
|
|
1530
1680
|
- `hostname?: string` - Hostname (default: "localhost")
|
|
1531
1681
|
- `development?: boolean` - Development mode (default: auto-detect)
|
|
1532
1682
|
- `enableAsyncLocalStorage?: boolean` - Enable async context tracking
|
|
1683
|
+
- `enableTracing?: boolean` - Enable OpenTelemetry tracing
|
|
1684
|
+
- `enableOpenApiGen?: boolean` - Enable OpenAPI spec generation (default: true)
|
|
1685
|
+
- `controllersOnly?: boolean` - If true, only allows controllers, disabling app.get/post/etc (default: false)
|
|
1686
|
+
- `requestTimeout?: number` - Global request timeout (ms)
|
|
1687
|
+
- `readTimeout?: number` - Request body read timeout (ms)
|
|
1688
|
+
- `serverFactory?: ServerFactory` - Custom server factory (for Node.js/Deno support)
|
|
1689
|
+
- `autoBackpressureFeedback?: boolean` - Enable automatic load shedding based on CPU usage
|
|
1690
|
+
- `autoBackpressureLevel?: number` - CPU usage % threshold for backpressure (default: 60)
|
|
1533
1691
|
- `logger?: Logger` - Custom logger instance
|
|
1534
1692
|
|
|
1535
1693
|
**Methods:**
|
|
@@ -1551,7 +1709,13 @@ const app = new Shokupan(config?: ShokupanConfig);
|
|
|
1551
1709
|
|
|
1552
1710
|
### ShokupanRouter Class
|
|
1553
1711
|
|
|
1554
|
-
Router for grouping
|
|
1712
|
+
Router for grouping operations, applying middleware, and mounting controllers. Additionally
|
|
1713
|
+
they are effective for creating sub-applications that are independently tested. Routers can
|
|
1714
|
+
have OpenAPI spec applied to all endpoints of the router. Additionally they can be mounted
|
|
1715
|
+
onto the main application or other routers.
|
|
1716
|
+
|
|
1717
|
+
When a router is mounted to an app, if you are using the DebugView plugin you will be able to
|
|
1718
|
+
see it under the Registry tab and the Graph tab.
|
|
1555
1719
|
|
|
1556
1720
|
```typescript
|
|
1557
1721
|
const router = new ShokupanRouter(config?: ShokupanRouteConfig);
|
|
@@ -1592,6 +1756,13 @@ Request context object.
|
|
|
1592
1756
|
- `state: Record<string, any>` - Shared state object
|
|
1593
1757
|
- `session: any` - Session data (with session plugin)
|
|
1594
1758
|
- `response: ShokupanResponse` - Response builder
|
|
1759
|
+
- `ip: string` - Client IP address
|
|
1760
|
+
- `hostname: string` - Hostname (e.g. "localhost")
|
|
1761
|
+
- `host: string` - Host (e.g. "localhost:3000")
|
|
1762
|
+
- `protocol: string` - Protocol (http/https)
|
|
1763
|
+
- `secure: boolean` - Whether the request is secure over HTTPS
|
|
1764
|
+
- `origin: string` - Origin URL
|
|
1765
|
+
- `signal: AbortSignal` - Request abort signal (for standard fetch requests)
|
|
1595
1766
|
|
|
1596
1767
|
**Methods:**
|
|
1597
1768
|
- `set(name: string, value: string): ShokupanContext` - Set a response header
|
|
@@ -1602,6 +1773,7 @@ Request context object.
|
|
|
1602
1773
|
- `json(data: any, status?: number): ShokupanContext` - Return JSON response
|
|
1603
1774
|
- `text(data: string, status?: number): ShokupanContext` - Return text response
|
|
1604
1775
|
- `html(data: string, status?: number): ShokupanContext` - Return HTML response
|
|
1776
|
+
- `jsx(element: any): ShokupanContext` - Render a JSX element
|
|
1605
1777
|
- `redirect(url: string, status?: number): ShokupanContext` - Redirect response
|
|
1606
1778
|
- `file(path: string, fileOptions?: BlobPropertyBag, responseOptions?: ResponseInit): Response` - Return file response
|
|
1607
1779
|
|
|
@@ -1639,7 +1811,6 @@ Container.clear();
|
|
|
1639
1811
|
- 🚧 **Framework Plugins** - Drop-in adapters for [Express](https://expressjs.com/), [Koa](https://koajs.com/), and [Elysia](https://elysiajs.com/)
|
|
1640
1812
|
- 🚧 **Enhanced WebSockets** - Event support and HTTP simulation
|
|
1641
1813
|
- 🚧 **Benchmarks** - Comprehensive performance comparisons
|
|
1642
|
-
- 🚧 **Scaling** - Automatic clustering support
|
|
1643
1814
|
- 🚧 **RPC Support** - [tRPC](https://trpc.io/) and [gRPC](https://grpc.io/) integration
|
|
1644
1815
|
- 🚧 **Binary Formats** - [Protobuf](https://protobuf.dev/) and [MessagePack](https://msgpack.org/) support
|
|
1645
1816
|
- 🚧 **Reliability** - Circuit breaker pattern for resilience
|
|
@@ -1652,7 +1823,7 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
1652
1823
|
1. Fork the repository
|
|
1653
1824
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1654
1825
|
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
1655
|
-
4.
|
|
1826
|
+
4. Publish the branch (`git push origin feature/amazing-feature`)
|
|
1656
1827
|
5. Open a Pull Request
|
|
1657
1828
|
|
|
1658
1829
|
## 📝 License
|
|
@@ -1664,7 +1835,8 @@ MIT License - see the [LICENSE](LICENSE) file for details.
|
|
|
1664
1835
|
- Inspired by [Express](https://expressjs.com/), [Koa](https://koajs.com/), [NestJS](https://nestjs.com/), and [Elysia](https://elysiajs.com/)
|
|
1665
1836
|
- Built for the amazing [Bun](https://bun.sh/) runtime
|
|
1666
1837
|
- Powered by [Arctic](https://github.com/pilcrowonpaper/arctic) for OAuth2 support
|
|
1838
|
+
- Tests and Benchmarks created with Antigravity
|
|
1667
1839
|
|
|
1668
1840
|
---
|
|
1669
1841
|
|
|
1670
|
-
**Made with
|
|
1842
|
+
**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;
|
|
@@ -3435,7 +3473,8 @@ function enableOpenApiValidation(app) {
|
|
|
3435
3473
|
}
|
|
3436
3474
|
const eta = new eta$2.Eta();
|
|
3437
3475
|
class ScalarPlugin extends ShokupanRouter {
|
|
3438
|
-
constructor(pluginOptions) {
|
|
3476
|
+
constructor(pluginOptions = {}) {
|
|
3477
|
+
pluginOptions.config ??= {};
|
|
3439
3478
|
super();
|
|
3440
3479
|
this.pluginOptions = pluginOptions;
|
|
3441
3480
|
this.init();
|