@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 +5 -5
- package/src/app.ts +26 -12
- package/src/internal/utils.ts +30 -1
- package/src/open-api.ts +11 -0
- package/src/types.ts +10 -2
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.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"packageManager": "bun@1.2
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
231
|
-
|
|
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"
|
package/src/internal/utils.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
462
|
-
?
|
|
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
|
//
|