@hyperspan/framework 0.5.5 → 1.0.0-alpha.1
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/package.json +34 -26
- package/src/callsites.ts +17 -0
- package/src/client/css.ts +2 -0
- package/src/client/js.ts +82 -0
- package/src/layout.ts +31 -0
- package/src/middleware.ts +53 -14
- package/src/plugins.ts +64 -58
- package/src/server.test.ts +88 -0
- package/src/server.ts +294 -427
- package/src/types.ts +110 -0
- package/src/utils.ts +5 -0
- package/build.ts +0 -16
- package/dist/assets.js +0 -120
- package/dist/chunk-atw8cdg1.js +0 -19
- package/dist/middleware.js +0 -179
- package/dist/server.js +0 -2266
- package/src/actions.test.ts +0 -106
- package/src/actions.ts +0 -256
- package/src/assets.ts +0 -176
package/src/types.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperspan Types
|
|
3
|
+
*/
|
|
4
|
+
export namespace Hyperspan {
|
|
5
|
+
export interface Server {
|
|
6
|
+
_config: Hyperspan.Config;
|
|
7
|
+
_routes: Array<Hyperspan.Route>;
|
|
8
|
+
_middleware: Array<Hyperspan.MiddlewareFunction>;
|
|
9
|
+
use: (middleware: Hyperspan.MiddlewareFunction) => Hyperspan.Server;
|
|
10
|
+
get: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
11
|
+
post: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
12
|
+
put: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
13
|
+
patch: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
14
|
+
delete: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
15
|
+
options: (path: string, handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type Plugin = (config: Hyperspan.Config) => Promise<void> | void;
|
|
19
|
+
|
|
20
|
+
export type Config = {
|
|
21
|
+
appDir: string;
|
|
22
|
+
publicDir: string;
|
|
23
|
+
plugins: Array<Hyperspan.Plugin>; // Loaders for client islands
|
|
24
|
+
// For customizing the routes and adding your own...
|
|
25
|
+
beforeRoutesAdded?: (server: Hyperspan.Server) => void;
|
|
26
|
+
afterRoutesAdded?: (server: Hyperspan.Server) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export interface Context {
|
|
30
|
+
vars: Record<string, any>;
|
|
31
|
+
route: {
|
|
32
|
+
path: string;
|
|
33
|
+
params: Record<string, string>;
|
|
34
|
+
cssImports?: string[];
|
|
35
|
+
}
|
|
36
|
+
req: {
|
|
37
|
+
url: URL;
|
|
38
|
+
raw: Request;
|
|
39
|
+
method: string; // Always uppercase
|
|
40
|
+
headers: Headers; // Case-insensitive
|
|
41
|
+
query: URLSearchParams;
|
|
42
|
+
body: any;
|
|
43
|
+
};
|
|
44
|
+
res: {
|
|
45
|
+
headers: Headers; // Headers to merge with final outgoing response
|
|
46
|
+
html: (html: string, options?: { status?: number; headers?: Record<string, string> }) => Response
|
|
47
|
+
json: (json: any, options?: { status?: number; headers?: Record<string, string> }) => Response;
|
|
48
|
+
text: (text: string, options?: { status?: number; headers?: Record<string, string> }) => Response;
|
|
49
|
+
redirect: (url: string, options?: { status?: number; headers?: Record<string, string> }) => Response;
|
|
50
|
+
error: (error: Error, options?: { status?: number; headers?: Record<string, string> }) => Response;
|
|
51
|
+
notFound: (options?: { status?: number; headers?: Record<string, string> }) => Response;
|
|
52
|
+
raw: Response;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type ClientIslandOptions = {
|
|
57
|
+
ssr?: boolean;
|
|
58
|
+
loading?: 'lazy' | undefined;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type RouteConfig = {
|
|
62
|
+
name?: string;
|
|
63
|
+
path?: string;
|
|
64
|
+
cssImports?: string[];
|
|
65
|
+
};
|
|
66
|
+
export type RouteHandler = (context: Hyperspan.Context) => unknown;
|
|
67
|
+
export type RouteHandlerOptions = {
|
|
68
|
+
middleware?: Hyperspan.MiddlewareFunction[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// TypeScript inference for typed route params
|
|
72
|
+
// Source - https://stackoverflow.com/a/78170543
|
|
73
|
+
// Posted by jcalz
|
|
74
|
+
// Retrieved 2025-11-12, License - CC BY-SA 4.0
|
|
75
|
+
export type RouteParamsParser<T extends string, A = unknown> =
|
|
76
|
+
T extends `${string}:${infer F}/${infer R}` ? RouteParamsParser<R, A & Record<F, string>> :
|
|
77
|
+
(A & (T extends `${string}:${infer F}` ? Record<F, string> : unknown)) extends
|
|
78
|
+
infer U ? { [K in keyof U]: U[K] } : never
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Next function type for middleware
|
|
83
|
+
*/
|
|
84
|
+
export type NextFunction = () => Promise<Response>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Middleware function signature
|
|
88
|
+
* Accepts context and next function, returns a Response
|
|
89
|
+
*/
|
|
90
|
+
export type MiddlewareFunction = (
|
|
91
|
+
context: Hyperspan.Context,
|
|
92
|
+
next: Hyperspan.NextFunction
|
|
93
|
+
) => Promise<Response> | Response;
|
|
94
|
+
|
|
95
|
+
export interface Route {
|
|
96
|
+
_kind: 'hsRoute';
|
|
97
|
+
_name: string | undefined;
|
|
98
|
+
_config: Hyperspan.RouteConfig;
|
|
99
|
+
_path(): string;
|
|
100
|
+
_methods(): string[];
|
|
101
|
+
get: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
102
|
+
post: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
103
|
+
put: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
104
|
+
patch: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
105
|
+
delete: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
106
|
+
options: (handler: Hyperspan.RouteHandler, handlerOptions?: Hyperspan.RouteHandlerOptions) => Hyperspan.Route;
|
|
107
|
+
middleware: (middleware: Array<Hyperspan.MiddlewareFunction>) => Hyperspan.Route;
|
|
108
|
+
fetch: (request: Request) => Promise<Response>;
|
|
109
|
+
};
|
|
110
|
+
}
|
package/src/utils.ts
ADDED
package/build.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { build } from 'bun';
|
|
2
|
-
|
|
3
|
-
const entrypoints = ['./src/server.ts', './src/assets.ts', './src/middleware.ts'];
|
|
4
|
-
const external = ['@hyperspan/html'];
|
|
5
|
-
const outdir = './dist';
|
|
6
|
-
const target = 'node';
|
|
7
|
-
const splitting = true;
|
|
8
|
-
|
|
9
|
-
// Build JS
|
|
10
|
-
await build({
|
|
11
|
-
entrypoints,
|
|
12
|
-
external,
|
|
13
|
-
outdir,
|
|
14
|
-
target,
|
|
15
|
-
splitting,
|
|
16
|
-
});
|
package/dist/assets.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import"./chunk-atw8cdg1.js";
|
|
2
|
-
|
|
3
|
-
// src/assets.ts
|
|
4
|
-
import { html } from "@hyperspan/html";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
|
-
import { readdir } from "node:fs/promises";
|
|
7
|
-
import { resolve } from "node:path";
|
|
8
|
-
var IS_PROD = false;
|
|
9
|
-
var PWD = import.meta.dir;
|
|
10
|
-
var CLIENTJS_PUBLIC_PATH = "/_hs/js";
|
|
11
|
-
var ISLAND_PUBLIC_PATH = "/_hs/js/islands";
|
|
12
|
-
var clientImportMap = new Map;
|
|
13
|
-
var clientJSFiles = new Map;
|
|
14
|
-
async function buildClientJS() {
|
|
15
|
-
const sourceFile = resolve(PWD, "../", "./src/clientjs/hyperspan-client.ts");
|
|
16
|
-
const output = await Bun.build({
|
|
17
|
-
entrypoints: [sourceFile],
|
|
18
|
-
outdir: `./public/${CLIENTJS_PUBLIC_PATH}`,
|
|
19
|
-
naming: IS_PROD ? "[dir]/[name]-[hash].[ext]" : undefined,
|
|
20
|
-
minify: IS_PROD
|
|
21
|
-
});
|
|
22
|
-
const jsFile = output.outputs[0].path.split("/").reverse()[0];
|
|
23
|
-
clientJSFiles.set("_hs", { src: `${CLIENTJS_PUBLIC_PATH}/${jsFile}` });
|
|
24
|
-
}
|
|
25
|
-
function renderClientJS(module, loadScript) {
|
|
26
|
-
if (!module.__CLIENT_JS) {
|
|
27
|
-
throw new Error(`[Hyperspan] Client JS was not loaded by Hyperspan! Ensure the filename ends with .client.ts to use this render method.`);
|
|
28
|
-
}
|
|
29
|
-
return html.raw(module.__CLIENT_JS.renderScriptTag({
|
|
30
|
-
loadScript: loadScript ? typeof loadScript === "string" ? loadScript : functionToString(loadScript) : undefined
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
function functionToString(fn) {
|
|
34
|
-
let str = fn.toString().trim();
|
|
35
|
-
if (!str.includes("function ")) {
|
|
36
|
-
if (str.includes("async ")) {
|
|
37
|
-
str = "async function " + str.replace("async ", "");
|
|
38
|
-
} else {
|
|
39
|
-
str = "function " + str;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const lines = str.split(`
|
|
43
|
-
`);
|
|
44
|
-
const firstLine = lines[0];
|
|
45
|
-
const lastLine = lines[lines.length - 1];
|
|
46
|
-
if (!lastLine?.includes("}")) {
|
|
47
|
-
return str.replace("=> ", "{ return ") + "; }";
|
|
48
|
-
}
|
|
49
|
-
if (firstLine.includes("=>")) {
|
|
50
|
-
return str.replace("=> ", "");
|
|
51
|
-
}
|
|
52
|
-
return str;
|
|
53
|
-
}
|
|
54
|
-
var clientCSSFiles = new Map;
|
|
55
|
-
async function buildClientCSS() {
|
|
56
|
-
if (clientCSSFiles.has("_hs")) {
|
|
57
|
-
return clientCSSFiles.get("_hs");
|
|
58
|
-
}
|
|
59
|
-
const cssDir = "./public/_hs/css/";
|
|
60
|
-
const cssFiles = await readdir(cssDir);
|
|
61
|
-
let foundCSSFile = "";
|
|
62
|
-
for (const file of cssFiles) {
|
|
63
|
-
if (!file.endsWith(".css")) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
foundCSSFile = file.replace(cssDir, "");
|
|
67
|
-
clientCSSFiles.set("_hs", foundCSSFile);
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
if (!foundCSSFile) {
|
|
71
|
-
console.log(`Unable to build CSS files from ${cssDir}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function hyperspanStyleTags() {
|
|
75
|
-
const cssFiles = Array.from(clientCSSFiles.entries());
|
|
76
|
-
return html`${cssFiles.map(([_, file]) => html`<link rel="stylesheet" href="/_hs/css/${file}" />`)}`;
|
|
77
|
-
}
|
|
78
|
-
function hyperspanScriptTags() {
|
|
79
|
-
const jsFiles = Array.from(clientJSFiles.entries());
|
|
80
|
-
return html`
|
|
81
|
-
<script type="importmap">
|
|
82
|
-
{"imports": ${Object.fromEntries(clientImportMap)}}
|
|
83
|
-
</script>
|
|
84
|
-
${jsFiles.map(([key, file]) => html`<script
|
|
85
|
-
id="js-${key}"
|
|
86
|
-
type="${file.type || "text/javascript"}"
|
|
87
|
-
src="${file.src}"
|
|
88
|
-
></script>`)}
|
|
89
|
-
`;
|
|
90
|
-
}
|
|
91
|
-
function assetHash(content) {
|
|
92
|
-
return createHash("md5").update(content).digest("hex");
|
|
93
|
-
}
|
|
94
|
-
var ISLAND_DEFAULTS = () => ({
|
|
95
|
-
ssr: true,
|
|
96
|
-
loading: undefined
|
|
97
|
-
});
|
|
98
|
-
function renderIsland(Component, props, options = ISLAND_DEFAULTS()) {
|
|
99
|
-
if (Component.__HS_ISLAND?.render) {
|
|
100
|
-
return html.raw(Component.__HS_ISLAND.render(props, options));
|
|
101
|
-
}
|
|
102
|
-
throw new Error(`Module ${Component.name} was not loaded with an island plugin! Did you forget to install an island plugin and add it to the createServer() 'islandPlugins' config?`);
|
|
103
|
-
}
|
|
104
|
-
export {
|
|
105
|
-
renderIsland,
|
|
106
|
-
renderClientJS,
|
|
107
|
-
hyperspanStyleTags,
|
|
108
|
-
hyperspanScriptTags,
|
|
109
|
-
functionToString,
|
|
110
|
-
clientJSFiles,
|
|
111
|
-
clientImportMap,
|
|
112
|
-
clientCSSFiles,
|
|
113
|
-
buildClientJS,
|
|
114
|
-
buildClientCSS,
|
|
115
|
-
assetHash,
|
|
116
|
-
ISLAND_PUBLIC_PATH,
|
|
117
|
-
ISLAND_DEFAULTS,
|
|
118
|
-
CLIENTJS_PUBLIC_PATH
|
|
119
|
-
};
|
|
120
|
-
|
package/dist/chunk-atw8cdg1.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
7
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
8
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
9
|
-
for (let key of __getOwnPropNames(mod))
|
|
10
|
-
if (!__hasOwnProp.call(to, key))
|
|
11
|
-
__defProp(to, key, {
|
|
12
|
-
get: () => mod[key],
|
|
13
|
-
enumerable: true
|
|
14
|
-
});
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
18
|
-
|
|
19
|
-
export { __toESM, __commonJS };
|
package/dist/middleware.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__commonJS,
|
|
3
|
-
__toESM
|
|
4
|
-
} from "./chunk-atw8cdg1.js";
|
|
5
|
-
|
|
6
|
-
// node_modules/timestring/index.js
|
|
7
|
-
var require_timestring = __commonJS((exports, module) => {
|
|
8
|
-
module.exports = parseTimestring;
|
|
9
|
-
var DEFAULT_OPTS = {
|
|
10
|
-
hoursPerDay: 24,
|
|
11
|
-
daysPerWeek: 7,
|
|
12
|
-
weeksPerMonth: 4,
|
|
13
|
-
monthsPerYear: 12,
|
|
14
|
-
daysPerYear: 365.25
|
|
15
|
-
};
|
|
16
|
-
var UNIT_MAP = {
|
|
17
|
-
ms: ["ms", "milli", "millisecond", "milliseconds"],
|
|
18
|
-
s: ["s", "sec", "secs", "second", "seconds"],
|
|
19
|
-
m: ["m", "min", "mins", "minute", "minutes"],
|
|
20
|
-
h: ["h", "hr", "hrs", "hour", "hours"],
|
|
21
|
-
d: ["d", "day", "days"],
|
|
22
|
-
w: ["w", "week", "weeks"],
|
|
23
|
-
mth: ["mon", "mth", "mths", "month", "months"],
|
|
24
|
-
y: ["y", "yr", "yrs", "year", "years"]
|
|
25
|
-
};
|
|
26
|
-
function parseTimestring(value, returnUnit, opts) {
|
|
27
|
-
opts = Object.assign({}, DEFAULT_OPTS, opts || {});
|
|
28
|
-
if (typeof value === "number" || value.match(/^[-+]?[0-9.]+$/g)) {
|
|
29
|
-
value = parseInt(value) + "ms";
|
|
30
|
-
}
|
|
31
|
-
let totalSeconds = 0;
|
|
32
|
-
const unitValues = getUnitValues(opts);
|
|
33
|
-
const groups = value.toLowerCase().replace(/[^.\w+-]+/g, "").match(/[-+]?[0-9.]+[a-z]+/g);
|
|
34
|
-
if (groups === null) {
|
|
35
|
-
throw new Error(`The value [${value}] could not be parsed by timestring`);
|
|
36
|
-
}
|
|
37
|
-
groups.forEach((group) => {
|
|
38
|
-
const value2 = group.match(/[0-9.]+/g)[0];
|
|
39
|
-
const unit = group.match(/[a-z]+/g)[0];
|
|
40
|
-
totalSeconds += getSeconds(value2, unit, unitValues);
|
|
41
|
-
});
|
|
42
|
-
if (returnUnit) {
|
|
43
|
-
return convert(totalSeconds, returnUnit, unitValues);
|
|
44
|
-
}
|
|
45
|
-
return totalSeconds;
|
|
46
|
-
}
|
|
47
|
-
function getUnitValues(opts) {
|
|
48
|
-
const unitValues = {
|
|
49
|
-
ms: 0.001,
|
|
50
|
-
s: 1,
|
|
51
|
-
m: 60,
|
|
52
|
-
h: 3600
|
|
53
|
-
};
|
|
54
|
-
unitValues.d = opts.hoursPerDay * unitValues.h;
|
|
55
|
-
unitValues.w = opts.daysPerWeek * unitValues.d;
|
|
56
|
-
unitValues.mth = opts.daysPerYear / opts.monthsPerYear * unitValues.d;
|
|
57
|
-
unitValues.y = opts.daysPerYear * unitValues.d;
|
|
58
|
-
return unitValues;
|
|
59
|
-
}
|
|
60
|
-
function getUnitKey(unit) {
|
|
61
|
-
for (const key of Object.keys(UNIT_MAP)) {
|
|
62
|
-
if (UNIT_MAP[key].indexOf(unit) > -1) {
|
|
63
|
-
return key;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
throw new Error(`The unit [${unit}] is not supported by timestring`);
|
|
67
|
-
}
|
|
68
|
-
function getSeconds(value, unit, unitValues) {
|
|
69
|
-
return value * unitValues[getUnitKey(unit)];
|
|
70
|
-
}
|
|
71
|
-
function convert(value, unit, unitValues) {
|
|
72
|
-
return value / unitValues[getUnitKey(unit)];
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// node_modules/hono/dist/middleware/etag/digest.js
|
|
77
|
-
var mergeBuffers = (buffer1, buffer2) => {
|
|
78
|
-
if (!buffer1) {
|
|
79
|
-
return buffer2;
|
|
80
|
-
}
|
|
81
|
-
const merged = new Uint8Array(new ArrayBuffer(buffer1.byteLength + buffer2.byteLength));
|
|
82
|
-
merged.set(new Uint8Array(buffer1), 0);
|
|
83
|
-
merged.set(buffer2, buffer1.byteLength);
|
|
84
|
-
return merged;
|
|
85
|
-
};
|
|
86
|
-
var generateDigest = async (stream, generator) => {
|
|
87
|
-
if (!stream) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
let result = undefined;
|
|
91
|
-
const reader = stream.getReader();
|
|
92
|
-
for (;; ) {
|
|
93
|
-
const { value, done } = await reader.read();
|
|
94
|
-
if (done) {
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
result = await generator(mergeBuffers(result, value));
|
|
98
|
-
}
|
|
99
|
-
if (!result) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
return Array.prototype.map.call(new Uint8Array(result), (x) => x.toString(16).padStart(2, "0")).join("");
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// node_modules/hono/dist/middleware/etag/index.js
|
|
106
|
-
var RETAINED_304_HEADERS = [
|
|
107
|
-
"cache-control",
|
|
108
|
-
"content-location",
|
|
109
|
-
"date",
|
|
110
|
-
"etag",
|
|
111
|
-
"expires",
|
|
112
|
-
"vary"
|
|
113
|
-
];
|
|
114
|
-
var stripWeak = (tag) => tag.replace(/^W\//, "");
|
|
115
|
-
function etagMatches(etag2, ifNoneMatch) {
|
|
116
|
-
return ifNoneMatch != null && ifNoneMatch.split(/,\s*/).some((t) => stripWeak(t) === stripWeak(etag2));
|
|
117
|
-
}
|
|
118
|
-
function initializeGenerator(generator) {
|
|
119
|
-
if (!generator) {
|
|
120
|
-
if (crypto && crypto.subtle) {
|
|
121
|
-
generator = (body) => crypto.subtle.digest({
|
|
122
|
-
name: "SHA-1"
|
|
123
|
-
}, body);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return generator;
|
|
127
|
-
}
|
|
128
|
-
var etag = (options) => {
|
|
129
|
-
const retainedHeaders = options?.retainedHeaders ?? RETAINED_304_HEADERS;
|
|
130
|
-
const weak = options?.weak ?? false;
|
|
131
|
-
const generator = initializeGenerator(options?.generateDigest);
|
|
132
|
-
return async function etag2(c, next) {
|
|
133
|
-
const ifNoneMatch = c.req.header("If-None-Match") ?? null;
|
|
134
|
-
await next();
|
|
135
|
-
const res = c.res;
|
|
136
|
-
let etag3 = res.headers.get("ETag");
|
|
137
|
-
if (!etag3) {
|
|
138
|
-
if (!generator) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
const hash = await generateDigest(res.clone().body, generator);
|
|
142
|
-
if (hash === null) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
etag3 = weak ? `W/"${hash}"` : `"${hash}"`;
|
|
146
|
-
}
|
|
147
|
-
if (etagMatches(etag3, ifNoneMatch)) {
|
|
148
|
-
c.res = new Response(null, {
|
|
149
|
-
status: 304,
|
|
150
|
-
statusText: "Not Modified",
|
|
151
|
-
headers: {
|
|
152
|
-
ETag: etag3
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
c.res.headers.forEach((_, key) => {
|
|
156
|
-
if (retainedHeaders.indexOf(key.toLowerCase()) === -1) {
|
|
157
|
-
c.res.headers.delete(key);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
} else {
|
|
161
|
-
c.res.headers.set("ETag", etag3);
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// src/middleware.ts
|
|
167
|
-
var import_timestring = __toESM(require_timestring(), 1);
|
|
168
|
-
function cacheTime(timeStrOrSeconds) {
|
|
169
|
-
return (c, next) => etag()(c, () => {
|
|
170
|
-
if (c.req.method.toUpperCase() === "GET") {
|
|
171
|
-
const timeInSeconds = typeof timeStrOrSeconds === "number" ? timeStrOrSeconds : import_timestring.default(timeStrOrSeconds);
|
|
172
|
-
c.header("Cache-Control", `public, max-age=${timeInSeconds}`);
|
|
173
|
-
}
|
|
174
|
-
return next();
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
export {
|
|
178
|
-
cacheTime
|
|
179
|
-
};
|