@revojs/server 0.2.8 → 0.2.9

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.d.ts CHANGED
@@ -64,8 +64,17 @@ export declare function isResponse(result: Result): result is Response;
64
64
  export declare function isWebSocket(result: Result): result is WebSocket;
65
65
  export declare function useRouter(scope: Scope): RouterContext<Route>;
66
66
  export declare function useServer(scope: Scope): ServerContext;
67
+ export declare function createServerScope(request: Request, variables: object, states?: ServerContext["states"]): Promise<{
68
+ hooks?: {
69
+ [x: string]: ArrayLike<import("revojs").Hook> | undefined;
70
+ } | undefined;
71
+ context?: {
72
+ [x: string]: {} | undefined;
73
+ } | undefined;
74
+ } & Scope>;
67
75
  export declare function useSchedule(scope: Scope): ScheduleContext;
68
76
  export declare function useUrl(scope: Scope, base?: string): URL;
77
+ export declare function requireSameOrigin(scope: Scope): void;
69
78
  export declare function useParameters(scope: Scope): Record<string, string>;
70
79
  export declare function useParameters<T extends StandardSchemaV1>(scope: Scope, schema: T): StandardSchemaV1.InferOutput<T>;
71
80
  export declare function useParametersAsync<T extends StandardSchemaV1>(scope: Scope, schema: T): Promise<StandardSchemaV1.InferOutput<T>>;
@@ -101,16 +110,16 @@ export declare function getState<T>(scope: Scope, name: string): T;
101
110
  export declare function setState<T>(scope: Scope, name: string, value: T): void;
102
111
  export declare function fetch<T>(scope: Scope, input: string | URL, options?: RequestInit): Promise<T>;
103
112
  export declare function invoke(scope: Scope, pipeline: Array<Middleware>, index?: number): Promise<Result>;
113
+ export declare function invokeWebSocket(webSocket: WebSocket, connection: WebSocketConnection, invoke: () => Awaitable<void>): Promise<void>;
104
114
  export declare function createStaticMiddleware(resolve: (path: string | URL) => Awaitable<Blob>): {
105
115
  fetch(scope: Scope, next: (() => Result) | undefined): Promise<void | Response | WebSocket>;
106
116
  };
107
117
  export declare function createServer(): Promise<Server>;
108
- export declare let STATES: Record<string, unknown>;
109
118
  export declare const SERVER_CONTEXT: import("revojs").Descriptor<ServerContext>;
110
119
  export declare const SERVER_ROUTER_CONTEXT: import("revojs").Descriptor<RouterContext<Route>>;
111
120
  export declare const SCHEDULE_CONTEXT: import("revojs").Descriptor<ScheduleContext>;
112
121
  export declare const cookiePriorities: readonly ["Low", "Medium", "High"];
113
122
  export declare const cookieSameSites: readonly ["Lax", "Strict", "None"];
114
123
  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"];
124
+ export declare const encodings: readonly ["ascii", "utf8", "utf16le", "ucs2", "ucs-2", "base64", "latin1", "binary", "hex"];
116
125
  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 CHANGED
@@ -2,7 +2,7 @@ import assets from "#assets";
2
2
  import routes from "#routes";
3
3
  import schedules from "#schedules";
4
4
  import { isClient, isServer } from "#variables";
5
- import { Router, VARIABLE_CONTEXT, createScope, defineContext, getContext, setContext, useRoute, useVariables } from "revojs";
5
+ import { Router, STATE_CONTEXT, VARIABLE_CONTEXT, createScope, defineContext, getContext, setContext, useRoute, useVariables } from "revojs";
6
6
  import { scheduleAliases } from "revojs/shared";
7
7
  //#region src/index.ts
