@kaito-http/core 3.0.0-beta.20 → 3.0.0-beta.21

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/dist/index.cjs CHANGED
@@ -128,19 +128,6 @@ var KaitoResponse = class {
128
128
  }
129
129
  };
130
130
 
131
- // src/util.ts
132
- function createUtilities(getContext) {
133
- return {
134
- getContext,
135
- router: () => Router.create()
136
- };
137
- }
138
- function parsable(parse) {
139
- return {
140
- parse
141
- };
142
- }
143
-
144
131
  // src/router/router.ts
145
132
  var Router = class _Router {
146
133
  state;
@@ -153,9 +140,10 @@ var Router = class _Router {
153
140
  return {};
154
141
  }
155
142
  const result = {};
156
- for (const [key, parsable2] of Object.entries(schema)) {
143
+ for (const key in schema) {
144
+ if (!schema.hasOwnProperty(key)) continue;
157
145
  const value = url.searchParams.get(key);
158
- result[key] = parsable2.parse(value);
146
+ result[key] = schema[key].parse(value);
159
147
  }
160
148
  return result;
161
149
  }
@@ -246,9 +234,6 @@ var Router = class _Router {
246
234
  params
247
235
  });
248
236
  if (result instanceof Response) {
249
- if (server.enableClientResponseHints) {
250
- result.headers.set("x-kaito-is-response", "1");
251
- }
252
237
  return result;
253
238
  }
