@aklinker1/zeta 1.1.2 → 1.2.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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@aklinker1/zeta",
3
3
  "description": "Composable, testable, OpenAPI-first backend framework with validation built-in",
4
- "version": "1.1.2",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "packageManager": "bun@1.2.17",
7
+ "packageManager": "bun@1.3.2",
8
8
  "module": "src/index.ts",
9
9
  "types": "src/index.ts",
10
10
  "exports": {
@@ -42,12 +42,13 @@
42
42
  "docs:preview": "vitepress preview docs"
43
43
  },
44
44
  "dependencies": {
45
- "openapi-types": "^12.1.3",
46
45
  "@standard-schema/spec": "^1.0.0",
46
+ "openapi-types": "^12.1.3",
47
47
  "rou3": "^0.7.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@aklinker1/check": "^2.1.0",
50
+ "@aklinker1/check": "^2.2.0",
51
+ "@typescript/native-preview": "^7.0.0-dev.20251114.1",
51
52
  "@types/bun": "latest",
52
53
  "changelogen": "^0.6.2",
53
54
  "elysia": "^1.3.5",
@@ -59,7 +60,6 @@
59
60
  "prettier": "^3.5.3",
60
61
  "publint": "^0.3.12",
61
62
  "tinybench": "^4.0.1",
62
- "typescript": "^5.8.3",
63
63
  "vitepress": "^2.0.0-alpha.12",
64
64
  "vitepress-plugin-mermaid": "^2.0.17",
65
65
  "zod": "^4.1.11"
package/src/app.ts CHANGED
@@ -13,9 +13,14 @@ import type {
13
13
  DefaultAppData,
14
14
  BasePrefix,
15
15
  SchemaAdapter,
16
+ Transport,
16
17
  } from "./types";
17
18
  import { addRoute, createRouter, findRoute } from "rou3";
18
- import { callCtxModifierHooks, serializeErrorResponse } from "./internal/utils";
19
+ import {
20
+ callCtxModifierHooks,
21
+ detectTransport,
22
+ serializeErrorResponse,
23
+ } from "./internal/utils";
19
24
  import type { OpenAPIV3_1 } from "openapi-types";
20
25
  import { buildOpenApiDocs, buildScalarHtml } from "./open-api";
21
26
 
@@ -227,17 +232,8 @@ export function createApp<TPrefix extends BasePrefix = "">(
227
232
  },
228
233
 
229
234
  listen: (port, cb) => {
230
- if (typeof Bun !== "undefined") {
231
- Bun.serve({ port, fetch: app.build() });
232
- if (cb) setTimeout(cb, 0);
233
- } else if (
234
- // @ts-expect-error: Deno types not installed.
235
- typeof Deno !== "undefined"
236
- ) {
237
- // @ts-expect-error: Deno types not installed.
238
- Deno.serve({ port, fetch: app.build() });
239
- if (cb) setTimeout(cb, 0);
240
- }
235
+ const transport = options?.transport ?? detectTransport();
236
+ transport.listen(port, app.build(), cb);
241
237
  return app;
242
238
  },
243
239
 
@@ -428,6 +424,24 @@ export type CreateAppOptions<TPrefix extends BasePrefix = ""> = {
428
424
  */
429
425
  schemaAdapter?: SchemaAdapter;
430
426
 
427
+ /**
428
+ * Tell Zeta how to serve your app over a port. By default, Zeta will detect
429
+ * if you're runtime is Bun or Deno, and use the appropriate transport.
430
+ *
431
+ * If you need to customize the transport, like adding an `idleTimeout` to
432
+ * bun, you can do so by passing options into the transport's factory function.
433
+ *
434
+ * @example
435
+ * ```ts
436
+ * import { createBunTransport } from "@aklinker1/zeta/transports/bun-transport"
437
+ *
438
+ * const app = createApp({
439
+ * transport: createBunTransport(),
440
+ * });
441
+ * ```
442
+ */
443
+ transport?: Transport;
444
+
431
445
  /**
432
446
  * Where the OpenAPI JSON docs is hosted.
433
447
  * @default "/openapi.json"
@@ -7,9 +7,12 @@ import type {
7
7
  MaybePromise,
8
8
  RouterData,
9
9
  StatusResult,
10
+ Transport,
10
11
  } from "../types";
11
12
  import type { MatchedRoute } from "rou3";
12
13
  import type { ErrorResponse } from "../custom-responses";
14
+ import { createBunTransport } from "../transports/bun-transport";
15
+ import { createDenoTransport } from "../transports/deno-transport";
13
16
 
14
17
  export function validateSchema<T>(
15
18
  schema: StandardSchemaV1<T, T>,
@@ -71,7 +74,14 @@ export function getRawParams(
71
74
  rawParams["**"] = rawParams["_"];
72
75
  delete rawParams["_"];
73
76
  }
74
- return rawParams;
77
+
78
+ // Decode all values automatically
79
+ return Object.fromEntries(
80
+ Object.entries(rawParams).map(([key, value]) => [
81
+ key,
82
+ decodeURIComponent(value),
83
+ ]),
84
+ );
75
85
  }
76
86
 
77
87
  export function getErrorStack(err: Error): string[] | undefined {
@@ -129,3 +139,22 @@ export const IsStatusResult = Symbol("IsStatusResult");
129
139
  export function isStatusResult(result: any): result is StatusResult {
130
140
  return IsStatusResult in result;
131
141
  }
142
+
143
+ export function detectTransport(): Transport {
144
+ // @ts-ignore: Bun types may not be available
145
+ if (typeof Bun !== "undefined") return createBunTransport();
146
+ // @ts-ignore: Deno types may not be available
147
+ if (typeof Deno !== "undefined") return createDenoTransport();
148
+
149
+ throw Error(`Cannot automatically detect which transport to use. You must specify a transport in your top-level app:
150
+
151
+ ---
152
+ import { createBunTransport } from '@aklinker1/zeta/transports/bun-transport';
153
+
154
+ const app = createApp({
155
+ transport: createBunTransport(),
156
+ })
157
+
158
+ app.listen();
159
+ ---`);
160
+ }
package/src/open-api.ts CHANGED
@@ -193,6 +193,7 @@ function optimizeSpec(spec: OpenAPI.Document): OpenAPI.Document {
193
193
 
194
194
  // Optimizations
195
195
  addModelRefs(optimized);
196
+ sortComponentSchemas(optimized);
196
197
 
197
198
  return optimized;
198
199
  }
@@ -234,3 +235,13 @@ function addModelRefs(spec: any): void {
234
235
  // Process the "paths" object
235
236
  recurse(spec.paths);
236
237
  }
238
+
239
+ function sortComponentSchemas(spec: any): void {
240
+ if (!spec?.components?.schemas) return;
241
+
242
+ spec.components.schemas = Object.fromEntries(
243
+ Object.entries(spec.components.schemas).sort((a, b) =>
244
+ a[0].toLowerCase().localeCompare(b[0].toLowerCase()),
245
+ ),
246
+ );
247
+ }
package/src/types.ts CHANGED
@@ -458,8 +458,8 @@ export type GetRouteHandlerReturnType<TRouteDef extends RouteDef> =
458
458
  TRouteDef extends { responses: symbol } // is any check
459
459
  ? any
460
460
  : TRouteDef extends { responses: infer TResponses }
461
- ? TResponses extends StandardSchemaV1<infer TResponse>
462
- ? TResponse
461
+ ? TResponses extends StandardSchemaV1
462
+ ? StandardSchemaV1.InferInput<TResponses>
463
463
  : TRouteDef["responses"] extends Record<number, StandardSchemaV1<any>>
464
464
  ? StatusResult
465
465
  : never
@@ -1042,6 +1042,14 @@ export interface SchemaAdapter {
1042
1042
  getMeta: (schema: StandardSchemaV1) => Record<string, any> | undefined;
1043
1043
  }
1044
1044
 
1045
+ //
1046
+ // TRANSPORTS
1047
+ //
1048
+
1049
+ export interface Transport {
1050
+ listen: (port: number, fetch: ServerSideFetch, cb?: () => void) => void;
1051
+ }
1052
+
1045
1053
  //
1046
1054
  // SETTER
1047
1055
  //