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 CHANGED
@@ -1,33 +1,35 @@
1
1
  # Shokupan 🍞
2
2
 
3
- > A low-lift modern web framework for Bun
3
+ > A delightful, type-safe web framework for Bun
4
4
 
5
- Shokupan is a high-performance, feature-rich web framework built specifically for Bun. It combines the familiarity of Express.js with modern NestJS-style architecture (Dependency Injection, Controllers) and seamless compatibility with the vast ecosystem of Express plugins — all while maintaining exceptional performance and built-in OpenAPI support.
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
- - 🚀 **Built for Bun** - Native [Bun](https://bun.sh/) performance with optimized routing
12
- - 🎯 **TypeScript First** - Full type safety with decorators and generics
13
- - 📝 **Auto OpenAPI** - Generate [OpenAPI](https://www.openapis.org/) specs automatically from routes
14
- - 🔌 **Rich Plugin System** - CORS, Sessions, Auth, Validation, Rate Limiting, and more
15
- - 🌐 **Flexible Routing** - Express-style routes or decorator-based controllers
16
- - 🔀 **Express Compatible** - Works with [Express](https://expressjs.com/) middleware patterns
17
- - 📊 **Built-in Telemetry** - [OpenTelemetry](https://opentelemetry.io/) instrumentation out of the box
18
- - 🔐 **OAuth2 Support** - GitHub, Google, Microsoft, Apple, Auth0, Okta
19
- - **Multi-validator Support** - Zod, Ajv, TypeBox, Valibot
20
- - 📚 **OpenAPI Docs** - Beautiful OpenAPI documentation with [Scalar](https://scalar.dev/)
21
- - **Short shift** - Very simple migration from [Express](https://expressjs.com/) or [NestJS](https://nestjs.com/) to Shokupan
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
- ## 📦 Installation
26
+ ![Shokupan Debug Dashboard](docs/src/assets/debug_dashboard_overview.png)
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/index.ts
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/index.ts"]
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 routes.
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. Push to the branch (`git push origin feature/amazing-feature`)
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 🍞 by the Shokupan team**
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(): Bun.SocketAddress;
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 routes = this[$routes].map((r) => ({
1510
- type: "route",
1511
- path: r.path,
1512
- method: r.method,
1513
- metadata: r.metadata,
1514
- handlerName: r.handler.name,
1515
- tags: r.handlerSpec?.tags,
1516
- order: r.order,
1517
- _fn: r.handler
1518
- // Expose handler for debugging instrumentation
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
- // Check if we can store this
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
- c.handlerStack.push({
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.buffer;
2936
+ body = encoded;
2899
2937
  bodySize = encoded.byteLength;
2900
2938
  } else if (ctx._rawBody instanceof Uint8Array) {
2901
- body = ctx._rawBody.buffer;
2939
+ body = ctx._rawBody;
2902
2940
  bodySize = ctx._rawBody.byteLength;
2903
2941
  } else {
2904
2942
  body = ctx._rawBody;
2905
- bodySize = ctx._rawBody.byteLength;
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;