254
239
  return response.toResponse({
@@ -294,11 +279,7 @@ var Router = class _Router {
294
279
  };
295
280
 
296
281
  // src/server.ts
297
- function createKaitoHandler(userConfig) {
298
- const config = {
299
- enableClientResponseHints: true,
300
- ...userConfig
301
- };
282
+ function createKaitoHandler(config) {
302
283
  const handle = config.router.freeze(config);
303
284
  return async (request) => {
304
285
  if (config.before) {
@@ -313,6 +294,19 @@ function createKaitoHandler(userConfig) {
313
294
  return response;
314
295
  };
315
296
  }
297
+
298
+ // src/util.ts
299
+ function createUtilities(getContext) {
300
+ return {
301
+ getContext,
302
+ router: () => Router.create()
303
+ };
304
+ }
305
+ function parsable(parse) {
306
+ return {
307
+ parse
308
+ };
309
+ }
316
310
  // Annotate the CommonJS export names for ESM import in node:
317
311
  0 && (module.exports = {
318
312
  KaitoError,
package/dist/index.d.cts CHANGED
@@ -126,25 +126,8 @@ type ServerConfig<ContextFrom> = {
126
126
  * ```
127
127
  */
128
128
  transform?: (req: Request, res: Response) => Promise<Response | void | undefined>;
129
- /**
130
- * Controls whether the server includes a header to indicate non-JSON responses.
131
- *
132
- * When a route handler returns a `Response` object, this setting determines if
133
- * the server adds a header indicating the response should not be parsed as JSON.
134
- *
135
- * The `@kaito-http/client` package checks for this header's presence:
136
- * - If present: Returns the raw Response object
137
- * - If absent: Attempts to parse the response as JSON
138
- *
139
- * You might want to disable this feature when:
140
- * 1. Using a custom client that doesn't recognize this header
141
- * 2. Security requirements prevent exposing framework details in headers
142
- *
143
- * @default true
144
- */
145
- enableClientResponseHints: boolean;
146
129
  };
147
- declare function createKaitoHandler<Context>(userConfig: MakeOptional<ServerConfig<Context>, 'enableClientResponseHints'>): (request: Request) => Promise<Response>;
130
+ declare function createKaitoHandler<Context>(config: ServerConfig<Context>): (request: Request) => Promise<Response>;
148
131
 
149
132
  type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextFrom, infer ContextTo, infer Result, infer Path, infer Method, infer Query, infer BodyOutput> ? Route<ContextFrom, ContextTo, Result, `${Prefix}${Path}`, Method, Query, BodyOutput> : never;
150
133
  type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
package/dist/index.d.ts CHANGED
@@ -126,25 +126,8 @@ type ServerConfig<ContextFrom> = {
126
126
  * ```
127
127
  */
128
128
  transform?: (req: Request, res: Response) => Promise<Response | void | undefined>;
129
- /**
130
- * Controls whether the server includes a header to indicate non-JSON responses.
131
- *
132
- * When a route handler returns a `Response` object, this setting determines if
133
- * the server adds a header indicating the response should not be parsed as JSON.
134
- *
135
- * The `@kaito-http/client` package checks for this header's presence:
136
- * - If present: Returns the raw Response object
137
- * - If absent: Attempts to parse the response as JSON
138
- *
139
- * You might want to disable this feature when:
140
- * 1. Using a custom client that doesn't recognize this header
141
- * 2. Security requirements prevent exposing framework details in headers
142
- *
143
- * @default true
144
- */
145
- enableClientResponseHints: boolean;
146
129
  };
147
- declare function createKaitoHandler<Context>(userConfig: MakeOptional<ServerConfig<Context>, 'enableClientResponseHints'>): (request: Request) => Promise<Response>;
130
+ declare function createKaitoHandler<Context>(config: ServerConfig<Context>): (request: Request) => Promise<Response>;
148
131
 
149
132
  type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextFrom, infer ContextTo, infer Result, infer Path, infer Method, infer Query, infer BodyOutput> ? Route<ContextFrom, ContextTo, Result, `${Prefix}${Path}`, Method, Query, BodyOutput> : never;
150
133
  type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
package/dist/index.js CHANGED
@@ -96,19 +96,6 @@ var KaitoResponse = class {
96
96
  }
97
97
  };
98
98
 
99
- // src/util.ts
100
- function createUtilities(getContext) {
101
- return {
102
- getContext,
103
- router: () => Router.create()
104
- };
105
- }
106
- function parsable(parse) {
107
- return {
108
- parse
109
- };
110
- }
111
-
112
99
  // src/router/router.ts
113
100
  var Router = class _Router {
114
101
  state;
@@ -121,9 +108,10 @@ var Router = class _Router {
121
108
  return {};
122
109
  }
123
110
  const result = {};
124
- for (const [key, parsable2] of Object.entries(schema)) {
111
+ for (const key in schema) {
112
+ if (!schema.hasOwnProperty(key)) continue;
125
113
  const value = url.searchParams.get(key);
126
- result[key] = parsable2.parse(value);
114
+ result[key] = schema[key].parse(value);
127
115
  }
128
116
  return result;
129
117
  }
@@ -214,9 +202,6 @@ var Router = class _Router {
214
202
  params
215
203
  });
216
204
  if (result instanceof Response) {
217
- if (server.enableClientResponseHints) {
218
- result.headers.set("x-kaito-is-response", "1");
219
- }
220
205
  return result;
221
206
  }
222
207
  return response.toResponse({
@@ -262,11 +247,7 @@ var Router = class _Router {
262
247
  };
263
248
 
264
249
  // src/server.ts
265
- function createKaitoHandler(userConfig) {
266
- const config = {
267
- enableClientResponseHints: true,
268
- ...userConfig
269
- };
250
+ function createKaitoHandler(config) {
270
251
  const handle = config.router.freeze(config);
271
252
  return async (request) => {
272
253
  if (config.before) {
@@ -281,6 +262,19 @@ function createKaitoHandler(userConfig) {
281
262
  return response;
282
263
  };
283
264
  }
265
+
266
+ // src/util.ts
267
+ function createUtilities(getContext) {
268
+ return {
269
+ getContext,
270
+ router: () => Router.create()
271
+ };
272
+ }
273
+ function parsable(parse) {
274
+ return {
275
+ parse
276
+ };
277
+ }
284
278
  export {
285
279
  KaitoError,
286
280
  KaitoRequest,
@@ -25,6 +25,7 @@ __export(stream_exports, {
25
25
  SSEController: () => SSEController,
26
26
  sse: () => sse,
27
27
  sseEventToString: () => sseEventToString,
28
+ sseFromAnyReadable: () => sseFromAnyReadable,
28
29
  stream: () => stream
29
30
  });
30
31
  module.exports = __toCommonJS(stream_exports);
@@ -63,8 +64,8 @@ function sseEventToString(event) {
63
64
  result += `retry:${event.retry}
64
65
  `;
65
66
  }
66
- if (event.data) {
67
- result += `data:${event.data}`;
67
+ if (event.data !== void 0) {
68
+ result += `data:${JSON.stringify(event.data)}`;
68
69
  }
69
70
  return result;
70
71
  }
@@ -79,8 +80,11 @@ var SSEController = class {
79
80
  close() {
80
81
  this.controller.close();
81
82
  }
83
+ [Symbol.dispose]() {
84
+ this.close();
85
+ }
82
86
  };
83
- function sse(source) {
87
+ function sseFromSource(source) {
84
88
  const start = source.start;
85
89
  const pull = source.pull;
86
90
  const cancel = source.cancel;
@@ -99,6 +103,33 @@ function sse(source) {
99
103
  });
100
104
  return new KaitoSSEResponse(readable);
101
105
  }
106
+ function sse(source) {
107
+ const evaluated = typeof source === "function" ? source() : source;
108
+ if ("next" in evaluated) {
109
+ const generator = evaluated;
110
+ return sseFromSource({
111
+ async start(controller) {
112
+ try {
113
+ for await (const event of generator) {
114
+ controller.enqueue(event);
115
+ }
116
+ } finally {
117
+ controller.close();
118
+ }
119
+ }
120
+ });
121
+ } else {
122
+ return sseFromSource(evaluated);
123
+ }
124
+ }
125
+ function sseFromAnyReadable(stream2, transform) {
126
+ const transformer = new TransformStream({
127
+ transform: (chunk, controller) => {
128
+ controller.enqueue(transform(chunk));
129
+ }
130
+ });
131
+ return sse(stream2.pipeThrough(transformer));
132
+ }
102
133
  // Annotate the CommonJS export names for ESM import in node:
103
134
  0 && (module.exports = {
104
135
  KaitoSSEResponse,
@@ -106,5 +137,6 @@ function sse(source) {
106
137
  SSEController,
107
138
  sse,
108
139
  sseEventToString,
140
+ sseFromAnyReadable,
109
141
  stream
110
142
  });
@@ -1,35 +1,39 @@
1
- declare class KaitoStreamResponse<T> extends Response {
2
- constructor(body: ReadableStream<T>);
1
+ declare class KaitoStreamResponse<R> extends Response {
2
+ constructor(body: ReadableStream<R>);
3
3
  [Symbol.asyncIterator](): AsyncGenerator<Uint8Array<ArrayBufferLike>, void, unknown>;
4
4
  }
5
- declare class KaitoSSEResponse extends KaitoStreamResponse<string> {
5
+ declare class KaitoSSEResponse<_ClientType> extends KaitoStreamResponse<string> {
6
6
  }
7
- declare function stream<T = string>(body: UnderlyingDefaultSource<T>): KaitoStreamResponse<T>;
8
- type SSEEvent = ({
9
- data: string;
10
- event: string;
7
+ declare function stream<R>(body: UnderlyingDefaultSource<R>): KaitoStreamResponse<R>;
8
+ type SSEEvent<T, E extends string> = ({
9
+ data: T;
10
+ event?: E | undefined;
11
11
  } | {
12
- data: string;
13
- event?: string | undefined;
14
- } | {
15
- data?: string | undefined;
16
- event: string;
12
+ data?: T | undefined;
13
+ event: E;
17
14
  }) & {
18
15
  retry?: number;
19
16
  id?: string;
20
17
  };
21
- declare function sseEventToString(event: SSEEvent): string;
22
- declare class SSEController {
18
+ /**
19
+ * Converts an SSE Event into a string, ready for sending to the client
20
+ * @param event The SSE Event
21
+ * @returns A stringified version
22
+ */
23
+ declare function sseEventToString(event: SSEEvent<unknown, string>): string;
24
+ declare class SSEController<U, E extends string> implements Disposable {
23
25
  private readonly controller;
24
26
  constructor(controller: ReadableStreamDefaultController<string>);
25
- enqueue(event: SSEEvent): void;
27
+ enqueue(event: SSEEvent<U, E>): void;
26
28
  close(): void;
29
+ [Symbol.dispose](): void;
27
30
  }
28
- interface SSESource {
31
+ interface SSESource<U, E extends string> {
29
32
  cancel?: UnderlyingSourceCancelCallback;
30
- start?(controller: SSEController): Promise<void>;
31
- pull?(controller: SSEController): Promise<void>;
33
+ start?(controller: SSEController<U, E>): Promise<void>;
34
+ pull?(controller: SSEController<U, E>): Promise<void>;
32
35
  }
33
- declare function sse(source: SSESource): KaitoSSEResponse;
36
+ declare function sse<U, E extends string, T extends SSEEvent<U, E>>(source: SSESource<U, E> | AsyncGenerator<T, unknown, unknown> | (() => AsyncGenerator<T, unknown, unknown>)): KaitoSSEResponse<T>;
37
+ declare function sseFromAnyReadable<R, U, E extends string>(stream: ReadableStream<R>, transform: (chunk: R) => SSEEvent<U, E>): KaitoSSEResponse<SSEEvent<U, E>>;
34
38
 
35
- export { KaitoSSEResponse, KaitoStreamResponse, SSEController, type SSEEvent, type SSESource, sse, sseEventToString, stream };
39
+ export { KaitoSSEResponse, KaitoStreamResponse, SSEController, type SSEEvent, type SSESource, sse, sseEventToString, sseFromAnyReadable, stream };
@@ -1,35 +1,39 @@
1
- declare class KaitoStreamResponse<T> extends Response {
2
- constructor(body: ReadableStream<T>);
1
+ declare class KaitoStreamResponse<R> extends Response {
2
+ constructor(body: ReadableStream<R>);
3
3
  [Symbol.asyncIterator](): AsyncGenerator<Uint8Array<ArrayBufferLike>, void, unknown>;
4
4
  }
5
- declare class KaitoSSEResponse extends KaitoStreamResponse<string> {
5
+ declare class KaitoSSEResponse<_ClientType> extends KaitoStreamResponse<string> {
6
6
  }
7
- declare function stream<T = string>(body: UnderlyingDefaultSource<T>): KaitoStreamResponse<T>;
8
- type SSEEvent = ({
9
- data: string;
10
- event: string;
7
+ declare function stream<R>(body: UnderlyingDefaultSource<R>): KaitoStreamResponse<R>;
8
+ type SSEEvent<T, E extends string> = ({
9
+ data: T;
10
+ event?: E | undefined;
11
11
  } | {
12
- data: string;
13
- event?: string | undefined;
14
- } | {
15
- data?: string | undefined;
16
- event: string;
12
+ data?: T | undefined;
13
+ event: E;
17
14
  }) & {
18
15
  retry?: number;
19
16
  id?: string;
20
17
  };
21
- declare function sseEventToString(event: SSEEvent): string;
22
- declare class SSEController {
18
+ /**
19
+ * Converts an SSE Event into a string, ready for sending to the client
20
+ * @param event The SSE Event
21
+ * @returns A stringified version
22
+ */
23
+ declare function sseEventToString(event: SSEEvent<unknown, string>): string;
24
+ declare class SSEController<U, E extends string> implements Disposable {
23
25
  private readonly controller;
24
26
  constructor(controller: ReadableStreamDefaultController<string>);
25
- enqueue(event: SSEEvent): void;
27
+ enqueue(event: SSEEvent<U, E>): void;
26
28
  close(): void;
29
+ [Symbol.dispose](): void;
27
30
  }
28
- interface SSESource {
31
+ interface SSESource<U, E extends string> {
29
32
  cancel?: UnderlyingSourceCancelCallback;
30
- start?(controller: SSEController): Promise<void>;
31
- pull?(controller: SSEController): Promise<void>;
33
+ start?(controller: SSEController<U, E>): Promise<void>;
34
+ pull?(controller: SSEController<U, E>): Promise<void>;
32
35
  }
33
- declare function sse(source: SSESource): KaitoSSEResponse;
36
+ declare function sse<U, E extends string, T extends SSEEvent<U, E>>(source: SSESource<U, E> | AsyncGenerator<T, unknown, unknown> | (() => AsyncGenerator<T, unknown, unknown>)): KaitoSSEResponse<T>;
37
+ declare function sseFromAnyReadable<R, U, E extends string>(stream: ReadableStream<R>, transform: (chunk: R) => SSEEvent<U, E>): KaitoSSEResponse<SSEEvent<U, E>>;
34
38
 
35
- export { KaitoSSEResponse, KaitoStreamResponse, SSEController, type SSEEvent, type SSESource, sse, sseEventToString, stream };
39
+ export { KaitoSSEResponse, KaitoStreamResponse, SSEController, type SSEEvent, type SSESource, sse, sseEventToString, sseFromAnyReadable, stream };
@@ -34,8 +34,8 @@ function sseEventToString(event) {
34
34
  result += `retry:${event.retry}
35
35
  `;
36
36
  }
37
- if (event.data) {
38
- result += `data:${event.data}`;
37
+ if (event.data !== void 0) {
38
+ result += `data:${JSON.stringify(event.data)}`;
39
39
  }
40
40
  return result;
41
41
  }
@@ -50,8 +50,11 @@ var SSEController = class {
50
50
  close() {
51
51
  this.controller.close();
52
52
  }
53
+ [Symbol.dispose]() {
54
+ this.close();
55
+ }
53
56
  };
54
- function sse(source) {
57
+ function sseFromSource(source) {
55
58
  const start = source.start;
56
59
  const pull = source.pull;
57
60
  const cancel = source.cancel;
@@ -70,11 +73,39 @@ function sse(source) {
70
73
  });
71
74
  return new KaitoSSEResponse(readable);
72
75
  }
76
+ function sse(source) {
77
+ const evaluated = typeof source === "function" ? source() : source;
78
+ if ("next" in evaluated) {
79
+ const generator = evaluated;
80
+ return sseFromSource({
81
+ async start(controller) {
82
+ try {
83
+ for await (const event of generator) {
84
+ controller.enqueue(event);
85
+ }
86
+ } finally {
87
+ controller.close();
88
+ }
89
+ }
90
+ });
91
+ } else {
92
+ return sseFromSource(evaluated);
93
+ }
94
+ }
95
+ function sseFromAnyReadable(stream2, transform) {
96
+ const transformer = new TransformStream({
97
+ transform: (chunk, controller) => {
98
+ controller.enqueue(transform(chunk));
99
+ }
100
+ });
101
+ return sse(stream2.pipeThrough(transformer));
102
+ }
73
103
  export {
74
104
  KaitoSSEResponse,
75
105
  KaitoStreamResponse,
76
106
  SSEController,
77
107
  sse,
78
108
  sseEventToString,
109
+ sseFromAnyReadable,
79
110
  stream
80
111
  };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@kaito-http/core",
3
- "version": "3.0.0-beta.20",
3
+ "version": "3.0.0-beta.21",
4
4
  "type": "module",
5
5
  "author": "Alistair Smith <hi@alistair.sh>",
6
6
  "description": "Functional HTTP Framework for TypeScript",
7
7
  "scripts": {
8
8
  "build": "tsup",
9
- "test": "node --test --import=tsx ./src/**/*.test.ts",
10
- "attw": "attw --profile node16 --pack ."
9
+ "attw": "attw --profile node16 --pack .",
10
+ "test": "node --test --import=tsx ./src/**/*.test.ts"
11
11
  },
12
12
  "exports": {
13
13
  "./package.json": "./package.json",
@@ -4,7 +4,7 @@ import {KaitoRequest} from '../request.ts';
4
4
  import {KaitoResponse} from '../response.ts';
5
5
  import type {AnyQueryDefinition, AnyRoute, Route} from '../route.ts';
6
6
  import type {ServerConfig} from '../server.ts';
7
- import type {ErroredAPIResponse, MakeOptional, Parsable} from '../util.ts';
7
+ import type {ErroredAPIResponse, Parsable} from '../util.ts';
8
8
  import type {KaitoMethod} from './types.ts';
9
9
 
10
10
  type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> =
@@ -46,9 +46,10 @@ export class Router<ContextFrom, ContextTo, R extends AnyRoute> {
46
46
  }
47
47
 
48
48
  const result: Record<PropertyKey, unknown> = {};
49
- for (const [key, parsable] of Object.entries(schema)) {
49
+ for (const key in schema) {
50
+ if (!schema.hasOwnProperty(key)) continue;
50
51
  const value = url.searchParams.get(key);
51
- result[key] = parsable.parse(value);
52
+ result[key] = (schema[key] as Parsable).parse(value);
52
53
  }
53
54
 
54
55
  return result as {
@@ -111,7 +112,7 @@ export class Router<ContextFrom, ContextTo, R extends AnyRoute> {
111
112
  });
112
113
  };
113
114
 
114
- public freeze = (server: Omit<MakeOptional<ServerConfig<ContextFrom>, 'enableClientResponseHints'>, 'router'>) => {
115
+ public freeze = (server: Omit<ServerConfig<ContextFrom>, 'router'>) => {
115
116
  const routes = new Map<string, Map<KaitoMethod, AnyRoute>>();
116
117
 
117
118
  for (const route of this.state.routes) {
@@ -187,10 +188,6 @@ export class Router<ContextFrom, ContextTo, R extends AnyRoute> {
187
188
  });
188
189
 
189
190
  if (result instanceof Response) {
190
- if (server.enableClientResponseHints) {
191
- result.headers.set('x-kaito-is-response', '1');
192
- }
193
-
194
191
  return result;
195
192
  }
196
193
 
package/src/server.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {KaitoError} from './error.ts';
2
2
  import type {KaitoRequest} from './request.ts';
3
3
  import type {Router} from './router/router.ts';
4
- import type {GetContext, MakeOptional} from './util.ts';
4
+ import type {GetContext} from './util.ts';
5
5
 
6
6
  export type Before = (req: Request) => Promise<Response | void | undefined>;
7
7
 
@@ -64,34 +64,9 @@ export type ServerConfig<ContextFrom> = {
64
64
  * ```
65
65
  */
66
66
  transform?: (req: Request, res: Response) => Promise<Response | void | undefined>;
67
-
68
- /**
69
- * Controls whether the server includes a header to indicate non-JSON responses.
70
- *
71
- * When a route handler returns a `Response` object, this setting determines if
72
- * the server adds a header indicating the response should not be parsed as JSON.
73
- *
74
- * The `@kaito-http/client` package checks for this header's presence:
75
- * - If present: Returns the raw Response object
76
- * - If absent: Attempts to parse the response as JSON
77
- *
78
- * You might want to disable this feature when:
79
- * 1. Using a custom client that doesn't recognize this header
80
- * 2. Security requirements prevent exposing framework details in headers
81
- *
82
- * @default true
83
- */
84
- enableClientResponseHints: boolean;
85
67
  };
86
68
 
87
- export function createKaitoHandler<Context>(
88
- userConfig: MakeOptional<ServerConfig<Context>, 'enableClientResponseHints'>,
89
- ) {
90
- const config: ServerConfig<Context> = {
91
- enableClientResponseHints: true,
92
- ...userConfig,
93
- };
94
-
69
+ export function createKaitoHandler<Context>(config: ServerConfig<Context>) {
95
70
  const handle = config.router.freeze(config);
96
71
 
97
72
  return async (request: Request): Promise<Response> => {
@@ -129,12 +129,13 @@ export function sse<U, E extends string, T extends SSEEvent<U, E>>(
129
129
  // TODO: use `using` once Node.js supports it
130
130
  // // ensures close is called on controller when we're done
131
131
  // using c = controller;
132
-
133
- for await (const event of generator) {
134
- controller.enqueue(event);
132
+ try {
133
+ for await (const event of generator) {
134
+ controller.enqueue(event);
135
+ }
136
+ } finally {
137
+ controller.close();
135
138
  }
136
-
137
- controller.close();
138
139
  },
139
140
  });
140
141
  } else {