@revojs/server 0.1.58

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.
@@ -0,0 +1,116 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import { Router, type RouterContext, type Scope } from "revojs";
3
+ import type { Awaitable, Mergeable } from "revojs/shared";
4
+ export type ScheduleWhen = "@yearly" | "@annually" | "@monthly" | "@weekly" | "@daily" | "@midnight" | "@hourly" | "* * * * *" | "0 * * * *" | "0 0 * * *" | "0 0 * * 0" | "0 0 1 * *" | "0 0 1 1 *" | `${string} ${string} ${string} ${string} ${string}` | (string & {});
5
+ export type CookiePriority = (typeof cookiePriorities)[number];
6
+ export type CookieSameSite = (typeof cookieSameSites)[number];
7
+ export type HttpMethod = (typeof httpMethods)[number];
8
+ export type Encoding = (typeof encodings)[number];
9
+ export type StatusCode = (typeof statusCodes)[number];
10
+ export type Result = Awaitable<void | Response | WebSocket>;
11
+ export interface CookieOptions {
12
+ domain?: string;
13
+ expires?: Date;
14
+ httpOnly?: boolean;
15
+ maxAge?: number;
16
+ path?: string;
17
+ priority?: CookiePriority;
18
+ sameSite?: CookieSameSite;
19
+ secure?: boolean;
20
+ }
21
+ export interface ResponseConfig extends Record<string, unknown> {
22
+ status?: StatusCode;
23
+ statusText?: string;
24
+ headers: Headers;
25
+ }
26
+ export interface ServerContext {
27
+ states: Record<string, unknown>;
28
+ request: Request;
29
+ response: ResponseConfig;
30
+ }
31
+ export interface WebSocketConnection {
32
+ send: (message: string | BufferSource) => Promise<void>;
33
+ close: (code?: number, reason?: string) => Promise<void>;
34
+ }
35
+ export interface WebSocket {
36
+ open: (connection: WebSocketConnection) => Awaitable<void>;
37
+ message: (connection: WebSocketConnection, message: string | ArrayBuffer | ArrayBufferView) => Awaitable<void>;
38
+ error: (connection: WebSocketConnection, error: Error) => Awaitable<void>;
39
+ close: (connection: WebSocketConnection, code?: number, reason?: string) => Awaitable<void>;
40
+ }
41
+ export interface Schedule {
42
+ when: ScheduleWhen;
43
+ run: (scope: Scope) => Awaitable<void>;
44
+ }
45
+ export interface ScheduleContext {
46
+ when: string;
47
+ }
48
+ export interface Route {
49
+ fetch: (scope: Scope) => Result;
50
+ }
51
+ export interface Middleware {
52
+ fetch: (scope: Scope, next?: () => Result) => Result;
53
+ }
54
+ export interface Server extends Route {
55
+ router: Router<Route>;
56
+ schedules: Array<Schedule>;
57
+ schedule: (scope: Scope) => Promise<void>;
58
+ }
59
+ export declare function defineRoute<T extends Route>(route: T): T;
60
+ export declare function defineMiddleware<T extends Middleware>(middleware: T): T;
61
+ export declare function defineSchedule<T extends Schedule>(schedule: T): T;
62
+ export declare function createWebSocket<T extends WebSocket>(scope: Scope, webSocket: T): T;
63
+ export declare function isResponse(result: Result): result is Response;
64
+ export declare function isWebSocket(result: Result): result is WebSocket;
65
+ export declare function useRouter(scope: Scope): RouterContext<Route>;
66
+ export declare function useServer(scope: Scope): ServerContext;
67
+ export declare function useSchedule(scope: Scope): ScheduleContext;
68
+ export declare function useUrl(scope: Scope, base?: string): URL;
69
+ export declare function useParameters(scope: Scope): Record<string, string>;
70
+ export declare function useParameters<T extends StandardSchemaV1>(scope: Scope, schema: T): StandardSchemaV1.InferOutput<T>;
71
+ export declare function useParametersAsync<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
72
+ export declare function useHeaders(scope: Scope): Record<string, string>;
73
+ export declare function useHeaders<T extends StandardSchemaV1>(scope: Scope, schema: T): StandardSchemaV1.InferOutput<T>;
74
+ export declare function useHeadersAsync<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
75
+ export declare function useQuery(scope: Scope): Record<string, string>;
76
+ export declare function useQuery<T extends StandardSchemaV1>(scope: Scope, schema: T): StandardSchemaV1.InferOutput<T>;
77
+ export declare function useQueryAsync<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
78
+ export declare function withQuery(scope: Scope, input: string, query: Record<string, string>): string;
79
+ export declare function useBody(scope: Scope): Promise<Record<string, unknown>>;
80
+ export declare function useBody<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
81
+ export declare function useBodyAsync<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
82
+ export declare function parseCookiePair(cookie: string): readonly [string, string] | undefined;
83
+ export declare function useCookies(scope: Scope): Record<string, string>;
84
+ export declare function useCookies<T extends StandardSchemaV1>(scope: Scope, schema: T): StandardSchemaV1.InferOutput<T>;
85
+ export declare function useCookiesAsync<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
86
+ export declare function useSetCookies(scope: Scope): Record<string, string>;
87
+ export declare function useSetCookies<T extends StandardSchemaV1>(scope: Scope, schema: T): StandardSchemaV1.InferOutput<T>;
88
+ export declare function useSetCookiesAsync<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
89
+ export declare function setCookie(scope: Scope, name: string, value: string, options?: CookieOptions): void;
90
+ export declare function deleteCookie(scope: Scope, name: string, options?: Omit<CookieOptions, "expires" | "maxAge">): void;
91
+ export declare function sendResponse<T>(scope: Scope, body?: T | BodyInit, config?: Mergeable<ResponseConfig>): Response;
92
+ export declare function sendOk<T>(scope: Scope, body?: T | BodyInit, config?: Mergeable<ResponseConfig>): Response;
93
+ export declare function sendCreated<T>(scope: Scope, body?: T | BodyInit, config?: Mergeable<ResponseConfig>): Response;
94
+ export declare function sendNoContent(scope: Scope, config?: Mergeable<ResponseConfig>): Response;
95
+ export declare function sendRedirect(scope: Scope, path: string, config?: Mergeable<ResponseConfig>): Response;
96
+ export declare function sendBadRequest<T>(scope: Scope, body?: T | BodyInit, config?: Mergeable<ResponseConfig>): Response;
97
+ export declare function sendUnauthorized<T>(scope: Scope, body?: T | BodyInit, config?: Mergeable<ResponseConfig>): Response;
98
+ export declare function sendForbidden<T>(scope: Scope, body?: T | BodyInit, config?: Mergeable<ResponseConfig>): Response;
99
+ export declare function sendNotFound<T>(scope: Scope, body?: T | BodyInit, config?: Mergeable<ResponseConfig>): Response;
100
+ export declare function getState<T>(scope: Scope, name: string): T;
101
+ export declare function setState<T>(scope: Scope, name: string, value: T): void;
102
+ export declare function fetch<T>(scope: Scope, input: string | URL, options?: RequestInit): Promise<T>;
103
+ export declare function invoke(scope: Scope, pipeline: Array<Middleware>, index?: number): Promise<Result>;
104
+ export declare function createStaticMiddleware(resolve: (path: string | URL) => Awaitable<Blob>): {
105
+ fetch(scope: Scope, next: (() => Result) | undefined): Promise<void | Response | WebSocket>;
106
+ };
107
+ export declare function createServer(): Promise<Server>;
108
+ export declare let STATES: Record<string, unknown>;
109
+ export declare const SERVER_CONTEXT: import("revojs").Descriptor<ServerContext>;
110
+ export declare const SERVER_ROUTER_CONTEXT: import("revojs").Descriptor<RouterContext<Route>>;
111
+ export declare const SCHEDULE_CONTEXT: import("revojs").Descriptor<ScheduleContext>;
112
+ export declare const cookiePriorities: readonly ["Low", "Medium", "High"];
113
+ export declare const cookieSameSites: readonly ["Lax", "Strict", "None"];
114
+ export declare const httpMethods: readonly ["GET", "HEAD", "PATCH", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"];
115
+ export declare const encodings: readonly ["ascii", "utf8", "utf8", "utf16le", "ucs2", "ucs-2", "base64", "latin1", "binary", "hex"];
116
+ export declare const statusCodes: readonly [100, 101, 102, 103, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 307, 308, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 420, 421, 422, 423, 424, 425, 426, 428, 429, 431, 444, 450, 451, 497, 498, 499, 500, 501, 502, 503, 504, 506, 507, 508, 509, 510, 511, 521, 522, 523, 525, 530, 599];
package/dist/index.js ADDED
@@ -0,0 +1,471 @@
1
+ import assets from "#assets";
2
+ import routes from "#routes";
3
+ import schedules from "#schedules";
4
+ import { isClient, isServer } from "#variables";
5
+ import { Router, VARIABLE_CONTEXT, createScope, defineContext, getContext, setContext, useRoute, useVariables } from "revojs";
6
+ import { scheduleAliases } from "revojs/shared";
7
+ //#region src/index.ts
8
+ function defineRoute(route) {
9
+ return route;
10
+ }
11
+ function defineMiddleware(middleware) {
12
+ return middleware;
13
+ }
14
+ function defineSchedule(schedule) {
15
+ return schedule;
16
+ }
17
+ function createWebSocket(scope, webSocket) {
18
+ const { upgrade } = useHeaders(scope);
19
+ if (upgrade === "websocket") return webSocket;
20
+ throw sendBadRequest(scope);
21
+ }
22
+ function isResponse(result) {
23
+ return result instanceof Response;
24
+ }
25
+ function isWebSocket(result) {
26
+ return typeof result === "object" && "open" in result && "message" in result && "close" in result && "error" in result;
27
+ }
28
+ function parseSchema(scope, schema, value) {
29
+ const output = schema["~standard"].validate(value);
30
+ if (output instanceof Promise) throw new Error("Schema validation is async. Use the async variant (e.g., useHeadersAsync).");
31
+ if ("issues" in output) throw sendBadRequest(scope, output);
32
+ return output.value;
33
+ }
34
+ async function parseSchemaAsync(scope, schema, value) {
35
+ const output = await schema["~standard"].validate(value);
36
+ if ("issues" in output) throw sendBadRequest(scope, output);
37
+ return output.value;
38
+ }
39
+ function useRouter(scope) {
40
+ return getContext(scope, SERVER_ROUTER_CONTEXT);
41
+ }
42
+ function useServer(scope) {
43
+ return getContext(scope, SERVER_CONTEXT);
44
+ }
45
+ function useSchedule(scope) {
46
+ return getContext(scope, SCHEDULE_CONTEXT);
47
+ }
48
+ function useUrl(scope, base) {
49
+ const { request } = useServer(scope);
50
+ const fallback = typeof globalThis.window === "undefined" ? "about:blank" : globalThis.location.href;
51
+ return new URL(request?.url ?? fallback, base);
52
+ }
53
+ function useParameters(scope, schema) {
54
+ const { parameters } = useRoute(scope);
55
+ return schema ? parseSchema(scope, schema, parameters) : parameters;
56
+ }
57
+ function useParametersAsync(scope, schema) {
58
+ const { parameters } = useRoute(scope);
59
+ return parseSchemaAsync(scope, schema, parameters);
60
+ }
61
+ function useHeaders(scope, schema) {
62
+ const { request } = useServer(scope);
63
+ const entries = Object.fromEntries(request?.headers ?? []);
64
+ return schema ? parseSchema(scope, schema, entries) : entries;
65
+ }
66
+ function useHeadersAsync(scope, schema) {
67
+ const { request } = useServer(scope);
68
+ return parseSchemaAsync(scope, schema, Object.fromEntries(request?.headers ?? []));
69
+ }
70
+ function useQuery(scope, schema) {
71
+ const { searchParams } = useUrl(scope);
72
+ const entries = Object.fromEntries(searchParams);
73
+ return schema ? parseSchema(scope, schema, entries) : entries;
74
+ }
75
+ function useQueryAsync(scope, schema) {
76
+ const { searchParams } = useUrl(scope);
77
+ return parseSchemaAsync(scope, schema, searchParams);
78
+ }
79
+ function withQuery(scope, input, query) {
80
+ const url = new URL(input, useUrl(scope));
81
+ for (const name in query) url.searchParams.set(name, query[name]);
82
+ return url.href;
83
+ }
84
+ async function readBody(request) {
85
+ const contentType = request.headers.get("Content-Type");
86
+ if (contentType?.includes("application/json")) return request.json();
87
+ if (contentType?.includes("multipart/form-data") || contentType?.includes("application/x-www-form-urlencoded")) {
88
+ const formData = await request.formData();
89
+ return Object.fromEntries(formData.entries());
90
+ }
91
+ return {};
92
+ }
93
+ async function useBody(scope, schema) {
94
+ const { request } = useServer(scope);
95
+ const entries = await readBody(request);
96
+ return schema ? parseSchema(scope, schema, entries) : entries;
97
+ }
98
+ async function useBodyAsync(scope, schema) {
99
+ const { request } = useServer(scope);
100
+ return parseSchemaAsync(scope, schema, await readBody(request));
101
+ }
102
+ function parseCookiePair(cookie) {
103
+ const index = cookie.indexOf("=");
104
+ if (index <= 0) return;
105
+ const name = cookie.slice(0, index).trim();
106
+ const value = cookie.slice(index + 1);
107
+ if (name.length === 0) return;
108
+ try {
109
+ return [name, decodeURIComponent(value)];
110
+ } catch {
111
+ return [name, value];
112
+ }
113
+ }
114
+ function useCookies(scope, schema) {
115
+ const { request } = useServer(scope);
116
+ const entries = (isClient ? document.cookie : request.headers.get("Cookie") ?? "").split(/;\s*/).reduce((result, cookie) => {
117
+ const pair = parseCookiePair(cookie);
118
+ if (pair) result[pair[0]] = pair[1];
119
+ return result;
120
+ }, {});
121
+ return schema ? parseSchema(scope, schema, entries) : entries;
122
+ }
123
+ function useCookiesAsync(scope, schema) {
124
+ const { request } = useServer(scope);
125
+ return parseSchemaAsync(scope, schema, (isClient ? document.cookie : request.headers.get("Cookie") ?? "").split(/;\s*/).reduce((result, cookie) => {
126
+ const pair = parseCookiePair(cookie);
127
+ if (pair) result[pair[0]] = pair[1];
128
+ return result;
129
+ }, {}));
130
+ }
131
+ function useSetCookies(scope, schema) {
132
+ const { request } = useServer(scope);
133
+ const entries = (request.headers.getSetCookie?.() ?? []).reduce((result, cookie) => {
134
+ const pair = parseCookiePair(cookie.split(";").at(0) ?? "");
135
+ if (pair) result[pair[0]] = pair[1];
136
+ return result;
137
+ }, {});
138
+ return schema ? parseSchema(scope, schema, entries) : entries;
139
+ }
140
+ function useSetCookiesAsync(scope, schema) {
141
+ const { request } = useServer(scope);
142
+ return parseSchemaAsync(scope, schema, (request.headers.getSetCookie?.() ?? []).reduce((result, cookie) => {
143
+ const pair = parseCookiePair(cookie.split(";").at(0) ?? "");
144
+ if (pair) result[pair[0]] = pair[1];
145
+ return result;
146
+ }, {}));
147
+ }
148
+ function setCookie(scope, name, value, options) {
149
+ const { response } = useServer(scope);
150
+ let cookie = name + "=" + encodeURIComponent(value);
151
+ if (options?.domain) cookie += `; Domain=${options.domain}`;
152
+ if (options?.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
153
+ if (options?.httpOnly) cookie += `; HttpOnly`;
154
+ if (options?.maxAge !== void 0) cookie += `; Max-Age=${options.maxAge}`;
155
+ if (options?.path) cookie += `; Path=${options.path}`;
156
+ if (options?.priority) cookie += `; Priority=${options.priority}`;
157
+ if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
158
+ if (options?.secure) cookie += `; Secure`;
159
+ if (isServer) response.headers.append("Set-Cookie", cookie);
160
+ else document.cookie = cookie;
161
+ }
162
+ function deleteCookie(scope, name, options) {
163
+ setCookie(scope, name, "", {
164
+ ...options,
165
+ maxAge: 0
166
+ });
167
+ }
168
+ function sendResponse(scope, body, config) {
169
+ const { response } = useServer(scope);
170
+ if (body !== null && body !== void 0 && !response.headers.has("Content-Type")) {
171
+ if (typeof body === "object" && !ArrayBuffer.isView(body)) {
172
+ if (body instanceof FormData) response.headers.set("Content-Type", "multipart/form-data");
173
+ else if (body instanceof URLSearchParams) response.headers.set("Content-Type", "application/x-www-form-urlencoded");
174
+ else if (body instanceof ReadableStream) response.headers.set("Content-Type", "application/octet-stream");
175
+ else if (!(body instanceof Blob)) {
176
+ response.headers.set("Content-Type", "application/json");
177
+ body = JSON.stringify(body);
178
+ }
179
+ } else if (typeof body === "string") {
180
+ const content = body.trimStart().toLowerCase();
181
+ if (content.startsWith("<!doctype") || content.startsWith("<html") || content.startsWith("<head")) response.headers.set("Content-Type", "text/html");
182
+ else response.headers.set("Content-Type", "text/plain");
183
+ } else if (typeof body === "boolean" || typeof body === "number") {
184
+ response.headers.set("Content-Type", "application/json");
185
+ body = JSON.stringify(body);
186
+ }
187
+ }
188
+ if (config?.headers instanceof Headers) for (const [key, value] of config.headers.entries()) response.headers.set(key, value);
189
+ const { status, statusText, headers, ...variables } = {
190
+ ...response,
191
+ ...config
192
+ };
193
+ return new Response(body, {
194
+ ...variables,
195
+ status,
196
+ statusText,
197
+ headers
198
+ });
199
+ }
200
+ function sendOk(scope, body, config) {
201
+ const { response } = useServer(scope);
202
+ response.status = 200;
203
+ return sendResponse(scope, body, config);
204
+ }
205
+ function sendCreated(scope, body, config) {
206
+ const { response } = useServer(scope);
207
+ response.status = 201;
208
+ return sendResponse(scope, body, config);
209
+ }
210
+ function sendNoContent(scope, config) {
211
+ const { response } = useServer(scope);
212
+ response.status = 204;
213
+ return sendResponse(scope, void 0, config);
214
+ }
215
+ function sendRedirect(scope, path, config) {
216
+ const { response } = useServer(scope);
217
+ response.status = 302;
218
+ response.headers.set("Location", path);
219
+ return sendResponse(scope, void 0, config);
220
+ }
221
+ function sendBadRequest(scope, body, config) {
222
+ const { response } = useServer(scope);
223
+ response.status = 400;
224
+ return sendResponse(scope, body, config);
225
+ }
226
+ function sendUnauthorized(scope, body, config) {
227
+ const { response } = useServer(scope);
228
+ response.status = 401;
229
+ return sendResponse(scope, body, config);
230
+ }
231
+ function sendForbidden(scope, body, config) {
232
+ const { response } = useServer(scope);
233
+ response.status = 403;
234
+ return sendResponse(scope, body, config);
235
+ }
236
+ function sendNotFound(scope, body, config) {
237
+ const { response } = useServer(scope);
238
+ response.status = 404;
239
+ return sendResponse(scope, body, config);
240
+ }
241
+ function getState(scope, name) {
242
+ if (isServer) {
243
+ const { states } = useServer(scope);
244
+ return states[name];
245
+ } else {
246
+ if (STATES === void 0) {
247
+ const element = document.getElementById("states");
248
+ STATES = element?.textContent ? JSON.parse(element.textContent) : {};
249
+ }
250
+ return STATES[name];
251
+ }
252
+ }
253
+ function setState(scope, name, value) {
254
+ if (isServer) {
255
+ const { states } = useServer(scope);
256
+ states[name] = value;
257
+ } else {
258
+ if (STATES === void 0) {
259
+ const element = document.getElementById("states");
260
+ STATES = element?.textContent ? JSON.parse(element.textContent) : {};
261
+ }
262
+ STATES[name] = value;
263
+ }
264
+ }
265
+ async function fetch(scope, input, options) {
266
+ let response;
267
+ if (isServer) {
268
+ const { states, request } = useServer(scope);
269
+ const url = new URL(input.toString(), request.url);
270
+ const next = await createScope({ context: {
271
+ [VARIABLE_CONTEXT]: useVariables(scope),
272
+ [SERVER_CONTEXT]: {
273
+ states,
274
+ request: new Request(url, options),
275
+ response: { headers: new Headers() }
276
+ }
277
+ } });
278
+ const previous = new URL(request.url);
279
+ if (url.origin === previous.origin) {
280
+ const result = await (await import("#server")).default.fetch(next);
281
+ if (isResponse(result)) response = result;
282
+ }
283
+ }
284
+ response ??= await globalThis.fetch(input, options);
285
+ if (response.ok === false) throw response;
286
+ switch (response.headers.get("Content-Type")?.split(";").shift() ?? "") {
287
+ case "application/json": return response.json();
288
+ case "text/plain":
289
+ case "text/html":
290
+ case "text/css":
291
+ case "text/xml":
292
+ case "application/xml":
293
+ case "application/javascript": return response.text();
294
+ case "application/octet-stream":
295
+ case "application/pdf":
296
+ case "application/zip":
297
+ case "image/png":
298
+ case "image/jpeg":
299
+ case "image/gif":
300
+ case "image/webp":
301
+ case "image/svg+xml":
302
+ case "audio/mpeg":
303
+ case "video/mp4": return response.blob();
304
+ case "multipart/form-data": return response.formData();
305
+ default: return response;
306
+ }
307
+ }
308
+ async function invoke(scope, pipeline, index = 0) {
309
+ return await pipeline.at(index)?.fetch(scope, async () => await invoke(scope, pipeline, index + 1));
310
+ }
311
+ function createStaticMiddleware(resolve) {
312
+ return defineMiddleware({ async fetch(scope, next) {
313
+ const { pathname } = useUrl(scope);
314
+ const asset = assets[pathname];
315
+ if (asset) return sendOk(scope, await resolve(asset.relative), { headers: new Headers({ "Content-Type": asset.type }) });
316
+ return next?.();
317
+ } });
318
+ }
319
+ async function createServer() {
320
+ const router = new Router();
321
+ const scheduled = new Array();
322
+ for (const source of Object.values(routes)) for (const [path, resolve] of Object.entries(source)) {
323
+ const segments = path.split("/");
324
+ for (const attribute of segments.pop()?.split(".") ?? []) if (httpMethods.includes(attribute.toUpperCase())) segments.unshift(attribute);
325
+ else segments.push(attribute);
326
+ router.use(segments, await resolve());
327
+ }
328
+ for (const source of Object.values(schedules)) for (const resolve of Object.values(source)) {
329
+ const schedule = await resolve();
330
+ if (schedule) scheduled.push(schedule);
331
+ }
332
+ return {
333
+ router,
334
+ schedules: scheduled,
335
+ async fetch(scope) {
336
+ try {
337
+ const { request } = useServer(scope);
338
+ const { pathname } = useUrl(scope);
339
+ const context = setContext(scope, SERVER_ROUTER_CONTEXT, await router.resolve(scope, (request.method.toLowerCase() + pathname).split("/")));
340
+ if (context.match) context.hooks.push(context.match);
341
+ return await invoke(scope, context.hooks) ?? sendNotFound(scope);
342
+ } catch (error) {
343
+ if (error instanceof Response) return error;
344
+ throw error;
345
+ }
346
+ },
347
+ async schedule(scope) {
348
+ const { when } = useSchedule(scope);
349
+ const expression = scheduleAliases[when.trim().toLowerCase()] ?? when.trim();
350
+ for (const schedule of scheduled) {
351
+ if ((scheduleAliases[schedule.when.trim().toLowerCase()] ?? schedule.when.trim()) !== expression) continue;
352
+ await schedule.run(scope);
353
+ }
354
+ }
355
+ };
356
+ }
357
+ var STATES;
358
+ var SERVER_CONTEXT = defineContext("SERVER_CONTEXT");
359
+ var SERVER_ROUTER_CONTEXT = defineContext("SERVER_ROUTER_CONTEXT");
360
+ var SCHEDULE_CONTEXT = defineContext("SCHEDULE_CONTEXT");
361
+ var cookiePriorities = [
362
+ "Low",
363
+ "Medium",
364
+ "High"
365
+ ];
366
+ var cookieSameSites = [
367
+ "Lax",
368
+ "Strict",
369
+ "None"
370
+ ];
371
+ var httpMethods = [
372
+ "GET",
373
+ "HEAD",
374
+ "PATCH",
375
+ "POST",
376
+ "PUT",
377
+ "DELETE",
378
+ "CONNECT",
379
+ "OPTIONS",
380
+ "TRACE"
381
+ ];
382
+ var encodings = [
383
+ "ascii",
384
+ "utf8",
385
+ "utf8",
386
+ "utf16le",
387
+ "ucs2",
388
+ "ucs-2",
389
+ "base64",
390
+ "latin1",
391
+ "binary",
392
+ "hex"
393
+ ];
394
+ var statusCodes = [
395
+ 100,
396
+ 101,
397
+ 102,
398
+ 103,
399
+ 200,
400
+ 201,
401
+ 202,
402
+ 203,
403
+ 204,
404
+ 205,
405
+ 206,
406
+ 207,
407
+ 208,
408
+ 226,
409
+ 300,
410
+ 301,
411
+ 302,
412
+ 303,
413
+ 304,
414
+ 305,
415
+ 307,
416
+ 308,
417
+ 400,
418
+ 401,
419
+ 402,
420
+ 403,
421
+ 404,
422
+ 405,
423
+ 406,
424
+ 407,
425
+ 408,
426
+ 409,
427
+ 410,
428
+ 411,
429
+ 412,
430
+ 413,
431
+ 414,
432
+ 415,
433
+ 416,
434
+ 417,
435
+ 418,
436
+ 420,
437
+ 421,
438
+ 422,
439
+ 423,
440
+ 424,
441
+ 425,
442
+ 426,
443
+ 428,
444
+ 429,
445
+ 431,
446
+ 444,
447
+ 450,
448
+ 451,
449
+ 497,
450
+ 498,
451
+ 499,
452
+ 500,
453
+ 501,
454
+ 502,
455
+ 503,
456
+ 504,
457
+ 506,
458
+ 507,
459
+ 508,
460
+ 509,
461
+ 510,
462
+ 511,
463
+ 521,
464
+ 522,
465
+ 523,
466
+ 525,
467
+ 530,
468
+ 599
469
+ ];
470
+ //#endregion
471
+ export { SCHEDULE_CONTEXT, SERVER_CONTEXT, SERVER_ROUTER_CONTEXT, STATES, cookiePriorities, cookieSameSites, createServer, createStaticMiddleware, createWebSocket, defineMiddleware, defineRoute, defineSchedule, deleteCookie, encodings, fetch, getState, httpMethods, invoke, isResponse, isWebSocket, parseCookiePair, sendBadRequest, sendCreated, sendForbidden, sendNoContent, sendNotFound, sendOk, sendRedirect, sendResponse, sendUnauthorized, setCookie, setState, statusCodes, useBody, useBodyAsync, useCookies, useCookiesAsync, useHeaders, useHeadersAsync, useParameters, useParametersAsync, useQuery, useQueryAsync, useRouter, useSchedule, useServer, useSetCookies, useSetCookiesAsync, useUrl, withQuery };
@@ -0,0 +1,3 @@
1
+ declare const _default: import("revojs/module").Module<"server", Record<string, never>, []>;
2
+ export default _default;
3
+ export declare const SERVER = "ssr";
@@ -0,0 +1,150 @@
1
+ import { fromNodeRequest, toNodeRequest } from "../node/index.js";
2
+ import package_default from "../package.js";
3
+ import { Cron } from "croner";
4
+ import { ServerResponse } from "http";
5
+ import { addTypes, addVirtual, defineModule } from "revojs/module";
6
+ import { isRunnableDevEnvironment } from "vite";
7
+ import { WebSocketServer } from "ws";
8
+ //#region src/module/index.ts
9
+ function isResponse(result) {
10
+ return result instanceof Response;
11
+ }
12
+ function isWebSocket(result) {
13
+ return typeof result === "object" && "open" in result && "message" in result && "close" in result && "error" in result;
14
+ }
15
+ var module_default = defineModule({
16
+ name: "server",
17
+ setup(config) {
18
+ addVirtual(config, "server", () => "import { createServer } from '@revojs/server'; export default await createServer()");
19
+ addTypes(config, "server", () => `declare module "#server" { import type { Server } from "revojs"; const server: Server; export default server; }`);
20
+ return {
21
+ sources: {
22
+ routes: {
23
+ match: [
24
+ "**/{get,head,post,put,delete,connect,options,trace,patch}.js",
25
+ "**/{get,head,post,put,delete,connect,options,trace,patch}.ts",
26
+ "**/+middleware.js",
27
+ "**/+middleware.ts",
28
+ "!*.d.ts"
29
+ ],
30
+ from: ["./routes"]
31
+ },
32
+ schedules: {
33
+ match: [
34
+ "**/*.js",
35
+ "**/*.ts",
36
+ "!*.d.ts"
37
+ ],
38
+ from: ["./schedules"]
39
+ }
40
+ },
41
+ vite: {
42
+ environments: { ["ssr"]: {
43
+ consumer: "server",
44
+ build: {
45
+ rolldownOptions: { output: {
46
+ entryFileNames: "assets/[hash].js",
47
+ chunkFileNames: "assets/[hash].js",
48
+ assetFileNames: "assets/[hash][extname]"
49
+ } },
50
+ outDir: "./dist/server"
51
+ }
52
+ } },
53
+ plugins: [{
54
+ name: package_default.name,
55
+ async configureServer(devServer) {
56
+ const target = devServer.environments["ssr"];
57
+ if (isRunnableDevEnvironment(target)) {
58
+ const { createScope, VARIABLE_CONTEXT } = await target.runner.import("revojs");
59
+ const { invoke, SCHEDULE_CONTEXT, SERVER_CONTEXT } = await target.runner.import("@revojs/server");
60
+ const server = await target.runner.import("#server").then((module) => module.default);
61
+ if (server) {
62
+ for (const schedule of server.schedules) new Cron(schedule.when, async () => {
63
+ await server.schedule(await createScope({ context: {
64
+ [VARIABLE_CONTEXT]: process.env,
65
+ [SCHEDULE_CONTEXT]: { when: schedule.when }
66
+ } }));
67
+ });
68
+ const webSocketServer = new WebSocketServer({ noServer: true });
69
+ webSocketServer.on("connection", async (webSocket) => {
70
+ const connection = {
71
+ async send(message) {
72
+ webSocket.send(message);
73
+ },
74
+ async close(code, reason) {
75
+ webSocket.close(code, reason);
76
+ }
77
+ };
78
+ webSocket.on("open", async () => {
79
+ try {
80
+ await webSocket.interneWebSocket.open(connection);
81
+ } catch (error) {
82
+ if (error instanceof Error) await webSocket.interneWebSocket.error(connection, error);
83
+ else throw error;
84
+ }
85
+ });
86
+ webSocket.on("message", async (message) => {
87
+ try {
88
+ await webSocket.interneWebSocket.message(connection, Array.isArray(message) ? Buffer.concat(message) : message);
89
+ } catch (error) {
90
+ if (error instanceof Error) await webSocket.interneWebSocket.error(connection, error);
91
+ else throw error;
92
+ }
93
+ });
94
+ webSocket.on("error", async (error) => {
95
+ await webSocket.interneWebSocket.error(connection, error);
96
+ });
97
+ webSocket.on("close", async (code, reason) => {
98
+ try {
99
+ await webSocket.interneWebSocket.close(connection, code, reason.toString());
100
+ } catch (error) {
101
+ if (error instanceof Error) await webSocket.interneWebSocket.error(connection, error);
102
+ else throw error;
103
+ }
104
+ });
105
+ });
106
+ devServer.httpServer?.on("upgrade", async (request, socket, head) => {
107
+ const protocol = request.headers["sec-websocket-protocol"];
108
+ if (protocol === "vite-hmr" || protocol === "vite-ping") return;
109
+ const scope = await createScope({ context: {
110
+ [VARIABLE_CONTEXT]: process.env,
111
+ [SERVER_CONTEXT]: {
112
+ states: {},
113
+ request: fromNodeRequest(request, new ServerResponse(request)),
114
+ response: { headers: new Headers() }
115
+ }
116
+ } });
117
+ const result = await invoke(scope, [server]);
118
+ if (isWebSocket(result)) webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
119
+ webSocket.interneWebSocket = result;
120
+ webSocketServer.emit("connection", webSocket, request);
121
+ });
122
+ else socket.destroy();
123
+ });
124
+ return () => {
125
+ devServer.middlewares.use(async (request, response, next) => {
126
+ request.url = request.originalUrl;
127
+ const scope = await createScope({ context: {
128
+ [VARIABLE_CONTEXT]: process.env,
129
+ [SERVER_CONTEXT]: {
130
+ states: {},
131
+ request: fromNodeRequest(request, response),
132
+ response: { headers: new Headers() }
133
+ }
134
+ } });
135
+ const result = await invoke(scope, [server]);
136
+ if (isResponse(result)) await toNodeRequest(result, response);
137
+ next();
138
+ });
139
+ };
140
+ }
141
+ }
142
+ }
143
+ }]
144
+ }
145
+ };
146
+ }
147
+ });
148
+ var SERVER = "ssr";
149
+ //#endregion
150
+ export { SERVER, module_default as default };
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage, ServerResponse } from "http";
2
+ export declare function fromNodeRequest(nodeReq: IncomingMessage, nodeRes: ServerResponse): Request;
3
+ export declare function toNodeRequest(res: Response, nodeRes: ServerResponse): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { once } from "events";
2
+ import { Readable } from "stream";
3
+ //#region src/node/index.ts
4
+ function createReadableStreamFromReadable(source) {
5
+ return Readable.toWeb(source);
6
+ }
7
+ function fromNodeHeaders(nodeHeaders) {
8
+ const headers = new Headers();
9
+ for (const [key, value] of Object.entries(nodeHeaders)) if (value) {
10
+ if (Array.isArray(value)) for (const v of value) headers.append(key, v);
11
+ else headers.set(key, value);
12
+ }
13
+ return headers;
14
+ }
15
+ function fromNodeRequest(nodeReq, nodeRes) {
16
+ const origin = nodeReq.headers.origin && "null" !== nodeReq.headers.origin ? nodeReq.headers.origin : `http://${nodeReq.headers.host}`;
17
+ const url = new URL(nodeReq.url ?? "", origin);
18
+ let controller = new AbortController();
19
+ const init = {
20
+ method: nodeReq.method,
21
+ headers: fromNodeHeaders(nodeReq.headers),
22
+ signal: controller.signal
23
+ };
24
+ if (nodeReq.method !== "GET" && nodeReq.method !== "HEAD") {
25
+ init.body = createReadableStreamFromReadable(nodeReq);
26
+ init.duplex = "half";
27
+ }
28
+ nodeRes.on("finish", () => {
29
+ controller = null;
30
+ });
31
+ nodeRes.on("close", () => controller?.abort());
32
+ return new Request(url.href, init);
33
+ }
34
+ async function toNodeRequest(res, nodeRes) {
35
+ nodeRes.statusCode = res.status;
36
+ nodeRes.statusMessage = res.statusText;
37
+ const cookiesStrings = [];
38
+ for (const [name, value] of res.headers) if (name === "set-cookie") {
39
+ const cookieArray = Array.isArray(value) ? value : [value ?? ""];
40
+ cookiesStrings.push(...cookieArray.flatMap((c) => c.split(/,\s*(?=[^=]+=)/)));
41
+ } else nodeRes.setHeader(name, value);
42
+ if (cookiesStrings.length) nodeRes.setHeader("set-cookie", cookiesStrings);
43
+ if (res.body) {
44
+ const readable = Readable.fromWeb(res.body);
45
+ readable.pipe(nodeRes);
46
+ await once(readable, "end");
47
+ } else nodeRes.end();
48
+ }
49
+ //#endregion
50
+ export { fromNodeRequest, toNodeRequest };
@@ -0,0 +1,53 @@
1
+ //#region package.json
2
+ var name = "@revojs/server";
3
+ var version = "0.1.58";
4
+ var repository = {
5
+ "type": "git",
6
+ "url": "git+https://github.com/tellua/revojs.git"
7
+ };
8
+ var files = ["dist"];
9
+ var type = "module";
10
+ var main = "./dist/index.js";
11
+ var module = "./dist/index.js";
12
+ var types = "./dist/index.d.ts";
13
+ var exports = {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./module": {
19
+ "types": "./dist/module/index.d.ts",
20
+ "import": "./dist/module/index.js"
21
+ }
22
+ };
23
+ var scripts = {
24
+ "build": "vite build",
25
+ "watch": "vite build --watch"
26
+ };
27
+ var dependencies = {
28
+ "@standard-schema/spec": "^1.1.0",
29
+ "croner": "^10.0.1",
30
+ "revojs": "*",
31
+ "ws": "^8.21.3"
32
+ };
33
+ var devDependencies = {
34
+ "@revojs/tsconfig": "*",
35
+ "@types/ws": "^8.18.1"
36
+ };
37
+ var package_default = {
38
+ name,
39
+ version,
40
+ license: "MIT",
41
+ repository,
42
+ files,
43
+ type,
44
+ main,
45
+ module,
46
+ types,
47
+ exports,
48
+ scripts,
49
+ dependencies,
50
+ devDependencies
51
+ };
52
+ //#endregion
53
+ export { package_default as default, dependencies, devDependencies, exports, files, main, module, name, repository, scripts, type, types, version };
@@ -0,0 +1,22 @@
1
+ declare module "#server" {
2
+ import type { Server } from "@revojs/server";
3
+
4
+ const server: Server;
5
+ export default server;
6
+ }
7
+
8
+ declare module "#routes" {
9
+ import type { Route } from "@revojs/server";
10
+ import type { Source } from "revojs";
11
+
12
+ const entries: Source<Route>;
13
+ export default entries;
14
+ }
15
+
16
+ declare module "#schedules" {
17
+ import type { Schedule } from "@revojs/server";
18
+ import type { Source } from "revojs";
19
+
20
+ const entries: Source<Schedule>;
21
+ export default entries;
22
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@revojs/server",
3
+ "version": "0.1.58",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/tellua/revojs.git"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./module": {
22
+ "types": "./dist/module/index.d.ts",
23
+ "import": "./dist/module/index.js"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "build": "vite build",
28
+ "watch": "vite build --watch"
29
+ },
30
+ "dependencies": {
31
+ "@standard-schema/spec": "^1.1.0",
32
+ "croner": "^10.0.1",
33
+ "revojs": "*",
34
+ "ws": "^8.21.3"
35
+ },
36
+ "devDependencies": {
37
+ "@revojs/tsconfig": "*",
38
+ "@types/ws": "^8.18.1"
39
+ }
40
+ }