8
8
  function defineRoute(route) {
@@ -23,7 +23,7 @@ function isResponse(result) {
23
23
  return result instanceof Response;
24
24
  }
25
25
  function isWebSocket(result) {
26
- return typeof result === "object" && "open" in result && "message" in result && "close" in result && "error" in result;
26
+ return result !== null && typeof result === "object" && "open" in result && "message" in result && "close" in result && "error" in result;
27
27
  }
28
28
  function parseSchema(scope, schema, value) {
29
29
  const output = schema["~standard"].validate(value);
@@ -42,6 +42,16 @@ function useRouter(scope) {
42
42
  function useServer(scope) {
43
43
  return getContext(scope, SERVER_CONTEXT);
44
44
  }
45
+ function createServerScope(request, variables, states = {}) {
46
+ return createScope({ context: {
47
+ [VARIABLE_CONTEXT]: variables,
48
+ [SERVER_CONTEXT]: {
49
+ states,
50
+ request,
51
+ response: { headers: new Headers() }
52
+ }
53
+ } });
54
+ }
45
55
  function useSchedule(scope) {
46
56
  return getContext(scope, SCHEDULE_CONTEXT);
47
57
  }
@@ -50,13 +60,16 @@ function useUrl(scope, base) {
50
60
  const fallback = typeof globalThis.window === "undefined" ? "about:blank" : globalThis.location.href;
51
61
  return new URL(request?.url ?? fallback, base);
52
62
  }
63
+ function requireSameOrigin(scope) {
64
+ const headers = useHeaders(scope);
65
+ if (headers.origin && headers.origin !== useUrl(scope).origin || headers["sec-fetch-site"] === "cross-site") throw sendForbidden(scope);
66
+ }
53
67
  function useParameters(scope, schema) {
54
68
  const { parameters } = useRoute(scope);
55
69
  return schema ? parseSchema(scope, schema, parameters) : parameters;
56
70
  }
57
71
  function useParametersAsync(scope, schema) {
58
- const { parameters } = useRoute(scope);
59
- return parseSchemaAsync(scope, schema, parameters);
72
+ return parseSchemaAsync(scope, schema, useParameters(scope));
60
73
  }
61
74
  function useHeaders(scope, schema) {
62
75
  const { request } = useServer(scope);
@@ -64,8 +77,7 @@ function useHeaders(scope, schema) {
64
77
  return schema ? parseSchema(scope, schema, entries) : entries;
65
78
  }
66
79
  function useHeadersAsync(scope, schema) {
67
- const { request } = useServer(scope);
68
- return parseSchemaAsync(scope, schema, Object.fromEntries(request?.headers ?? []));
80
+ return parseSchemaAsync(scope, schema, useHeaders(scope));
69
81
  }
70
82
  function useQuery(scope, schema) {
71
83
  const { searchParams } = useUrl(scope);
@@ -73,17 +85,22 @@ function useQuery(scope, schema) {
73
85
  return schema ? parseSchema(scope, schema, entries) : entries;
74
86
  }
75
87
  function useQueryAsync(scope, schema) {
76
- const { searchParams } = useUrl(scope);
77
- return parseSchemaAsync(scope, schema, searchParams);
88
+ return parseSchemaAsync(scope, schema, useQuery(scope));
78
89
  }
79
90
  function withQuery(scope, input, query) {
80
91
  const url = new URL(input, useUrl(scope));
81
92
  for (const [name, value] of new URLSearchParams(query)) url.searchParams.set(name, value);
82
93
  return url.href;
83
94
  }
84
- async function readBody(request) {
95
+ async function readBody(scope) {
96
+ const { request } = useServer(scope);
85
97
  const contentType = request.headers.get("Content-Type");
86
- if (contentType?.includes("application/json")) return request.json();
98
+ if (contentType?.includes("application/json")) try {
99
+ return await request.json();
100
+ } catch (error) {
101
+ if (error instanceof SyntaxError) throw sendBadRequest(scope);
102
+ throw error;
103
+ }
87
104
  if (contentType?.includes("multipart/form-data") || contentType?.includes("application/x-www-form-urlencoded")) {
88
105
  const formData = await request.formData();
89
106
  return Object.fromEntries(formData.entries());
@@ -91,13 +108,11 @@ async function readBody(request) {
91
108
  return {};
92
109
  }
93
110
  async function useBody(scope, schema) {
94
- const { request } = useServer(scope);
95
- const entries = await readBody(request);
111
+ const entries = await readBody(scope);
96
112
  return schema ? parseSchema(scope, schema, entries) : entries;
97
113
  }
98
114
  async function useBodyAsync(scope, schema) {
99
- const { request } = useServer(scope);
100
- return parseSchemaAsync(scope, schema, await readBody(request));
115
+ return parseSchemaAsync(scope, schema, await readBody(scope));
101
116
  }
102
117
  function parseCookiePair(cookie) {
103
118
  const index = cookie.indexOf("=");
@@ -121,12 +136,7 @@ function useCookies(scope, schema) {
121
136
  return schema ? parseSchema(scope, schema, entries) : entries;
122
137
  }
123
138
  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
- }, {}));
139
+ return parseSchemaAsync(scope, schema, useCookies(scope));
130
140
  }
131
141
  function useSetCookies(scope, schema) {
132
142
  const { request } = useServer(scope);
@@ -138,12 +148,7 @@ function useSetCookies(scope, schema) {
138
148
  return schema ? parseSchema(scope, schema, entries) : entries;
139
149
  }
140
150
  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
- }, {}));
151
+ return parseSchemaAsync(scope, schema, useSetCookies(scope));
147
152
  }
148
153
  function setCookie(scope, name, value, options) {
149
154
  const { response } = useServer(scope);
@@ -238,43 +243,25 @@ function sendNotFound(scope, body, config) {
238
243
  response.status = 404;
239
244
  return sendResponse(scope, body, config);
240
245
  }
241
- function getState(scope, name) {
246
+ function useStates(scope) {
242
247
  if (isServer) {
243
248
  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];
249
+ return states;
251
250
  }
251
+ return Object.hasOwn(scope.context, STATE_CONTEXT) ? getContext(scope, STATE_CONTEXT) : setContext(scope, STATE_CONTEXT, {});
252
+ }
253
+ function getState(scope, name) {
254
+ return useStates(scope)[name];
252
255
  }
253
256
  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
- }
257
+ useStates(scope)[name] = value;
264
258
  }
265
259
  async function fetch(scope, input, options) {
266
260
  let response;
267
261
  if (isServer) {
268
262
  const { states, request } = useServer(scope);
269
263
  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
- } });
264
+ const next = await createServerScope(new Request(url, options), useVariables(scope), states);
278
265
  const previous = new URL(request.url);
