@rsc-kit/core 0.24.3 → 0.25.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/dist/events.d.ts +25 -9
- package/dist/events.js.map +1 -1
- package/dist/host.js +8 -5
- package/dist/host.js.map +1 -1
- package/dist/js/useEvents.d.ts +11 -5
- package/dist/js/useEvents.js +70 -21
- package/dist/js/useEvents.js.map +1 -1
- package/dist/routes.d.ts +31 -0
- package/dist/routes.js.map +1 -1
- package/dist/vite.js +23 -0
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/events.d.ts
CHANGED
|
@@ -27,11 +27,11 @@ export interface EventsInput<P = Record<string, string>> {
|
|
|
27
27
|
request: Request;
|
|
28
28
|
}
|
|
29
29
|
/** A message with a name or an id, for a client that listens by name or resumes. Made by `named()`. */
|
|
30
|
-
export interface NamedEvent {
|
|
30
|
+
export interface NamedEvent<E extends string = string, D = unknown> {
|
|
31
31
|
readonly [NAMED]: true;
|
|
32
|
-
event?:
|
|
32
|
+
event?: E;
|
|
33
33
|
id?: string;
|
|
34
|
-
data:
|
|
34
|
+
data: D;
|
|
35
35
|
}
|
|
36
36
|
declare const NAMED: unique symbol;
|
|
37
37
|
/**
|
|
@@ -41,17 +41,33 @@ declare const NAMED: unique symbol;
|
|
|
41
41
|
* have a `data` key is still data, and the generator's own type stays the
|
|
42
42
|
* type of what it yields.
|
|
43
43
|
*/
|
|
44
|
-
export declare function named<D>(event:
|
|
44
|
+
export declare function named<const E extends string, D>(event: E, data: D, options?: {
|
|
45
45
|
id?: string;
|
|
46
|
-
}): NamedEvent
|
|
46
|
+
}): NamedEvent<E, D>;
|
|
47
47
|
/** How often a comment goes out on an idle stream, so a proxy keeps it open. */
|
|
48
48
|
export declare const KEEPALIVE_MS = 15000;
|
|
49
49
|
/** One message, framed. */
|
|
50
50
|
export declare function frame(value: unknown): string;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/**
|
|
52
|
+
* What `events()` returns: a GET handler that also says, as a type only, what
|
|
53
|
+
* its generator yields. Nothing reads `~events` at runtime; the build records
|
|
54
|
+
* it per url, which is how `useEvents('/api/orders/42/events')` types a
|
|
55
|
+
* message without being told - and stops compiling when the route changes
|
|
56
|
+
* what it sends.
|
|
57
|
+
*/
|
|
58
|
+
export type EventsRoute<P = Record<string, string>, Y = unknown> = ((request: Request, input: {
|
|
54
59
|
params: Promise<P>;
|
|
55
60
|
searchParams: Promise<URLSearchParams>;
|
|
56
|
-
}) => Response
|
|
61
|
+
}) => Response) & {
|
|
62
|
+
readonly "~events"?: {
|
|
63
|
+
yields: Y;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/** The unnamed messages among a stream's yields: what `useEvents` receives without `event`. */
|
|
67
|
+
export type MessageOf<Y> = Exclude<Y, NamedEvent<string, unknown>>;
|
|
68
|
+
/** The data of the messages named `E`. A name computed at runtime matches any. */
|
|
69
|
+
export type NamedMessageOf<Y, E extends string> = Y extends NamedEvent<infer N, infer D> ? (E extends N ? D : never) : never;
|
|
70
|
+
export declare function events<P = Record<string, string>, Y = unknown>(produce: (input: EventsInput<P>) => AsyncIterable<Y>, options?: {
|
|
71
|
+
keepaliveMs?: number;
|
|
72
|
+
}): EventsRoute<P, Y>;
|
|
57
73
|
export {};
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAkBH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CACnB,
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A route that streams: server-sent events from an async generator.\n *\n * // app/api/orders/[id]/events/route.ts\n * export const GET = events(async function* ({ params, signal }) {\n * for await (const status of orderStatus((await params).id, { signal })) {\n * yield { status }\n * }\n * })\n *\n * An ordinary route.ts, so it lives beside its pages, runs the middleware.ts\n * above it and takes the same params. What this adds is the framing - one\n * `data:` line per yield, JSON - a keepalive comment every fifteen seconds\n * so a proxy does not drop an idle stream, the headers a stream needs, and\n * an end to the generator when the browser goes away, which is what `signal`\n * is for. `useEvents` on the client reads it.\n *\n * Not a query. A query answers once and is cacheable; a stream answers for\n * as long as the tab is open and is not. A server component cannot await a\n * stream, so it has no place in the payload either.\n */\n\nexport interface EventsInput<P = Record<string, string>> {\n params: Promise<P>;\n searchParams: Promise<URLSearchParams>;\n /** Aborted when the browser disconnects. Pass it on, or check `aborted`. */\n signal: AbortSignal;\n request: Request;\n}\n\n/** A message with a name or an id, for a client that listens by name or resumes. Made by `named()`. */\nexport interface NamedEvent<E extends string = string, D = unknown> {\n readonly [NAMED]: true;\n event?: E;\n id?: string;\n data: D;\n}\n\nconst NAMED = Symbol.for(\"@rsc-kit/core.named-event\");\n\n/**\n * A message with a name or an id: `yield named('paid', order)`, or\n * `named('paid', order, { id: order.id })` for one a client can resume from.\n * Built by a function rather than by shape, so a message that happens to\n * have a `data` key is still data, and the generator's own type stays the\n * type of what it yields.\n */\nexport function named<const E extends string, D>(\n event: E,\n data: D,\n options: { id?: string } = {},\n): NamedEvent<E, D> {\n return { [NAMED]: true, event, id: options.id, data };\n}\n\n/** How often a comment goes out on an idle stream, so a proxy keeps it open. */\nexport const KEEPALIVE_MS = 15_000;\n\nfunction isNamed(value: unknown): value is NamedEvent {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { [NAMED]?: true })[NAMED] === true\n );\n}\n\n/** One message, framed. */\nexport function frame(value: unknown): string {\n const named = isNamed(value) ? value : null;\n const lines: string[] = [];\n\n if (named?.event) lines.push(`event: ${named.event}`);\n if (named?.id) lines.push(`id: ${named.id}`);\n lines.push(`data: ${JSON.stringify(named ? named.data : value)}`);\n\n return lines.join(\"\\n\") + \"\\n\\n\";\n}\n\n/**\n * What `events()` returns: a GET handler that also says, as a type only, what\n * its generator yields. Nothing reads `~events` at runtime; the build records\n * it per url, which is how `useEvents('/api/orders/42/events')` types a\n * message without being told - and stops compiling when the route changes\n * what it sends.\n */\nexport type EventsRoute<P = Record<string, string>, Y = unknown> = ((\n request: Request,\n input: { params: Promise<P>; searchParams: Promise<URLSearchParams> },\n) => Response) & { readonly \"~events\"?: { yields: Y } };\n\n/** The unnamed messages among a stream's yields: what `useEvents` receives without `event`. */\nexport type MessageOf<Y> = Exclude<Y, NamedEvent<string, unknown>>;\n\n/** The data of the messages named `E`. A name computed at runtime matches any. */\nexport type NamedMessageOf<Y, E extends string> =\n Y extends NamedEvent<infer N, infer D> ? (E extends N ? D : never) : never;\n\nexport function events<P = Record<string, string>, Y = unknown>(\n produce: (input: EventsInput<P>) => AsyncIterable<Y>,\n options: { keepaliveMs?: number } = {},\n): EventsRoute<P, Y> {\n const keepaliveMs = options.keepaliveMs ?? KEEPALIVE_MS;\n\n return (\n request: Request,\n input: { params: Promise<P>; searchParams: Promise<URLSearchParams> },\n ) => {\n const controller = new AbortController();\n const encoder = new TextEncoder();\n\n request.signal.addEventListener(\"abort\", () => controller.abort(), {\n once: true,\n });\n\n const stream = new ReadableStream<Uint8Array>({\n async start(sink) {\n // The retry hint first, so a browser that loses the connection waits\n // a moment rather than hammering.\n sink.enqueue(encoder.encode(\"retry: 1000\\n\\n\"));\n\n const keepalive = setInterval(() => {\n try {\n sink.enqueue(encoder.encode(\": keepalive\\n\\n\"));\n } catch {\n // Closed between the check and the write; the loop below ends it.\n }\n }, keepaliveMs);\n\n try {\n for await (const value of produce({\n ...input,\n signal: controller.signal,\n request,\n })) {\n if (controller.signal.aborted) break;\n\n sink.enqueue(encoder.encode(frame(value)));\n }\n } catch (error) {\n if (!controller.signal.aborted) sink.error(error);\n } finally {\n clearInterval(keepalive);\n\n if (!controller.signal.aborted) {\n try {\n sink.close();\n } catch {\n // Already closed by the consumer.\n }\n }\n }\n },\n cancel() {\n controller.abort();\n },\n });\n\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n // Nginx buffers by default, which turns a stream into one late burst.\n \"X-Accel-Buffering\": \"no\",\n },\n });\n };\n}\n"]}
|
package/dist/host.js
CHANGED
|
@@ -186,12 +186,15 @@ function refusalStatus(error) {
|
|
|
186
186
|
return 401;
|
|
187
187
|
if (name === "ServerAuthorizationError")
|
|
188
188
|
return 403;
|
|
189
|
-
// A status the host chose — a throttle's 429, a policy's 403
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
189
|
+
// A status the host chose — a throttle's 429, a policy's 403, a 503 while
|
|
190
|
+
// a dependency is down or the app is in maintenance — carried on the error
|
|
191
|
+
// by the transport. Only a deliberate refusal carries one (Go's Refuse,
|
|
192
|
+
// Laravel's abort); a crash never does, so a 5xx here was meant, and
|
|
193
|
+
// collapsing a 503 to a 500 tells a client to give up rather than come
|
|
194
|
+
// back. Bounded to errors: a host answering 200 or 3xx should not turn a
|
|
195
|
+
// failed render into a success or a redirect with no Location.
|
|
193
196
|
const carried = error?.refusalStatus;
|
|
194
|
-
if (typeof carried === "number" && carried >= 400 && carried <=
|
|
197
|
+
if (typeof carried === "number" && carried >= 400 && carried <= 599)
|
|
195
198
|
return carried;
|
|
196
199
|
return null;
|
|
197
200
|
}
|