@revojs/server 0.2.65 → 0.3.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/index.d.ts CHANGED
@@ -27,6 +27,10 @@ export interface FetchOptions extends RequestInit {
27
27
  json?: unknown;
28
28
  forwardHeaders?: Array<string>;
29
29
  }
30
+ export interface ApiMiddlewareOptions {
31
+ sameOrigin?: boolean;
32
+ contentTypes?: Array<string>;
33
+ }
30
34
  export interface ServerContext {
31
35
  states: Record<string, unknown>;
32
36
  request: Request;
@@ -60,6 +64,12 @@ export interface Server extends Route {
60
64
  schedules: Array<Schedule>;
61
65
  schedule: (scope: Scope) => Promise<void>;
62
66
  }
67
+ export declare class FetchError<T = unknown> extends Error {
68
+ readonly status: number;
69
+ readonly response: Response;
70
+ readonly data: T | undefined;
71
+ constructor(response: Response, data?: T);
72
+ }
63
73
  export declare function defineRoute<T extends Route>(route: T): T;
64
74
  export declare function defineMiddleware<T extends Middleware>(middleware: T): T;
65
75
  export declare function defineSchedule<T extends Schedule>(schedule: T): T;
@@ -76,6 +86,7 @@ export declare function createServerScope(request: Request, variables: object, s
76
86
  [x: string]: {} | undefined;
77
87
  } | undefined;
78
88
  } & Scope>;
89
+ export declare function waitUntil(scope: Scope, promise: Promise<unknown>): void;
79
90
  export declare function useSchedule(scope: Scope): ScheduleContext;
80
91
  export declare function useUrl(scope: Scope, base?: string): URL;
81
92
  export declare function requireSameOrigin(scope: Scope): void;
@@ -115,6 +126,9 @@ export declare function setState<T>(scope: Scope, name: string, value: T): void;
115
126
  export declare function fetch<T>(scope: Scope, input: string | URL, options?: FetchOptions): Promise<T>;
116
127
  export declare function invoke(scope: Scope, pipeline: Array<Middleware>, index?: number): Promise<Result>;
117
128
  export declare function invokeWebSocket(webSocket: WebSocket, connection: WebSocketConnection, invoke: () => Awaitable<void>): Promise<void>;
129
+ export declare function createApiMiddleware(options?: ApiMiddlewareOptions): {
130
+ fetch(scope: Scope, next: (() => Result) | undefined): Promise<void | Response | WebSocket>;
131
+ };
118
132
  export declare function createStaticMiddleware(resolve: (path: string | URL) => Awaitable<Blob>): {
119
133
  fetch(scope: Scope, next: (() => Result) | undefined): Promise<void | Response | WebSocket>;
120
134
  };
@@ -122,6 +136,7 @@ export declare function createServer(): Promise<Server>;
122
136
  export declare const SERVER_CONTEXT: import("revojs").Descriptor<ServerContext>;
123
137
  export declare const SERVER_ROUTER_CONTEXT: import("revojs").Descriptor<RouterContext<Route>>;
124
138
  export declare const SCHEDULE_CONTEXT: import("revojs").Descriptor<ScheduleContext>;
139
+ export declare const WAIT_UNTIL_CONTEXT: import("revojs").Descriptor<(promise: Promise<unknown>) => void>;
125
140
  export declare const cookiePriorities: readonly ["Low", "Medium", "High"];
126
141
  export declare const cookieSameSites: readonly ["Lax", "Strict", "None"];
127
142
  export declare const httpMethods: readonly ["GET", "HEAD", "PATCH", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"];
package/dist/index.js CHANGED
@@ -5,6 +5,18 @@ import { isClient, isServer } from "#variables";
5
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
+ var FetchError = class extends Error {
9
+ status;
10
+ response;
11
+ data;
12
+ constructor(response, data) {
13
+ super(`Request to ${response.url || "server"} failed with ${response.status}`);
14
+ this.name = "FetchError";
15
+ this.status = response.status;
16
+ this.response = response;
17
+ this.data = data;
18
+ }
19
+ };
8
20
  function defineRoute(route) {
9
21
  return route;
10
22
  }
@@ -52,6 +64,16 @@ function createServerScope(request, variables, states = {}) {
52
64
  }
53
65
  } });
54
66
  }
67
+ function waitUntil(scope, promise) {
68
+ const tracked = (async () => {
69
+ try {
70
+ await promise;
71
+ } catch (error) {
72
+ console.error(error);
73
+ }
74
+ })();
75
+ if (Object.hasOwn(scope.context, WAIT_UNTIL_CONTEXT)) getContext(scope, WAIT_UNTIL_CONTEXT)(tracked);
76
+ }
55
77
  function useSchedule(scope) {
56
78
  return getContext(scope, SCHEDULE_CONTEXT);
57
79
  }