279
266
  if (url.origin === previous.origin) {
280
267
  const result = await (await import("#server")).default.fetch(next);
@@ -308,6 +295,14 @@ async function fetch(scope, input, options) {
308
295
  async function invoke(scope, pipeline, index = 0) {
309
296
  return await pipeline.at(index)?.fetch(scope, async () => await invoke(scope, pipeline, index + 1));
310
297
  }
298
+ async function invokeWebSocket(webSocket, connection, invoke) {
299
+ try {
300
+ await invoke();
301
+ } catch (error) {
302
+ if (error instanceof Error) await webSocket.error(connection, error);
303
+ else throw error;
304
+ }
305
+ }
311
306
  function createStaticMiddleware(resolve) {
312
307
  return defineMiddleware({ async fetch(scope, next) {
313
308
  const { pathname } = useUrl(scope);
@@ -323,7 +318,9 @@ async function createServer() {
323
318
  const segments = path.split("/");
324
319
  for (const attribute of segments.pop()?.split(".") ?? []) if (httpMethods.includes(attribute.toUpperCase())) segments.unshift(attribute);
325
320
  else segments.push(attribute);
326
- router.use(segments, await resolve());
321
+ const route = await resolve();
322
+ if (segments.at(-1)?.startsWith("+")) for (const method of httpMethods) router.use([method.toLowerCase(), ...segments], route);
323
+ else router.use(segments, route);
327
324
  }
328
325
  for (const source of Object.values(schedules)) for (const resolve of Object.values(source)) {
329
326
  const schedule = await resolve();
@@ -354,7 +351,6 @@ async function createServer() {
354
351
  }
355
352
  };
356
353
  }
357
- var STATES;
358
354
  var SERVER_CONTEXT = defineContext("SERVER_CONTEXT");
359
355
  var SERVER_ROUTER_CONTEXT = defineContext("SERVER_ROUTER_CONTEXT");
360
356
  var SCHEDULE_CONTEXT = defineContext("SCHEDULE_CONTEXT");
@@ -382,7 +378,6 @@ var httpMethods = [
382
378
  var encodings = [
383
379
  "ascii",
384
380
  "utf8",
385
- "utf8",
386
381
  "utf16le",
387
382
  "ucs2",
388
383
  "ucs-2",
@@ -468,4 +463,4 @@ var statusCodes = [
468
463
  599
469
464
  ];
470
465
  //#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 };
466
+ export { SCHEDULE_CONTEXT, SERVER_CONTEXT, SERVER_ROUTER_CONTEXT, cookiePriorities, cookieSameSites, createServer, createServerScope, createStaticMiddleware, createWebSocket, defineMiddleware, defineRoute, defineSchedule, deleteCookie, encodings, fetch, getState, httpMethods, invoke, invokeWebSocket, isResponse, isWebSocket, parseCookiePair, requireSameOrigin, 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 };
@@ -1,3 +1,5 @@
1
+ import { type Config } from "revojs/module";
2
+ export declare function addRoutes(config: Config, ...directories: Array<string>): void;
1
3
  declare const _default: import("revojs/module").Module<"server", Record<string, never>, []>;
2
4
  export default _default;
3
5
  export declare const SERVER = "ssr";
@@ -6,17 +6,14 @@ import { addTypes, addVirtual, defineModule } from "revojs/module";
6
6
  import { isRunnableDevEnvironment } from "vite";
7
7
  import { WebSocketServer } from "ws";
8
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;
9
+ function addRoutes(config, ...directories) {
10
+ config.sources.routes?.from?.push(...directories);
14
11
  }
15
12
  var module_default = defineModule({
16
13
  name: "server",
17
14
  setup(config) {
18
15
  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; }`);
16
+ addTypes(config, "server", () => `declare module "#server" { import type { Server } from "@revojs/server"; const server: Server; export default server; }`);
20
17
  return {
21
18
  sources: {
22
19
  routes: {
@@ -58,7 +55,7 @@ var module_default = defineModule({
58
55
  const target = devServer.environments["ssr"];
59
56
  if (isRunnableDevEnvironment(target)) {
60
57
  const { createScope, VARIABLE_CONTEXT } = await target.runner.import("revojs");
61
- const { invoke, SCHEDULE_CONTEXT, SERVER_CONTEXT } = await target.runner.import("@revojs/server");
58
+ const { createServerScope, invoke, invokeWebSocket, isResponse, isWebSocket, SCHEDULE_CONTEXT } = await target.runner.import("@revojs/server");
62
59
  const server = await target.runner.import("#server").then((module) => module.default);
63
60
  if (server) {
64
61
  for (const schedule of server.schedules) new Cron(schedule.when, async () => {
@@ -78,44 +75,22 @@ var module_default = defineModule({
78
75
  }
79
76
  };
80
77
  webSocket.on("open", async () => {
81
- try {
82
- await webSocket.interneWebSocket.open(connection);
83
- } catch (error) {
84
- if (error instanceof Error) await webSocket.interneWebSocket.error(connection, error);
85
- else throw error;
86
- }
78
+ await invokeWebSocket(webSocket.interneWebSocket, connection, () => webSocket.interneWebSocket.open(connection));
87
79
  });
88
80
  webSocket.on("message", async (message) => {
89
- try {
90
- await webSocket.interneWebSocket.message(connection, Array.isArray(message) ? Buffer.concat(message) : message);
91
- } catch (error) {
92
- if (error instanceof Error) await webSocket.interneWebSocket.error(connection, error);
93
- else throw error;
94
- }
81
+ await invokeWebSocket(webSocket.interneWebSocket, connection, () => webSocket.interneWebSocket.message(connection, Array.isArray(message) ? Buffer.concat(message) : message));
95
82
  });
96
83
  webSocket.on("error", async (error) => {
97
84
  await webSocket.interneWebSocket.error(connection, error);
98
85
  });
99
86
  webSocket.on("close", async (code, reason) => {
100
- try {
101
- await webSocket.interneWebSocket.close(connection, code, reason.toString());
102
- } catch (error) {
103
- if (error instanceof Error) await webSocket.interneWebSocket.error(connection, error);
104
- else throw error;
105
- }
87
+ await invokeWebSocket(webSocket.interneWebSocket, connection, () => webSocket.interneWebSocket.close(connection, code, reason.toString()));
106
88
  });
107
89
  });
108
90
  devServer.httpServer?.on("upgrade", async (request, socket, head) => {
109
91
  const protocol = request.headers["sec-websocket-protocol"];
110
92
  if (protocol === "vite-hmr" || protocol === "vite-ping") return;
111
- const scope = await createScope({ context: {
112
- [VARIABLE_CONTEXT]: process.env,
113
- [SERVER_CONTEXT]: {
114
- states: {},
115
- request: fromNodeRequest(request, new ServerResponse(request)),
116
- response: { headers: new Headers() }
117
- }
118
- } });
93
+ const scope = await createServerScope(fromNodeRequest(request, new ServerResponse(request)), process.env);
119
94
  const result = await invoke(scope, [server]);
120
95
  if (isWebSocket(result)) webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
121
96
  webSocket.interneWebSocket = result;
@@ -126,14 +101,7 @@ var module_default = defineModule({
126
101
  return () => {
127
102
  devServer.middlewares.use(async (request, response, next) => {
128
103
  request.url = request.originalUrl;
129
- const scope = await createScope({ context: {
130
- [VARIABLE_CONTEXT]: process.env,
131
- [SERVER_CONTEXT]: {
132
- states: {},
133
- request: fromNodeRequest(request, response),
134
- response: { headers: new Headers() }
135
- }
136
- } });
104
+ const scope = await createServerScope(fromNodeRequest(request, response), process.env);
137
105
  const result = await invoke(scope, [server]);
138
106
  if (isResponse(result)) await toNodeRequest(result, response);
139
107
  next();
@@ -149,4 +117,4 @@ var module_default = defineModule({
149
117
  });
150
118
  var SERVER = "ssr";
151
119
  //#endregion
152
- export { SERVER, module_default as default };
120
+ export { SERVER, addRoutes, module_default as default };
package/dist/package.js CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region package.json
2
2
  var name = "@revojs/server";
3
- var version = "0.2.8";
3
+ var version = "0.2.9";
4
4
  var repository = {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/tellua/revojs.git"
@@ -22,6 +22,7 @@ var exports = {
22
22
  };
23
23
  var scripts = {
24
24
  "build": "vite build",
25
+ "typecheck": "tsc --noEmit -p tsconfig.json",
25
26
  "watch": "vite build --watch"
26
27
  };
27
28
  var dependencies = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revojs/server",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,6 +25,7 @@
25
25
  },
26
26
  "scripts": {
27
27
  "build": "vite build",
28
+ "typecheck": "tsc --noEmit -p tsconfig.json",
28
29
  "watch": "vite build --watch"
29
30
  },
30
31
  "dependencies": {