@rexeus/typeweaver-server 0.10.3 → 0.10.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.
@@ -1,5 +1,10 @@
1
- import type { IHttpResponse } from "@rexeus/typeweaver-core";
1
+ import type { IHttpRequest, IHttpResponse } from "@rexeus/typeweaver-core";
2
2
  import { defineMiddleware } from "../TypedMiddleware.js";
3
+ import {
4
+ hasHeaderName,
5
+ readHeaderValues,
6
+ readSingletonHeader,
7
+ } from "./header.js";
3
8
 
4
9
  export type CorsOptions = {
5
10
  readonly origin?:
@@ -22,13 +27,49 @@ const DEFAULT_METHODS = [
22
27
  "DELETE",
23
28
  ] as const;
24
29
 
30
+ const POLICY_CONTROLLED_CORS_HEADERS = new Set([
31
+ "access-control-allow-origin",
32
+ "access-control-allow-credentials",
33
+ "access-control-expose-headers",
34
+ "access-control-allow-methods",
35
+ "access-control-allow-headers",
36
+ "access-control-max-age",
37
+ ]);
38
+
39
+ type NormalizedCorsOptions = {
40
+ readonly origin: CorsOptions["origin"];
41
+ readonly allowMethods: string;
42
+ readonly allowHeaders: readonly string[] | undefined;
43
+ readonly exposeHeaders: string | undefined;
44
+ readonly maxAge: string | undefined;
45
+ readonly credentials: boolean;
46
+ };
47
+
48
+ type CorsRequest = {
49
+ readonly header: IHttpRequest["header"];
50
+ readonly method: IHttpRequest["method"];
51
+ readonly hasOrigin: boolean;
52
+ readonly origin: string | undefined;
53
+ };
54
+
55
+ function normalizeCorsOptions(options?: CorsOptions): NormalizedCorsOptions {
56
+ return {
57
+ origin: options?.origin,
58
+ allowMethods: (options?.allowMethods ?? DEFAULT_METHODS).join(", "),
59
+ allowHeaders: options?.allowHeaders,
60
+ exposeHeaders: options?.exposeHeaders?.join(", "),
61
+ maxAge: options?.maxAge?.toString(),
62
+ credentials: options?.credentials ?? false,
63
+ };
64
+ }
65
+
25
66
  function resolveOrigin(
26
67
  configOrigin: CorsOptions["origin"],
27
68
  requestOrigin: string | undefined,
28
69
  credentials: boolean
29
70
  ): string | undefined {
30
71
  if (configOrigin === undefined || configOrigin === "*") {
31
- if (credentials && requestOrigin) return requestOrigin;
72
+ if (credentials) return undefined;
32
73
  return "*";
33
74
  }
34
75
 
@@ -48,72 +89,219 @@ function resolveOrigin(
48
89
  function getRequestOrigin(
49
90
  header: Record<string, string | string[]> | undefined
50
91
  ): string | undefined {
51
- const origin = header?.["origin"];
52
- return typeof origin === "string" ? origin : undefined;
92
+ return readSingletonHeader(header, "origin");
53
93
  }
54
94
 
55
- export function cors(options?: CorsOptions) {
56
- const credentials = options?.credentials ?? false;
57
- const methods = (options?.allowMethods ?? DEFAULT_METHODS).join(", ");
58
- const exposeHeaders = options?.exposeHeaders?.join(", ");
59
- const maxAge = options?.maxAge?.toString();
95
+ function readCorsRequest(request: IHttpRequest): CorsRequest {
96
+ return {
97
+ header: request.header,
98
+ method: request.method,
99
+ hasOrigin: hasHeaderName(request.header, "origin"),
100
+ origin: getRequestOrigin(request.header),
101
+ };
102
+ }
60
103
 
61
- return defineMiddleware(async (ctx, next) => {
62
- const requestOrigin = getRequestOrigin(ctx.request.header);
63
- const origin = resolveOrigin(options?.origin, requestOrigin, credentials);
104
+ function isOriginDependentWithoutRequestOrigin(
105
+ configOrigin: CorsOptions["origin"],
106
+ credentials: boolean
107
+ ): boolean {
108
+ return (
109
+ typeof configOrigin === "function" ||
110
+ Array.isArray(configOrigin) ||
111
+ ((configOrigin === undefined || configOrigin === "*") && credentials)
112
+ );
113
+ }
64
114
 
65
- if (origin === undefined) return next();
115
+ function resolveRequestOrigin(
116
+ options: NormalizedCorsOptions,
117
+ request: CorsRequest
118
+ ): string | undefined {
119
+ if (request.hasOrigin && request.origin === undefined) {
120
+ return undefined;
121
+ }
66
122
 
67
- const corsHeaders: Record<string, string> = {
68
- "access-control-allow-origin": origin,
69
- };
123
+ const resolvedOrigin = resolveOrigin(
124
+ options.origin,
125
+ request.origin,
126
+ options.credentials
127
+ );
70
128
 
71
- if (credentials) {
72
- corsHeaders["access-control-allow-credentials"] = "true";
73
- }
129
+ return options.credentials && resolvedOrigin === "*"
130
+ ? undefined
131
+ : resolvedOrigin;
132
+ }
74
133
 
75
- if (origin !== "*") {
76
- corsHeaders["vary"] = "Origin";
134
+ function shouldVaryDeniedCorsResponse(
135
+ options: NormalizedCorsOptions,
136
+ request: CorsRequest
137
+ ): boolean {
138
+ return (
139
+ request.hasOrigin ||
140
+ isOriginDependentWithoutRequestOrigin(options.origin, options.credentials)
141
+ );
142
+ }
143
+
144
+ function buildSimpleCorsHeaders(
145
+ options: NormalizedCorsOptions,
146
+ origin: string
147
+ ): Record<string, string> {
148
+ const corsHeaders: Record<string, string> = {
149
+ "access-control-allow-origin": origin,
150
+ };
151
+
152
+ if (options.credentials) {
153
+ corsHeaders["access-control-allow-credentials"] = "true";
154
+ }
155
+
156
+ if (origin !== "*") {
157
+ corsHeaders["vary"] = "Origin";
158
+ }
159
+
160
+ if (options.exposeHeaders) {
161
+ corsHeaders["access-control-expose-headers"] = options.exposeHeaders;
162
+ }
163
+
164
+ return corsHeaders;
165
+ }
166
+
167
+ function isPreflightCorsRequest(request: CorsRequest): boolean {
168
+ return (
169
+ request.method === "OPTIONS" &&
170
+ request.origin !== undefined &&
171
+ readSingletonHeader(request.header, "access-control-request-method") !==
172
+ undefined
173
+ );
174
+ }
175
+
176
+ function buildPreflightCorsHeaders(
177
+ options: NormalizedCorsOptions,
178
+ request: CorsRequest,
179
+ simpleCorsHeaders: Record<string, string>
180
+ ): Record<string, string> {
181
+ const corsHeaders = { ...simpleCorsHeaders };
182
+ corsHeaders["access-control-allow-methods"] = options.allowMethods;
183
+
184
+ if (options.allowHeaders !== undefined) {
185
+ if (options.allowHeaders.length > 0) {
186
+ corsHeaders["access-control-allow-headers"] =
187
+ options.allowHeaders.join(", ");
77
188
  }
189
+ } else {
190
+ const requestedHeaders = readSingletonHeader(
191
+ request.header,
192
+ "access-control-request-headers"
193
+ );
194
+ if (typeof requestedHeaders === "string") {
195
+ corsHeaders["access-control-allow-headers"] = requestedHeaders;
196
+ }
197
+ }
78
198
 
79
- if (exposeHeaders) {
80
- corsHeaders["access-control-expose-headers"] = exposeHeaders;
199
+ if (options.maxAge !== undefined) {
200
+ corsHeaders["access-control-max-age"] = options.maxAge;
201
+ }
202
+
203
+ return corsHeaders;
204
+ }
205
+
206
+ function splitHeaderValues(values: readonly string[]): readonly string[] {
207
+ return values.flatMap(value =>
208
+ value
209
+ .split(",")
210
+ .map(item => item.trim())
211
+ .filter(item => item.length > 0)
212
+ );
213
+ }
214
+
215
+ function mergeVary(existing: readonly string[], value: string): string {
216
+ const values = splitHeaderValues(existing);
217
+ if (values.length === 0) return value;
218
+
219
+ const hasValue = values.some(
220
+ item => item.toLowerCase() === value.toLowerCase()
221
+ );
222
+
223
+ return hasValue ? values.join(", ") : [...values, value].join(", ");
224
+ }
225
+
226
+ function removePolicyControlledCorsHeaders(
227
+ responseHeaders: Record<string, string | string[]> | undefined
228
+ ): Record<string, string | string[]> {
229
+ const result: Record<string, string | string[]> = {};
230
+
231
+ for (const [key, value] of Object.entries(responseHeaders ?? {})) {
232
+ if (POLICY_CONTROLLED_CORS_HEADERS.has(key.toLowerCase())) continue;
233
+
234
+ result[key] = value;
235
+ }
236
+
237
+ return result;
238
+ }
239
+
240
+ function mergeResponseHeaders(
241
+ responseHeaders: Record<string, string | string[]> | undefined,
242
+ corsHeaders: Record<string, string>
243
+ ): Record<string, string | string[]> {
244
+ const result = removePolicyControlledCorsHeaders(responseHeaders);
245
+
246
+ const mergedCorsHeaders = { ...corsHeaders };
247
+ if (corsHeaders.vary !== undefined) {
248
+ for (const key of Object.keys(result)) {
249
+ if (key.toLowerCase() === "vary") delete result[key];
81
250
  }
82
251
 
83
- const isPreflight =
84
- ctx.request.method === "OPTIONS" &&
85
- ctx.request.header?.["access-control-request-method"] !== undefined;
86
-
87
- if (isPreflight) {
88
- corsHeaders["access-control-allow-methods"] = methods;
89
-
90
- const configuredHeaders = options?.allowHeaders;
91
- if (configuredHeaders && configuredHeaders.length > 0) {
92
- corsHeaders["access-control-allow-headers"] =
93
- configuredHeaders.join(", ");
94
- } else {
95
- const requestedHeaders =
96
- ctx.request.header?.["access-control-request-headers"];
97
- if (typeof requestedHeaders === "string") {
98
- corsHeaders["access-control-allow-headers"] = requestedHeaders;
99
- }
100
- }
252
+ mergedCorsHeaders.vary = mergeVary(
253
+ readHeaderValues(responseHeaders, "vary"),
254
+ corsHeaders.vary
255
+ );
256
+ }
257
+
258
+ return { ...result, ...mergedCorsHeaders };
259
+ }
260
+
261
+ function mergeCorsHeadersIntoResponse(
262
+ response: IHttpResponse,
263
+ corsHeaders: Record<string, string>
264
+ ): IHttpResponse {
265
+ return {
266
+ ...response,
267
+ header: mergeResponseHeaders(response.header, corsHeaders),
268
+ };
269
+ }
270
+
271
+ export function cors(options?: CorsOptions) {
272
+ const normalizedOptions = normalizeCorsOptions(options);
273
+
274
+ return defineMiddleware(async (ctx, next) => {
275
+ const corsRequest = readCorsRequest(ctx.request);
276
+ const origin = resolveRequestOrigin(normalizedOptions, corsRequest);
277
+
278
+ if (origin === undefined) {
279
+ const response = await next();
101
280
 
102
- if (maxAge) {
103
- corsHeaders["access-control-max-age"] = maxAge;
281
+ if (!shouldVaryDeniedCorsResponse(normalizedOptions, corsRequest)) {
282
+ return response;
104
283
  }
105
284
 
285
+ return mergeCorsHeadersIntoResponse(response, { vary: "Origin" });
286
+ }
287
+
288
+ const corsHeaders = buildSimpleCorsHeaders(normalizedOptions, origin);
289
+
290
+ if (isPreflightCorsRequest(corsRequest)) {
291
+ const preflightHeaders = buildPreflightCorsHeaders(
292
+ normalizedOptions,
293
+ corsRequest,
294
+ corsHeaders
295
+ );
296
+
106
297
  return {
107
298
  statusCode: 204,
108
- header: corsHeaders,
299
+ header: preflightHeaders,
109
300
  } satisfies IHttpResponse;
110
301
  }
111
302
 
112
303
  const response = await next();
113
304
 
114
- return {
115
- ...response,
116
- header: { ...corsHeaders, ...response.header },
117
- } satisfies IHttpResponse;
305
+ return mergeCorsHeadersIntoResponse(response, corsHeaders);
118
306
  });
119
307
  }
@@ -0,0 +1,59 @@
1
+ export type HeaderMap = Record<string, string | string[]> | undefined;
2
+
3
+ export function readSingletonHeader(
4
+ header: HeaderMap,
5
+ name: string
6
+ ): string | undefined {
7
+ const normalizedName = name.toLowerCase();
8
+ let foundValue: string | undefined;
9
+
10
+ for (const [key, value] of Object.entries(header ?? {})) {
11
+ if (key.toLowerCase() !== normalizedName) continue;
12
+ if (foundValue !== undefined || typeof value !== "string") {
13
+ return undefined;
14
+ }
15
+
16
+ foundValue = value;
17
+ }
18
+
19
+ return foundValue;
20
+ }
21
+
22
+ export function hasHeaderName(header: HeaderMap, name: string): boolean {
23
+ const normalizedName = name.toLowerCase();
24
+
25
+ return Object.keys(header ?? {}).some(
26
+ key => key.toLowerCase() === normalizedName
27
+ );
28
+ }
29
+
30
+ export function readHeaderValues(
31
+ header: HeaderMap,
32
+ name: string
33
+ ): readonly string[] {
34
+ const normalizedName = name.toLowerCase();
35
+ const values: string[] = [];
36
+
37
+ for (const [key, value] of Object.entries(header ?? {})) {
38
+ if (key.toLowerCase() !== normalizedName) continue;
39
+
40
+ values.push(...(Array.isArray(value) ? value : [value]));
41
+ }
42
+
43
+ return values;
44
+ }
45
+
46
+ export function omitHeaders(
47
+ header: HeaderMap,
48
+ names: readonly string[]
49
+ ): Record<string, string | string[]> {
50
+ const normalizedNames = new Set(names.map(name => name.toLowerCase()));
51
+ const headers: Record<string, string | string[]> = {};
52
+
53
+ for (const [key, value] of Object.entries(header ?? {})) {
54
+ if (normalizedNames.has(key.toLowerCase())) continue;
55
+ headers[key] = value;
56
+ }
57
+
58
+ return headers;
59
+ }
@@ -10,6 +10,7 @@ export type LogData = {
10
10
  export type LoggerOptions = {
11
11
  readonly logFn?: (message: string) => void;
12
12
  readonly format?: (data: LogData) => string;
13
+ readonly nowMs?: () => number;
13
14
  };
14
15
 
15
16
  const defaultFormat = (data: LogData): string =>
@@ -18,11 +19,12 @@ const defaultFormat = (data: LogData): string =>
18
19
  export function logger(options?: LoggerOptions) {
19
20
  const logFn = options?.logFn ?? console.log;
20
21
  const format = options?.format ?? defaultFormat;
22
+ const nowMs = options?.nowMs ?? (() => performance.now());
21
23
 
22
24
  return defineMiddleware(async (ctx, next) => {
23
- const start = performance.now();
25
+ const start = nowMs();
24
26
  const response = await next();
25
- const durationMs = Math.round(performance.now() - start);
27
+ const durationMs = Math.round(nowMs() - start);
26
28
 
27
29
  logFn(
28
30
  format({
@@ -10,10 +10,15 @@ export function poweredBy(options?: PoweredByOptions) {
10
10
 
11
11
  return defineMiddleware(async (_ctx, next) => {
12
12
  const response = await next();
13
+ const responseHeaders = Object.fromEntries(
14
+ Object.entries(response.header ?? {}).filter(
15
+ ([headerName]) => headerName.toLowerCase() !== "x-powered-by"
16
+ )
17
+ );
13
18
 
14
19
  return {
15
20
  ...response,
16
- header: { ...response.header, "x-powered-by": value },
21
+ header: { ...responseHeaders, "x-powered-by": value },
17
22
  } satisfies IHttpResponse;
18
23
  });
19
24
  }
@@ -1,29 +1,29 @@
1
1
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
2
2
  import { defineMiddleware } from "../TypedMiddleware.js";
3
+ import { omitHeaders, readSingletonHeader } from "./header.js";
3
4
 
4
5
  export type RequestIdOptions = {
5
6
  readonly headerName?: string;
6
7
  readonly generator?: () => string;
7
8
  };
8
9
 
10
+ const isValidRequestId = (value: string | undefined): value is string =>
11
+ value !== undefined && value.length > 0 && !/[\r\n]/.test(value);
12
+
9
13
  export function requestId(options?: RequestIdOptions) {
10
14
  const headerName = (options?.headerName ?? "x-request-id").toLowerCase();
11
15
  const generator = options?.generator ?? (() => crypto.randomUUID());
12
16
 
13
17
  return defineMiddleware<{ requestId: string }>(async (ctx, next) => {
14
- const existing = ctx.request.header?.[headerName];
15
- const id =
16
- typeof existing === "string"
17
- ? existing
18
- : Array.isArray(existing)
19
- ? (existing[0] ?? generator())
20
- : generator();
18
+ const existing = readSingletonHeader(ctx.request.header, headerName);
19
+ const id = isValidRequestId(existing) ? existing : generator();
21
20
 
22
21
  const response = await next({ requestId: id });
22
+ const header = omitHeaders(response.header, [headerName]);
23
23
 
24
24
  return {
25
25
  ...response,
26
- header: { ...response.header, [headerName]: id },
26
+ header: { ...header, [headerName]: id },
27
27
  } satisfies IHttpResponse;
28
28
  });
29
29
  }
@@ -2,55 +2,80 @@ import { pathMatcher } from "../PathMatcher.js";
2
2
  import { defineMiddleware } from "../TypedMiddleware.js";
3
3
  import type { TypedMiddleware } from "../TypedMiddleware.js";
4
4
 
5
+ /** Rejects middleware that would provide state from a conditional branch. */
6
+ type RejectsProvidedState<TProvides extends Record<string, unknown>> = [
7
+ keyof TProvides,
8
+ ] extends [never]
9
+ ? unknown
10
+ : never;
11
+
12
+ /**
13
+ * scoped/except middleware may be skipped, so it cannot safely provide
14
+ * downstream state; any upstream state requirements remain part of its type.
15
+ */
16
+ type StateNeutralMiddleware<
17
+ TProvides extends Record<string, unknown>,
18
+ TRequires extends Record<string, unknown>,
19
+ > = TypedMiddleware<TProvides, TRequires> & RejectsProvidedState<TProvides>;
20
+
5
21
  /**
6
22
  * Restricts a middleware to only run on paths matching the given patterns.
7
23
  *
8
24
  * Accepts the same pattern syntax as {@link pathMatcher}: exact (`"/users"`),
9
25
  * prefix (`"/api/*"`), and parameterized (`"/users/:id"`).
10
26
  *
11
- * Only accepts non-state middleware (`TypedMiddleware<{}, {}>`) to preserve
27
+ * Only accepts non-state-providing middleware to preserve
12
28
  * TypeWeaver's compile-time state guarantees — skipping a state-providing
13
29
  * middleware would leave downstream consumers with missing state.
30
+ * Any upstream state requirements declared by the wrapped middleware are
31
+ * preserved on the returned middleware descriptor.
14
32
  *
15
33
  * @example
16
34
  * ```typescript
17
35
  * app.use(scoped(["/api/*"], cors({ origin: "https://app.com" })));
18
36
  * ```
19
37
  */
20
- export function scoped(
38
+ export function scoped<
39
+ TProvides extends Record<string, unknown>,
40
+ TRequires extends Record<string, unknown>,
41
+ >(
21
42
  paths: readonly string[],
22
- middleware: TypedMiddleware<{}, {}>
23
- ): TypedMiddleware<{}, {}> {
43
+ middleware: StateNeutralMiddleware<TProvides, TRequires>
44
+ ): TypedMiddleware<TProvides, TRequires> {
24
45
  const matchers = paths.map(pathMatcher);
25
46
 
26
- return defineMiddleware(async (ctx, next) => {
47
+ return defineMiddleware<{}, TRequires>(async (ctx, next) => {
27
48
  if (!matchers.some(match => match(ctx.request.path))) {
28
49
  return next();
29
50
  }
30
51
  return middleware.handler(ctx, next);
31
- });
52
+ }) as TypedMiddleware<TProvides, TRequires>;
32
53
  }
33
54
 
34
55
  /**
35
56
  * Runs a middleware on all paths *except* those matching the given patterns.
36
57
  *
37
- * The inverse of {@link scoped}. Same pattern syntax and type constraint.
58
+ * The inverse of {@link scoped}. Same pattern syntax and type constraints,
59
+ * including preservation of wrapped middleware state requirements.
38
60
  *
39
61
  * @example
40
62
  * ```typescript
41
63
  * app.use(except(["/health", "/ready"], logger()));
42
64
  * ```
43
65
  */
44
- export function except(
66
+ export function except<
67
+ TProvides extends Record<string, unknown>,
68
+ TRequires extends Record<string, unknown>,
69
+ >(
45
70
  paths: readonly string[],
46
- middleware: TypedMiddleware<{}, {}>
47
- ): TypedMiddleware<{}, {}> {
71
+ middleware: StateNeutralMiddleware<TProvides, TRequires>
72
+ ): TypedMiddleware<TProvides, TRequires> {
48
73
  const matchers = paths.map(pathMatcher);
49
74
 
50
- return defineMiddleware(async (ctx, next) => {
75
+ return defineMiddleware<{}, TRequires>(async (ctx, next) => {
51
76
  if (matchers.some(match => match(ctx.request.path))) {
52
77
  return next();
53
78
  }
54
79
  return middleware.handler(ctx, next);
55
- });
80
+ }) as TypedMiddleware<TProvides, TRequires>;
56
81
  }
@@ -1,5 +1,6 @@
1
1
  import type { IHttpResponse } from "@rexeus/typeweaver-core";
2
2
  import { defineMiddleware } from "../TypedMiddleware.js";
3
+ import { omitHeaders } from "./header.js";
3
4
 
4
5
  export type SecureHeadersOptions = {
5
6
  readonly contentTypeOptions?: string | false;
@@ -48,10 +49,11 @@ export function secureHeaders(options?: SecureHeadersOptions) {
48
49
 
49
50
  return defineMiddleware(async (_ctx, next) => {
50
51
  const response = await next();
52
+ const header = omitHeaders(response.header, Object.keys(headers));
51
53
 
52
54
  return {
53
55
  ...response,
54
- header: { ...headers, ...response.header },
56
+ header: { ...header, ...headers },
55
57
  } satisfies IHttpResponse;
56
58
  });
57
59
  }
@@ -18,7 +18,8 @@ export type Server<%- pascalCaseEntityName %>ApiHandler<
18
18
  TState extends Record<string, unknown> = Record<string, unknown>,
19
19
  > = {
20
20
  <% for (const operation of operations) { %>
21
- <%- operation.handlerName %>: RequestHandler<I<%- operation.className %>Request, <%- operation.className %>Response, TState>;
21
+ <% if (operation.jsDoc) { %><%- operation.jsDoc %>
22
+ <% } %> <%- operation.handlerName %>: RequestHandler<I<%- operation.className %>Request, <%- operation.className %>Response, TState>;
22
23
  <% } %>
23
24
  };
24
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-server",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Generates a lightweight, dependency-free server with built-in routing and middleware from your API definitions. Powered by Typeweaver.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -47,15 +47,15 @@
47
47
  },
48
48
  "homepage": "https://github.com/rexeus/typeweaver#readme",
49
49
  "peerDependencies": {
50
- "@rexeus/typeweaver-core": "^0.10.3",
51
- "@rexeus/typeweaver-gen": "^0.10.3"
50
+ "@rexeus/typeweaver-core": "^0.10.5",
51
+ "@rexeus/typeweaver-gen": "^0.10.5"
52
52
  },
53
53
  "devDependencies": {
54
54
  "get-port": "^7.2.0",
55
55
  "test-utils": "file:../test-utils",
56
56
  "tsx": "^4.21.0",
57
- "@rexeus/typeweaver-core": "^0.10.3",
58
- "@rexeus/typeweaver-gen": "^0.10.3"
57
+ "@rexeus/typeweaver-core": "^0.10.5",
58
+ "@rexeus/typeweaver-gen": "^0.10.5"
59
59
  },
60
60
  "dependencies": {
61
61
  "polycase": "^1.1.0"