@@ -174,10 +196,8 @@ function sendResponse(scope, body, config) {
174
196
  const { response } = useServer(scope);
175
197
  if (body !== null && body !== void 0 && !response.headers.has("Content-Type")) {
176
198
  if (typeof body === "object" && !ArrayBuffer.isView(body)) {
177
- if (body instanceof FormData) response.headers.set("Content-Type", "multipart/form-data");
178
- else if (body instanceof URLSearchParams) response.headers.set("Content-Type", "application/x-www-form-urlencoded");
179
- else if (body instanceof ReadableStream) response.headers.set("Content-Type", "application/octet-stream");
180
- else if (!(body instanceof Blob)) {
199
+ if (body instanceof ReadableStream) response.headers.set("Content-Type", "application/octet-stream");
200
+ else if (!(body instanceof Blob || body instanceof FormData || body instanceof URLSearchParams)) {
181
201
  response.headers.set("Content-Type", "application/json");
182
202
  body = JSON.stringify(body);
183
203
  }
@@ -256,6 +276,15 @@ function getState(scope, name) {
256
276
  function setState(scope, name, value) {
257
277
  useStates(scope)[name] = value;
258
278
  }
279
+ async function readResponse(response) {
280
+ const contentType = response.headers.get("Content-Type")?.split(";").shift()?.trim().toLowerCase() ?? "";
281
+ if (response.status === 204 || response.status === 205 || response.body === null) return;
282
+ if (contentType === "application/json" || contentType.endsWith("+json")) return response.json();
283
+ if (contentType === "" || contentType === "text/event-stream") return response;
284
+ if (contentType.startsWith("text/") || contentType === "application/javascript" || contentType === "application/xml" || contentType.endsWith("+xml")) return response.text();
285
+ if (contentType === "multipart/form-data") return response.formData();
286
+ return response.blob();
287
+ }
259
288
  async function fetch(scope, input, options = {}) {
260
289
  const { json, forwardHeaders, ...init } = options;
261
290
  const headers = new Headers(init.headers);
@@ -276,33 +305,23 @@ async function fetch(scope, input, options = {}) {
276
305
  const value = request.headers.get(name);
277
306
  if (value !== null && !headers.has(name)) headers.set(name, value);
278
307
  }
279
- const result = await (await import("#server")).default.fetch(await createServerScope(new Request(url, init), useVariables(scope), states));
308
+ const child = await createServerScope(new Request(url, init), useVariables(scope), states);
309
+ if (Object.hasOwn(scope.context, WAIT_UNTIL_CONTEXT)) setContext(child, WAIT_UNTIL_CONTEXT, getContext(scope, WAIT_UNTIL_CONTEXT));
310
+ const result = await (await import("#server")).default.fetch(child);
280
311
  if (isResponse(result)) response = result;
281
312
  }
282
313
  }
283
314
  response ??= await globalThis.fetch(input, init);
284
- if (response.ok === false) throw response;
285
- switch (response.headers.get("Content-Type")?.split(";").shift() ?? "") {
286
- case "application/json": return response.json();
287
- case "text/plain":
288
- case "text/html":
289
- case "text/css":
290
- case "text/xml":
291
- case "application/xml":
292
- case "application/javascript": return response.text();
293
- case "application/octet-stream":
294
- case "application/pdf":
295
- case "application/zip":
296
- case "image/png":
297
- case "image/jpeg":
298
- case "image/gif":
299
- case "image/webp":
300
- case "image/svg+xml":
301
- case "audio/mpeg":
302
- case "video/mp4": return response.blob();
303
- case "multipart/form-data": return response.formData();
304
- default: return response;
315
+ if (!response.ok) {
316
+ let data;
317
+ try {
318
+ data = await readResponse(response.clone());
319
+ } catch {
320
+ data = void 0;
321
+ }
322
+ throw new FetchError(response, data);
305
323
  }
324
+ return await readResponse(response);
306
325
  }
307
326
  async function invoke(scope, pipeline, index = 0) {
308
327
  return await pipeline.at(index)?.fetch(scope, async () => await invoke(scope, pipeline, index + 1));
@@ -311,10 +330,31 @@ async function invokeWebSocket(webSocket, connection, invoke) {
311
330
  try {
312
331
  await invoke();
313
332
  } catch (error) {
314
- if (error instanceof Error) await webSocket.error(connection, error);
315
- else throw error;
333
+ let cause = new Error(String(error), { cause: error });
334
+ if (error instanceof Error) cause = error;
335
+ try {
336
+ await webSocket.error(connection, cause);
337
+ } catch (failure) {
338
+ console.error(failure);
339
+ }
316
340
  }
317
341
  }
342
+ function createApiMiddleware(options = {}) {
343
+ return defineMiddleware({ async fetch(scope, next) {
344
+ const { request, response } = useServer(scope);
345
+ response.headers.set("Cache-Control", "no-store");
346
+ if (![
347
+ "GET",
348
+ "HEAD",
349
+ "OPTIONS"
350
+ ].includes(request.method)) {
351
+ if (options.sameOrigin !== false) requireSameOrigin(scope);
352
+ const contentType = request.headers.get("Content-Type")?.split(";").shift()?.trim().toLowerCase();
353
+ if (options.contentTypes && request.body !== null && (contentType === void 0 || !options.contentTypes.includes(contentType))) return sendResponse(scope, `Content-Type must be ${options.contentTypes.join(" or ")}`, { status: 415 });
354
+ }
355
+ return next?.();
356
+ } });
357
+ }
318
358
  function createStaticMiddleware(resolve) {
319
359
  return defineMiddleware({ async fetch(scope, next) {
320
360
  const { pathname } = useUrl(scope);
@@ -345,7 +385,18 @@ async function createServer() {
345
385
  const segments = path.split("/");
346
386
  for (const attribute of segments.pop()?.split(".") ?? []) if (httpMethods.includes(attribute.toUpperCase())) segments.unshift(attribute);
347
387
  else segments.push(attribute);
348
- const route = await resolve();
388
+ let loading;
389
+ const route = { async fetch(scope, next) {
390
+ loading ??= resolve();
391
+ let loaded;
392
+ try {
393
+ loaded = await loading;
394
+ } catch (error) {
395
+ loading = void 0;
396
+ throw error;
397
+ }
398
+ return await loaded?.fetch(scope, next);
399
+ } };
349
400
  if (segments.at(-1)?.startsWith("+")) for (const method of httpMethods) router.use([method.toLowerCase(), ...segments], route);
350
401
  else router.use(segments, route);
351
402
  }
@@ -371,16 +422,24 @@ async function createServer() {
371
422
  async schedule(scope) {
372
423
  const { when } = useSchedule(scope);
373
424
  const expression = scheduleAliases[when.trim().toLowerCase()] ?? when.trim();
425
+ const errors = new Array();
374
426
  for (const schedule of scheduled) {
375
427
  if ((scheduleAliases[schedule.when.trim().toLowerCase()] ?? schedule.when.trim()) !== expression) continue;
376
- await schedule.run(scope);
428
+ try {
429
+ await schedule.run(scope);
430
+ } catch (error) {
431
+ console.error(error);
432
+ errors.push(error);
433
+ }
377
434
  }
435
+ if (errors.length > 0) throw new AggregateError(errors, `${errors.length} schedules failed for "${when}"`);
378
436
  }
379
437
  };
380
438
  }
381
439
  var SERVER_CONTEXT = defineContext("SERVER_CONTEXT");
382
440
  var SERVER_ROUTER_CONTEXT = defineContext("SERVER_ROUTER_CONTEXT");
383
441
  var SCHEDULE_CONTEXT = defineContext("SCHEDULE_CONTEXT");
442
+ var WAIT_UNTIL_CONTEXT = defineContext("WAIT_UNTIL_CONTEXT");
384
443
  var cookiePriorities = [
385
444
  "Low",
386
445
  "Medium",
@@ -490,4 +549,4 @@ var statusCodes = [
490
549
  599
491
550
  ];
492
551
  //#endregion
493
- 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 };
552
+ export { FetchError, SCHEDULE_CONTEXT, SERVER_CONTEXT, SERVER_ROUTER_CONTEXT, WAIT_UNTIL_CONTEXT, cookiePriorities, cookieSameSites, createApiMiddleware, 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, waitUntil, withQuery };
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.65";
3
+ var version = "0.3.0";
4
4
  var repository = {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/tellua/revojs.git"
@@ -28,11 +28,11 @@ var scripts = {
28
28
  var dependencies = {
29
29
  "@standard-schema/spec": "^1.1.0",
30
30
  "croner": "^10.0.1",
31
- "revojs": "*",
31
+ "revojs": "0.3.0",
32
32
  "ws": "^8.22.0"
33
33
  };
34
34
  var devDependencies = {
35
- "@revojs/tsconfig": "*",
35
+ "@revojs/tsconfig": "0.3.0",
36
36
  "@types/ws": "^8.18.2"
37
37
  };
38
38
  var package_default = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revojs/server",
3
- "version": "0.2.65",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,11 +31,11 @@
31
31
  "dependencies": {
32
32
  "@standard-schema/spec": "^1.1.0",
33
33
  "croner": "^10.0.1",
34
- "revojs": "*",
34
+ "revojs": "0.3.0",
35
35
  "ws": "^8.22.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@revojs/tsconfig": "*",
38
+ "@revojs/tsconfig": "0.3.0",
39
39
  "@types/ws": "^8.18.2"
40
40
  }
41
41
  }