@hyperspan/framework 0.3.1 → 0.3.3
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/build.ts +1 -1
- package/dist/assets.js +5 -8
- package/dist/chunk-atw8cdg1.js +19 -0
- package/dist/middleware.js +178 -0
- package/dist/server.js +33 -25
- package/package.json +12 -6
- package/src/actions.test.ts +5 -5
- package/src/actions.ts +3 -3
- package/src/assets.ts +9 -13
- package/src/clientjs/hyperspan-client.ts +36 -1
- package/src/clientjs/{idiomorph.esm.js → idiomorph.ts} +34 -34
- package/src/middleware.ts +19 -0
- package/src/server.ts +51 -27
- package/src/clientjs/md5.js +0 -176
- package/src/clientjs/preact.ts +0 -3
package/build.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { build } from 'bun';
|
|
2
2
|
|
|
3
|
-
const entrypoints = ['./src/server.ts', './src/assets.ts'];
|
|
3
|
+
const entrypoints = ['./src/server.ts', './src/assets.ts', './src/middleware.ts'];
|
|
4
4
|
const external = ['@hyperspan/html'];
|
|
5
5
|
const outdir = './dist';
|
|
6
6
|
const target = 'node';
|
package/dist/assets.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import"./chunk-atw8cdg1.js";
|
|
2
|
+
|
|
1
3
|
// src/assets.ts
|
|
2
4
|
import { html } from "@hyperspan/html";
|
|
3
5
|
import { createHash } from "node:crypto";
|
|
@@ -60,17 +62,12 @@ function assetHash(content) {
|
|
|
60
62
|
}
|
|
61
63
|
var ISLAND_PUBLIC_PATH = "/_hs/js/islands";
|
|
62
64
|
var ISLAND_DEFAULTS = () => ({
|
|
63
|
-
ssr: true
|
|
65
|
+
ssr: true,
|
|
66
|
+
loading: undefined
|
|
64
67
|
});
|
|
65
68
|
function renderIsland(Component, props, options = ISLAND_DEFAULTS()) {
|
|
66
69
|
if (Component.__HS_ISLAND?.render) {
|
|
67
|
-
return Component.__HS_ISLAND.render(props, options);
|
|
68
|
-
}
|
|
69
|
-
if (Component.__HS_ISLAND?.ssr && options.ssr) {
|
|
70
|
-
return Component.__HS_ISLAND.ssr(props);
|
|
71
|
-
}
|
|
72
|
-
if (Component.__HS_ISLAND?.clientOnly) {
|
|
73
|
-
return Component.__HS_ISLAND.clientOnly(props);
|
|
70
|
+
return html.raw(Component.__HS_ISLAND.render(props, options));
|
|
74
71
|
}
|
|
75
72
|
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?`);
|
|
76
73
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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 };
|
|
@@ -0,0 +1,178 @@
|
|
|
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(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
|
+
function etagMatches(etag2, ifNoneMatch) {
|
|
115
|
+
return ifNoneMatch != null && ifNoneMatch.split(/,\s*/).indexOf(etag2) > -1;
|
|
116
|
+
}
|
|
117
|
+
function initializeGenerator(generator) {
|
|
118
|
+
if (!generator) {
|
|
119
|
+
if (crypto && crypto.subtle) {
|
|
120
|
+
generator = (body) => crypto.subtle.digest({
|
|
121
|
+
name: "SHA-1"
|
|
122
|
+
}, body);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return generator;
|
|
126
|
+
}
|
|
127
|
+
var etag = (options) => {
|
|
128
|
+
const retainedHeaders = options?.retainedHeaders ?? RETAINED_304_HEADERS;
|
|
129
|
+
const weak = options?.weak ?? false;
|
|
130
|
+
const generator = initializeGenerator(options?.generateDigest);
|
|
131
|
+
return async function etag2(c, next) {
|
|
132
|
+
const ifNoneMatch = c.req.header("If-None-Match") ?? null;
|
|
133
|
+
await next();
|
|
134
|
+
const res = c.res;
|
|
135
|
+
let etag3 = res.headers.get("ETag");
|
|
136
|
+
if (!etag3) {
|
|
137
|
+
if (!generator) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const hash = await generateDigest(res.clone().body, generator);
|
|
141
|
+
if (hash === null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
etag3 = weak ? `W/"${hash}"` : `"${hash}"`;
|
|
145
|
+
}
|
|
146
|
+
if (etagMatches(etag3, ifNoneMatch)) {
|
|
147
|
+
c.res = new Response(null, {
|
|
148
|
+
status: 304,
|
|
149
|
+
statusText: "Not Modified",
|
|
150
|
+
headers: {
|
|
151
|
+
ETag: etag3
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
c.res.headers.forEach((_, key) => {
|
|
155
|
+
if (retainedHeaders.indexOf(key.toLowerCase()) === -1) {
|
|
156
|
+
c.res.headers.delete(key);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
c.res.headers.set("ETag", etag3);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/middleware.ts
|
|
166
|
+
var import_timestring = __toESM(require_timestring(), 1);
|
|
167
|
+
function cacheTime(timeStrOrSeconds) {
|
|
168
|
+
return (c, next) => etag()(c, () => {
|
|
169
|
+
if (c.req.method.toUpperCase() === "GET") {
|
|
170
|
+
const timeInSeconds = typeof timeStrOrSeconds === "number" ? timeStrOrSeconds : import_timestring.default(timeStrOrSeconds);
|
|
171
|
+
c.header("Cache-Control", `public, max-age=${timeInSeconds}`);
|
|
172
|
+
}
|
|
173
|
+
return next();
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
cacheTime
|
|
178
|
+
};
|
package/dist/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
buildClientCSS,
|
|
3
3
|
buildClientJS
|
|
4
4
|
} from "./assets.js";
|
|
5
|
+
import"./chunk-atw8cdg1.js";
|
|
5
6
|
|
|
6
7
|
// src/server.ts
|
|
7
8
|
import { readdir } from "node:fs/promises";
|
|
@@ -1847,18 +1848,6 @@ function createRoute(handler) {
|
|
|
1847
1848
|
_handlers["POST"] = handler2;
|
|
1848
1849
|
return api;
|
|
1849
1850
|
},
|
|
1850
|
-
put(handler2) {
|
|
1851
|
-
_handlers["PUT"] = handler2;
|
|
1852
|
-
return api;
|
|
1853
|
-
},
|
|
1854
|
-
delete(handler2) {
|
|
1855
|
-
_handlers["DELETE"] = handler2;
|
|
1856
|
-
return api;
|
|
1857
|
-
},
|
|
1858
|
-
patch(handler2) {
|
|
1859
|
-
_handlers["PATCH"] = handler2;
|
|
1860
|
-
return api;
|
|
1861
|
-
},
|
|
1862
1851
|
middleware(middleware) {
|
|
1863
1852
|
_middleware = middleware;
|
|
1864
1853
|
return api;
|
|
@@ -1881,7 +1870,7 @@ function createRoute(handler) {
|
|
|
1881
1870
|
const streamOpt = context.req.query("__nostream");
|
|
1882
1871
|
const streamingEnabled = !userIsBot && (streamOpt !== undefined ? streamOpt : true);
|
|
1883
1872
|
if (isHSHtml(routeContent)) {
|
|
1884
|
-
if (streamingEnabled) {
|
|
1873
|
+
if (streamingEnabled && routeContent.asyncContent?.length > 0) {
|
|
1885
1874
|
return new StreamResponse(renderStream(routeContent));
|
|
1886
1875
|
} else {
|
|
1887
1876
|
const output = await renderAsync(routeContent);
|
|
@@ -1909,7 +1898,7 @@ function createAPIRoute(handler) {
|
|
|
1909
1898
|
_handlers["GET"] = handler;
|
|
1910
1899
|
}
|
|
1911
1900
|
const api = {
|
|
1912
|
-
_kind: "
|
|
1901
|
+
_kind: "hsAPIRoute",
|
|
1913
1902
|
get(handler2) {
|
|
1914
1903
|
_handlers["GET"] = handler2;
|
|
1915
1904
|
return api;
|
|
@@ -1988,21 +1977,40 @@ function getRunnableRoute(route) {
|
|
|
1988
1977
|
throw new Error(`Route not runnable. Use "export default createRoute()" to create a Hyperspan route. Exported methods found were: ${Object.keys(route).join(", ")}`);
|
|
1989
1978
|
}
|
|
1990
1979
|
function isRunnableRoute(route) {
|
|
1991
|
-
|
|
1980
|
+
if (typeof route !== "object") {
|
|
1981
|
+
return false;
|
|
1982
|
+
}
|
|
1983
|
+
const obj = route;
|
|
1984
|
+
const runnableKind = ["hsRoute", "hsAPIRoute", "hsAction"].includes(obj?._kind);
|
|
1985
|
+
return runnableKind && "_getRouteHandlers" in obj;
|
|
1992
1986
|
}
|
|
1993
1987
|
async function showErrorReponse(context, err) {
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1988
|
+
let status = 500;
|
|
1989
|
+
const message = err.message || "Internal Server Error";
|
|
1990
|
+
if (err instanceof HTTPException) {
|
|
1991
|
+
status = err.status;
|
|
1992
|
+
}
|
|
1993
|
+
const stack = !IS_PROD && err.stack ? err.stack.split(`
|
|
1999
1994
|
`).slice(1).join(`
|
|
2000
|
-
`) : ""
|
|
2001
|
-
|
|
1995
|
+
`) : "";
|
|
1996
|
+
const output = render(html`
|
|
1997
|
+
<!DOCTYPE html>
|
|
1998
|
+
<html lang="en">
|
|
1999
|
+
<head>
|
|
2000
|
+
<meta charset="UTF-8" />
|
|
2001
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
2002
|
+
<title>Application Error</title>
|
|
2003
|
+
</head>
|
|
2004
|
+
<body>
|
|
2005
|
+
<main>
|
|
2006
|
+
<h1>Application Error</h1>
|
|
2007
|
+
<strong>${message}</strong>
|
|
2008
|
+
${stack ? html`<pre>${stack}</pre>` : ""}
|
|
2009
|
+
</main>
|
|
2010
|
+
</body>
|
|
2011
|
+
</html>
|
|
2002
2012
|
`);
|
|
2003
|
-
return context.html(output, {
|
|
2004
|
-
status: 500
|
|
2005
|
-
});
|
|
2013
|
+
return context.html(output, { status });
|
|
2006
2014
|
}
|
|
2007
2015
|
var ROUTE_SEGMENT = /(\[[a-zA-Z_\.]+\])/g;
|
|
2008
2016
|
async function buildRoutes(config) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperspan/framework",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Hyperspan Web Framework",
|
|
5
5
|
"main": "dist/server.ts",
|
|
6
6
|
"types": "src/server.ts",
|
|
@@ -17,13 +17,17 @@
|
|
|
17
17
|
"types": "./src/server.ts",
|
|
18
18
|
"default": "./dist/server.js"
|
|
19
19
|
},
|
|
20
|
-
"./actions": {
|
|
21
|
-
"types": "./src/actions.ts",
|
|
22
|
-
"default": "./src/actions.ts"
|
|
23
|
-
},
|
|
24
20
|
"./assets": {
|
|
25
21
|
"types": "./src/assets.ts",
|
|
26
22
|
"default": "./dist/assets.js"
|
|
23
|
+
},
|
|
24
|
+
"./middleware": {
|
|
25
|
+
"types": "./src/middleware.ts",
|
|
26
|
+
"default": "./dist/middleware.js"
|
|
27
|
+
},
|
|
28
|
+
"./unstable/actions": {
|
|
29
|
+
"types": "./src/actions.ts",
|
|
30
|
+
"default": "./src/actions.ts"
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
"author": "Vance Lucas <vance@vancelucas.com>",
|
|
@@ -56,6 +60,7 @@
|
|
|
56
60
|
"@types/bun": "^1.2.14",
|
|
57
61
|
"@types/node": "^22.15.20",
|
|
58
62
|
"@types/react": "^19.1.5",
|
|
63
|
+
"@types/timestring": "^7.0.0",
|
|
59
64
|
"prettier": "^3.5.3",
|
|
60
65
|
"typescript": "^5.8.3"
|
|
61
66
|
},
|
|
@@ -63,6 +68,7 @@
|
|
|
63
68
|
"@hyperspan/html": "^0.1.7",
|
|
64
69
|
"hono": "^4.7.10",
|
|
65
70
|
"isbot": "^5.1.28",
|
|
66
|
-
"
|
|
71
|
+
"timestring": "^7.0.0",
|
|
72
|
+
"zod": "^3.25.67"
|
|
67
73
|
}
|
|
68
74
|
}
|
package/src/actions.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import z from 'zod
|
|
2
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { unstable__createAction } from './actions';
|
|
3
3
|
import { describe, it, expect } from 'bun:test';
|
|
4
4
|
import { html, render, type HSHtml } from '@hyperspan/html';
|
|
5
5
|
import type { Context } from 'hono';
|
|
@@ -22,7 +22,7 @@ describe('createAction', () => {
|
|
|
22
22
|
const schema = z.object({
|
|
23
23
|
name: z.string(),
|
|
24
24
|
});
|
|
25
|
-
const action =
|
|
25
|
+
const action = unstable__createAction(schema, formWithNameOnly);
|
|
26
26
|
|
|
27
27
|
const formResponse = render(action.render({ data: { name: 'John' } }) as HSHtml);
|
|
28
28
|
expect(formResponse).toContain('value="John"');
|
|
@@ -34,7 +34,7 @@ describe('createAction', () => {
|
|
|
34
34
|
const schema = z.object({
|
|
35
35
|
name: z.string().nonempty(),
|
|
36
36
|
});
|
|
37
|
-
const action =
|
|
37
|
+
const action = unstable__createAction(schema, formWithNameOnly)
|
|
38
38
|
.post((c, { data }) => {
|
|
39
39
|
return html`<div>Thanks for submitting the form, ${data?.name}!</div>`;
|
|
40
40
|
})
|
|
@@ -65,7 +65,7 @@ describe('createAction', () => {
|
|
|
65
65
|
const schema = z.object({
|
|
66
66
|
name: z.string().nonempty(),
|
|
67
67
|
});
|
|
68
|
-
const action =
|
|
68
|
+
const action = unstable__createAction(schema)
|
|
69
69
|
.form(formWithNameOnly)
|
|
70
70
|
.post((c, { data }) => {
|
|
71
71
|
return html`<div>Thanks for submitting the form, ${data?.name}!</div>`;
|
package/src/actions.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface HSAction<T extends z.ZodTypeAny> {
|
|
|
32
32
|
run(method: 'GET' | 'POST', c: Context): Promise<THSResponseTypes>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function
|
|
35
|
+
export function unstable__createAction<T extends z.ZodTypeAny>(
|
|
36
36
|
schema: T | null = null,
|
|
37
37
|
form: Parameters<HSAction<T>['form']>[0] | null = null
|
|
38
38
|
) {
|
|
@@ -87,7 +87,7 @@ export function createAction<T extends z.ZodTypeAny>(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const formData = await c.req.formData();
|
|
90
|
-
const jsonData =
|
|
90
|
+
const jsonData = unstable__formDataToJSON(formData);
|
|
91
91
|
const schemaData = schema ? schema.safeParse(jsonData) : null;
|
|
92
92
|
const data = schemaData?.success ? (schemaData.data as z.infer<T>) : undefined;
|
|
93
93
|
let error: z.ZodError | Error | null = null;
|
|
@@ -128,7 +128,7 @@ export type THSHandlerResponse = (context: Context) => THSResponseTypes | Promis
|
|
|
128
128
|
*
|
|
129
129
|
* @link https://stackoverflow.com/a/75406413
|
|
130
130
|
*/
|
|
131
|
-
export function
|
|
131
|
+
export function unstable__formDataToJSON(formData: FormData): Record<string, string | string[]> {
|
|
132
132
|
let object = {};
|
|
133
133
|
|
|
134
134
|
/**
|
package/src/assets.ts
CHANGED
|
@@ -3,6 +3,11 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { readdir } from 'node:fs/promises';
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
|
|
6
|
+
export type THSIslandOptions = {
|
|
7
|
+
ssr?: boolean;
|
|
8
|
+
loading?: 'lazy' | undefined;
|
|
9
|
+
};
|
|
10
|
+
|
|
6
11
|
const IS_PROD = process.env.NODE_ENV === 'production';
|
|
7
12
|
const PWD = import.meta.dir;
|
|
8
13
|
|
|
@@ -97,24 +102,15 @@ export function assetHash(content: string): string {
|
|
|
97
102
|
* Island defaults
|
|
98
103
|
*/
|
|
99
104
|
export const ISLAND_PUBLIC_PATH = '/_hs/js/islands';
|
|
100
|
-
export const ISLAND_DEFAULTS = () => ({
|
|
105
|
+
export const ISLAND_DEFAULTS: () => THSIslandOptions = () => ({
|
|
101
106
|
ssr: true,
|
|
107
|
+
loading: undefined,
|
|
102
108
|
});
|
|
103
109
|
|
|
104
110
|
export function renderIsland(Component: any, props: any, options = ISLAND_DEFAULTS()) {
|
|
105
|
-
// Render
|
|
111
|
+
// Render island with its own logic
|
|
106
112
|
if (Component.__HS_ISLAND?.render) {
|
|
107
|
-
return Component.__HS_ISLAND.render(props, options);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// If ssr is true, render the island with the ssr function
|
|
111
|
-
if (Component.__HS_ISLAND?.ssr && options.ssr) {
|
|
112
|
-
return Component.__HS_ISLAND.ssr(props);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// If ssr is false, render the island with the clientOnly function
|
|
116
|
-
if (Component.__HS_ISLAND?.clientOnly) {
|
|
117
|
-
return Component.__HS_ISLAND.clientOnly(props);
|
|
113
|
+
return html.raw(Component.__HS_ISLAND.render(props, options));
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
throw new Error(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { html } from '@hyperspan/html';
|
|
2
|
-
import { Idiomorph } from './idiomorph
|
|
2
|
+
import { Idiomorph } from './idiomorph';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Used for streaming content from the server to the client.
|
|
@@ -31,6 +31,9 @@ function htmlAsyncContentObserver() {
|
|
|
31
31
|
Idiomorph.morph(slotEl, el.content.cloneNode(true));
|
|
32
32
|
el.parentNode.removeChild(el);
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
// Lazy load scripts (if any) after the content is inserted
|
|
36
|
+
lazyLoadScripts();
|
|
34
37
|
}
|
|
35
38
|
} catch (e) {
|
|
36
39
|
console.error(e);
|
|
@@ -122,5 +125,37 @@ function formSubmitToRoute(e: Event, form: HTMLFormElement) {
|
|
|
122
125
|
});
|
|
123
126
|
}
|
|
124
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Intersection observer for lazy loading <script> tags
|
|
130
|
+
*/
|
|
131
|
+
const lazyLoadScriptObserver = new IntersectionObserver(
|
|
132
|
+
(entries, observer) => {
|
|
133
|
+
entries
|
|
134
|
+
.filter((entry) => entry.isIntersecting)
|
|
135
|
+
.forEach((entry) => {
|
|
136
|
+
observer.unobserve(entry.target);
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
if (entry.target.children[0]?.content) {
|
|
139
|
+
// @ts-ignore
|
|
140
|
+
entry.target.replaceWith(entry.target.children[0].content);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
{ rootMargin: '0px 0px -200px 0px' }
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Lazy load <script> tags in the current document
|
|
149
|
+
*/
|
|
150
|
+
function lazyLoadScripts() {
|
|
151
|
+
document
|
|
152
|
+
.querySelectorAll('div[data-loading=lazy]')
|
|
153
|
+
.forEach((el) => lazyLoadScriptObserver.observe(el));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
window.addEventListener('load', () => {
|
|
157
|
+
lazyLoadScripts();
|
|
158
|
+
});
|
|
159
|
+
|
|
125
160
|
// @ts-ignore
|
|
126
161
|
window.html = html;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
1
2
|
/**
|
|
2
3
|
* @typedef {object} ConfigHead
|
|
3
4
|
*
|
|
@@ -206,9 +207,8 @@ var Idiomorph = (function () {
|
|
|
206
207
|
*/
|
|
207
208
|
function saveAndRestoreFocus(ctx, fn) {
|
|
208
209
|
if (!ctx.config.restoreFocus) return fn();
|
|
209
|
-
let activeElement =
|
|
210
|
-
document.activeElement
|
|
211
|
-
);
|
|
210
|
+
let activeElement =
|
|
211
|
+
/** @type {HTMLInputElement|HTMLTextAreaElement|null} */ document.activeElement;
|
|
212
212
|
|
|
213
213
|
// don't bother if the active element is not an input or textarea
|
|
214
214
|
if (
|
|
@@ -324,7 +324,7 @@ var Idiomorph = (function () {
|
|
|
324
324
|
if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null;
|
|
325
325
|
if (ctx.idMap.has(newChild)) {
|
|
326
326
|
// node has children with ids with possible state so create a dummy elt of same type and apply full morph algorithm
|
|
327
|
-
const newEmptyChild = document.createElement(/** @type {Element} */
|
|
327
|
+
const newEmptyChild = document.createElement(/** @type {Element} */ newChild.tagName);
|
|
328
328
|
oldParent.insertBefore(newEmptyChild, insertionPoint);
|
|
329
329
|
morphNode(newEmptyChild, newChild, ctx);
|
|
330
330
|
ctx.callbacks.afterNodeAdded(newEmptyChild);
|
|
@@ -431,8 +431,8 @@ var Idiomorph = (function () {
|
|
|
431
431
|
*/
|
|
432
432
|
function isSoftMatch(oldNode, newNode) {
|
|
433
433
|
// ok to cast: if one is not element, `id` and `tagName` will be undefined and we'll just compare that.
|
|
434
|
-
const oldElt = /** @type {Element} */
|
|
435
|
-
const newElt = /** @type {Element} */
|
|
434
|
+
const oldElt = /** @type {Element} */ oldNode;
|
|
435
|
+
const newElt = /** @type {Element} */ newNode;
|
|
436
436
|
|
|
437
437
|
return (
|
|
438
438
|
oldElt.nodeType === newElt.nodeType &&
|
|
@@ -483,7 +483,7 @@ var Idiomorph = (function () {
|
|
|
483
483
|
let cursor = startInclusive;
|
|
484
484
|
// remove nodes until the endExclusive node
|
|
485
485
|
while (cursor && cursor !== endExclusive) {
|
|
486
|
-
let tempNode = /** @type {Node} */
|
|
486
|
+
let tempNode = /** @type {Node} */ cursor;
|
|
487
487
|
cursor = cursor.nextSibling;
|
|
488
488
|
removeNode(ctx, tempNode);
|
|
489
489
|
}
|
|
@@ -503,11 +503,9 @@ var Idiomorph = (function () {
|
|
|
503
503
|
function moveBeforeById(parentNode, id, after, ctx) {
|
|
504
504
|
const target =
|
|
505
505
|
/** @type {Element} - will always be found */
|
|
506
|
-
(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
ctx.pantry.querySelector(`[id="${id}"]`)
|
|
510
|
-
);
|
|
506
|
+
(ctx.target.id === id && ctx.target) ||
|
|
507
|
+
ctx.target.querySelector(`[id="${id}"]`) ||
|
|
508
|
+
ctx.pantry.querySelector(`[id="${id}"]`);
|
|
511
509
|
removeElementFromAncestorsIdMaps(target, ctx);
|
|
512
510
|
moveBefore(parentNode, target, after);
|
|
513
511
|
return target;
|
|
@@ -587,7 +585,7 @@ var Idiomorph = (function () {
|
|
|
587
585
|
// ignore the head element
|
|
588
586
|
} else if (oldNode instanceof HTMLHeadElement && ctx.head.style !== 'morph') {
|
|
589
587
|
// ok to cast: if newContent wasn't also a <head>, it would've got caught in the `!isSoftMatch` branch above
|
|
590
|
-
handleHeadElement(oldNode, /** @type {HTMLHeadElement} */
|
|
588
|
+
handleHeadElement(oldNode, /** @type {HTMLHeadElement} */ newContent, ctx);
|
|
591
589
|
} else {
|
|
592
590
|
morphAttributes(oldNode, newContent, ctx);
|
|
593
591
|
if (!ignoreValueOfActiveElement(oldNode, ctx)) {
|
|
@@ -613,8 +611,8 @@ var Idiomorph = (function () {
|
|
|
613
611
|
// if is an element type, sync the attributes from the
|
|
614
612
|
// new node into the new node
|
|
615
613
|
if (type === 1 /* element type */) {
|
|
616
|
-
const oldElt = /** @type {Element} */
|
|
617
|
-
const newElt = /** @type {Element} */
|
|
614
|
+
const oldElt = /** @type {Element} */ oldNode;
|
|
615
|
+
const newElt = /** @type {Element} */ newNode;
|
|
618
616
|
|
|
619
617
|
const oldAttributes = oldElt.attributes;
|
|
620
618
|
const newAttributes = newElt.attributes;
|
|
@@ -868,9 +866,9 @@ var Idiomorph = (function () {
|
|
|
868
866
|
let promises = [];
|
|
869
867
|
for (const newNode of nodesToAppend) {
|
|
870
868
|
// TODO: This could theoretically be null, based on type
|
|
871
|
-
let newElt = /** @type {ChildNode} */
|
|
872
|
-
|
|
873
|
-
|
|
869
|
+
let newElt = /** @type {ChildNode} */ document
|
|
870
|
+
.createRange()
|
|
871
|
+
.createContextualFragment(newNode.outerHTML).firstChild;
|
|
874
872
|
if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {
|
|
875
873
|
if (('href' in newElt && newElt.href) || ('src' in newElt && newElt.src)) {
|
|
876
874
|
/** @type {(result?: any) => void} */ let resolve;
|
|
@@ -1116,16 +1114,16 @@ var Idiomorph = (function () {
|
|
|
1116
1114
|
return document.createElement('div'); // dummy parent element
|
|
1117
1115
|
} else if (typeof newContent === 'string') {
|
|
1118
1116
|
return normalizeParent(parseContent(newContent));
|
|
1119
|
-
} else if (generatedByIdiomorph.has(/** @type {Element} */
|
|
1117
|
+
} else if (generatedByIdiomorph.has(/** @type {Element} */ newContent)) {
|
|
1120
1118
|
// the template tag created by idiomorph parsing can serve as a dummy parent
|
|
1121
|
-
return /** @type {Element} */
|
|
1119
|
+
return /** @type {Element} */ newContent;
|
|
1122
1120
|
} else if (newContent instanceof Node) {
|
|
1123
1121
|
if (newContent.parentNode) {
|
|
1124
1122
|
// we can't use the parent directly because newContent may have siblings
|
|
1125
1123
|
// that we don't want in the morph, and reparenting might be expensive (TODO is it?),
|
|
1126
1124
|
// so instead we create a fake parent node that only sees a slice of its children.
|
|
1127
1125
|
/** @type {Element} */
|
|
1128
|
-
return /** @type {any} */
|
|
1126
|
+
return /** @type {any} */ new SlicedParentNode(newContent);
|
|
1129
1127
|
} else {
|
|
1130
1128
|
// a single node is added as a child to a dummy parent
|
|
1131
1129
|
const dummyParent = document.createElement('div');
|
|
@@ -1154,7 +1152,7 @@ var Idiomorph = (function () {
|
|
|
1154
1152
|
/** @param {Node} node */
|
|
1155
1153
|
constructor(node) {
|
|
1156
1154
|
this.originalNode = node;
|
|
1157
|
-
this.realParentNode = /** @type {Element} */
|
|
1155
|
+
this.realParentNode = /** @type {Element} */ node.parentNode;
|
|
1158
1156
|
this.previousSibling = node.previousSibling;
|
|
1159
1157
|
this.nextSibling = node.nextSibling;
|
|
1160
1158
|
}
|
|
@@ -1178,16 +1176,19 @@ var Idiomorph = (function () {
|
|
|
1178
1176
|
* @returns {Element[]}
|
|
1179
1177
|
*/
|
|
1180
1178
|
querySelectorAll(selector) {
|
|
1181
|
-
return this.childNodes.reduce(
|
|
1182
|
-
|
|
1183
|
-
if (node
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1179
|
+
return this.childNodes.reduce(
|
|
1180
|
+
(results, node) => {
|
|
1181
|
+
if (node instanceof Element) {
|
|
1182
|
+
if (node.matches(selector)) results.push(node);
|
|
1183
|
+
const nodeList = node.querySelectorAll(selector);
|
|
1184
|
+
for (let i = 0; i < nodeList.length; i++) {
|
|
1185
|
+
results.push(nodeList[i]);
|
|
1186
|
+
}
|
|
1187
1187
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1188
|
+
return results;
|
|
1189
|
+
},
|
|
1190
|
+
/** @type {Element[]} */ []
|
|
1191
|
+
);
|
|
1191
1192
|
}
|
|
1192
1193
|
|
|
1193
1194
|
/**
|
|
@@ -1255,9 +1256,8 @@ var Idiomorph = (function () {
|
|
|
1255
1256
|
'<body><template>' + newContent + '</template></body>',
|
|
1256
1257
|
'text/html'
|
|
1257
1258
|
);
|
|
1258
|
-
let content =
|
|
1259
|
-
responseDoc.body.querySelector('template')
|
|
1260
|
-
).content;
|
|
1259
|
+
let content =
|
|
1260
|
+
/** @type {HTMLTemplateElement} */ responseDoc.body.querySelector('template').content;
|
|
1261
1261
|
generatedByIdiomorph.add(content);
|
|
1262
1262
|
return content;
|
|
1263
1263
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Context, Next } from 'hono';
|
|
2
|
+
import { etag } from 'hono/etag';
|
|
3
|
+
import timestring from 'timestring';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cache the response for a given time length ('30s', '1d', '1w', '1m', etc) or given number of seconds
|
|
7
|
+
*/
|
|
8
|
+
export function cacheTime(timeStrOrSeconds: string | number) {
|
|
9
|
+
return (c: Context, next: Next) =>
|
|
10
|
+
etag()(c, () => {
|
|
11
|
+
// Only cache GET requests
|
|
12
|
+
if (c.req.method.toUpperCase() === 'GET') {
|
|
13
|
+
const timeInSeconds =
|
|
14
|
+
typeof timeStrOrSeconds === 'number' ? timeStrOrSeconds : timestring(timeStrOrSeconds);
|
|
15
|
+
c.header('Cache-Control', `public, max-age=${timeInSeconds}`);
|
|
16
|
+
}
|
|
17
|
+
return next();
|
|
18
|
+
});
|
|
19
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -6,7 +6,9 @@ import { buildClientJS, buildClientCSS } from './assets';
|
|
|
6
6
|
import { Hono, type Context } from 'hono';
|
|
7
7
|
import { serveStatic } from 'hono/bun';
|
|
8
8
|
import { HTTPException } from 'hono/http-exception';
|
|
9
|
+
|
|
9
10
|
import type { HandlerResponse, MiddlewareHandler } from 'hono/types';
|
|
11
|
+
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
10
12
|
|
|
11
13
|
export const IS_PROD = process.env.NODE_ENV === 'production';
|
|
12
14
|
const CWD = process.cwd();
|
|
@@ -22,9 +24,6 @@ export type THSRoute = {
|
|
|
22
24
|
_kind: 'hsRoute';
|
|
23
25
|
get: (handler: THSRouteHandler) => THSRoute;
|
|
24
26
|
post: (handler: THSRouteHandler) => THSRoute;
|
|
25
|
-
put: (handler: THSRouteHandler) => THSRoute;
|
|
26
|
-
delete: (handler: THSRouteHandler) => THSRoute;
|
|
27
|
-
patch: (handler: THSRouteHandler) => THSRoute;
|
|
28
27
|
middleware: (middleware: Array<MiddlewareHandler>) => THSRoute;
|
|
29
28
|
_getRouteHandlers: () => Array<MiddlewareHandler | ((context: Context) => HandlerResponse<any>)>;
|
|
30
29
|
};
|
|
@@ -57,26 +56,23 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
|
|
|
57
56
|
|
|
58
57
|
const api: THSRoute = {
|
|
59
58
|
_kind: 'hsRoute',
|
|
59
|
+
/**
|
|
60
|
+
* Add a GET route handler (primary page display)
|
|
61
|
+
*/
|
|
60
62
|
get(handler: THSRouteHandler) {
|
|
61
63
|
_handlers['GET'] = handler;
|
|
62
64
|
return api;
|
|
63
65
|
},
|
|
66
|
+
/**
|
|
67
|
+
* Add a POST route handler (typically to process form data)
|
|
68
|
+
*/
|
|
64
69
|
post(handler: THSRouteHandler) {
|
|
65
70
|
_handlers['POST'] = handler;
|
|
66
71
|
return api;
|
|
67
72
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
},
|
|
72
|
-
delete(handler: THSRouteHandler) {
|
|
73
|
-
_handlers['DELETE'] = handler;
|
|
74
|
-
return api;
|
|
75
|
-
},
|
|
76
|
-
patch(handler: THSRouteHandler) {
|
|
77
|
-
_handlers['PATCH'] = handler;
|
|
78
|
-
return api;
|
|
79
|
-
},
|
|
73
|
+
/**
|
|
74
|
+
* Add middleware specific to this route
|
|
75
|
+
*/
|
|
80
76
|
middleware(middleware: Array<MiddlewareHandler>) {
|
|
81
77
|
_middleware = middleware;
|
|
82
78
|
return api;
|
|
@@ -107,7 +103,8 @@ export function createRoute(handler?: THSRouteHandler): THSRoute {
|
|
|
107
103
|
|
|
108
104
|
// Render HSHtml if returned from route handler
|
|
109
105
|
if (isHSHtml(routeContent)) {
|
|
110
|
-
if
|
|
106
|
+
// Stream only if enabled and there is async content to stream
|
|
107
|
+
if (streamingEnabled && (routeContent as HSHtml).asyncContent?.length > 0) {
|
|
111
108
|
return new StreamResponse(renderStream(routeContent as HSHtml)) as Response;
|
|
112
109
|
} else {
|
|
113
110
|
const output = await renderAsync(routeContent as HSHtml);
|
|
@@ -147,7 +144,7 @@ export function createAPIRoute(handler?: THSAPIRouteHandler): THSAPIRoute {
|
|
|
147
144
|
}
|
|
148
145
|
|
|
149
146
|
const api: THSAPIRoute = {
|
|
150
|
-
_kind: '
|
|
147
|
+
_kind: 'hsAPIRoute',
|
|
151
148
|
get(handler: THSAPIRouteHandler) {
|
|
152
149
|
_handlers['GET'] = handler;
|
|
153
150
|
return api;
|
|
@@ -256,9 +253,18 @@ export function getRunnableRoute(route: unknown): THSRoute {
|
|
|
256
253
|
);
|
|
257
254
|
}
|
|
258
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Check if a route is runnable by Hyperspan
|
|
258
|
+
*/
|
|
259
259
|
export function isRunnableRoute(route: unknown): boolean {
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
if (typeof route !== 'object') {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const obj = route as { _kind: string; _getRouteHandlers: any };
|
|
265
|
+
const runnableKind = ['hsRoute', 'hsAPIRoute', 'hsAction'].includes(obj?._kind);
|
|
266
|
+
|
|
267
|
+
return runnableKind && '_getRouteHandlers' in obj;
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
/**
|
|
@@ -266,17 +272,35 @@ export function isRunnableRoute(route: unknown): boolean {
|
|
|
266
272
|
* @TODO: Should check for and load user-customizeable template with special name (app/__error.ts ?)
|
|
267
273
|
*/
|
|
268
274
|
async function showErrorReponse(context: Context, err: Error) {
|
|
275
|
+
let status: ContentfulStatusCode = 500;
|
|
276
|
+
const message = err.message || 'Internal Server Error';
|
|
277
|
+
|
|
278
|
+
// Send correct status code if HTTPException
|
|
279
|
+
if (err instanceof HTTPException) {
|
|
280
|
+
status = err.status as ContentfulStatusCode;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const stack = !IS_PROD && err.stack ? err.stack.split('\n').slice(1).join('\n') : '';
|
|
284
|
+
|
|
269
285
|
const output = render(html`
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
<
|
|
273
|
-
|
|
274
|
-
|
|
286
|
+
<!DOCTYPE html>
|
|
287
|
+
<html lang="en">
|
|
288
|
+
<head>
|
|
289
|
+
<meta charset="UTF-8" />
|
|
290
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
291
|
+
<title>Application Error</title>
|
|
292
|
+
</head>
|
|
293
|
+
<body>
|
|
294
|
+
<main>
|
|
295
|
+
<h1>Application Error</h1>
|
|
296
|
+
<strong>${message}</strong>
|
|
297
|
+
${stack ? html`<pre>${stack}</pre>` : ''}
|
|
298
|
+
</main>
|
|
299
|
+
</body>
|
|
300
|
+
</html>
|
|
275
301
|
`);
|
|
276
302
|
|
|
277
|
-
return context.html(output, {
|
|
278
|
-
status: 500,
|
|
279
|
-
});
|
|
303
|
+
return context.html(output, { status });
|
|
280
304
|
}
|
|
281
305
|
|
|
282
306
|
export type THSServerConfig = {
|
package/src/clientjs/md5.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fast browser md5 function (used for static asset hashing)
|
|
3
|
-
* @link https://www.myersdaily.org/joseph/javascript/md5.js
|
|
4
|
-
*/
|
|
5
|
-
function md5cycle(x, k) {
|
|
6
|
-
var a = x[0],
|
|
7
|
-
b = x[1],
|
|
8
|
-
c = x[2],
|
|
9
|
-
d = x[3];
|
|
10
|
-
|
|
11
|
-
a = ff(a, b, c, d, k[0], 7, -680876936);
|
|
12
|
-
d = ff(d, a, b, c, k[1], 12, -389564586);
|
|
13
|
-
c = ff(c, d, a, b, k[2], 17, 606105819);
|
|
14
|
-
b = ff(b, c, d, a, k[3], 22, -1044525330);
|
|
15
|
-
a = ff(a, b, c, d, k[4], 7, -176418897);
|
|
16
|
-
d = ff(d, a, b, c, k[5], 12, 1200080426);
|
|
17
|
-
c = ff(c, d, a, b, k[6], 17, -1473231341);
|
|
18
|
-
b = ff(b, c, d, a, k[7], 22, -45705983);
|
|
19
|
-
a = ff(a, b, c, d, k[8], 7, 1770035416);
|
|
20
|
-
d = ff(d, a, b, c, k[9], 12, -1958414417);
|
|
21
|
-
c = ff(c, d, a, b, k[10], 17, -42063);
|
|
22
|
-
b = ff(b, c, d, a, k[11], 22, -1990404162);
|
|
23
|
-
a = ff(a, b, c, d, k[12], 7, 1804603682);
|
|
24
|
-
d = ff(d, a, b, c, k[13], 12, -40341101);
|
|
25
|
-
c = ff(c, d, a, b, k[14], 17, -1502002290);
|
|
26
|
-
b = ff(b, c, d, a, k[15], 22, 1236535329);
|
|
27
|
-
|
|
28
|
-
a = gg(a, b, c, d, k[1], 5, -165796510);
|
|
29
|
-
d = gg(d, a, b, c, k[6], 9, -1069501632);
|
|
30
|
-
c = gg(c, d, a, b, k[11], 14, 643717713);
|
|
31
|
-
b = gg(b, c, d, a, k[0], 20, -373897302);
|
|
32
|
-
a = gg(a, b, c, d, k[5], 5, -701558691);
|
|
33
|
-
d = gg(d, a, b, c, k[10], 9, 38016083);
|
|
34
|
-
c = gg(c, d, a, b, k[15], 14, -660478335);
|
|
35
|
-
b = gg(b, c, d, a, k[4], 20, -405537848);
|
|
36
|
-
a = gg(a, b, c, d, k[9], 5, 568446438);
|
|
37
|
-
d = gg(d, a, b, c, k[14], 9, -1019803690);
|
|
38
|
-
c = gg(c, d, a, b, k[3], 14, -187363961);
|
|
39
|
-
b = gg(b, c, d, a, k[8], 20, 1163531501);
|
|
40
|
-
a = gg(a, b, c, d, k[13], 5, -1444681467);
|
|
41
|
-
d = gg(d, a, b, c, k[2], 9, -51403784);
|
|
42
|
-
c = gg(c, d, a, b, k[7], 14, 1735328473);
|
|
43
|
-
b = gg(b, c, d, a, k[12], 20, -1926607734);
|
|
44
|
-
|
|
45
|
-
a = hh(a, b, c, d, k[5], 4, -378558);
|
|
46
|
-
d = hh(d, a, b, c, k[8], 11, -2022574463);
|
|
47
|
-
c = hh(c, d, a, b, k[11], 16, 1839030562);
|
|
48
|
-
b = hh(b, c, d, a, k[14], 23, -35309556);
|
|
49
|
-
a = hh(a, b, c, d, k[1], 4, -1530992060);
|
|
50
|
-
d = hh(d, a, b, c, k[4], 11, 1272893353);
|
|
51
|
-
c = hh(c, d, a, b, k[7], 16, -155497632);
|
|
52
|
-
b = hh(b, c, d, a, k[10], 23, -1094730640);
|
|
53
|
-
a = hh(a, b, c, d, k[13], 4, 681279174);
|
|
54
|
-
d = hh(d, a, b, c, k[0], 11, -358537222);
|
|
55
|
-
c = hh(c, d, a, b, k[3], 16, -722521979);
|
|
56
|
-
b = hh(b, c, d, a, k[6], 23, 76029189);
|
|
57
|
-
a = hh(a, b, c, d, k[9], 4, -640364487);
|
|
58
|
-
d = hh(d, a, b, c, k[12], 11, -421815835);
|
|
59
|
-
c = hh(c, d, a, b, k[15], 16, 530742520);
|
|
60
|
-
b = hh(b, c, d, a, k[2], 23, -995338651);
|
|
61
|
-
|
|
62
|
-
a = ii(a, b, c, d, k[0], 6, -198630844);
|
|
63
|
-
d = ii(d, a, b, c, k[7], 10, 1126891415);
|
|
64
|
-
c = ii(c, d, a, b, k[14], 15, -1416354905);
|
|
65
|
-
b = ii(b, c, d, a, k[5], 21, -57434055);
|
|
66
|
-
a = ii(a, b, c, d, k[12], 6, 1700485571);
|
|
67
|
-
d = ii(d, a, b, c, k[3], 10, -1894986606);
|
|
68
|
-
c = ii(c, d, a, b, k[10], 15, -1051523);
|
|
69
|
-
b = ii(b, c, d, a, k[1], 21, -2054922799);
|
|
70
|
-
a = ii(a, b, c, d, k[8], 6, 1873313359);
|
|
71
|
-
d = ii(d, a, b, c, k[15], 10, -30611744);
|
|
72
|
-
c = ii(c, d, a, b, k[6], 15, -1560198380);
|
|
73
|
-
b = ii(b, c, d, a, k[13], 21, 1309151649);
|
|
74
|
-
a = ii(a, b, c, d, k[4], 6, -145523070);
|
|
75
|
-
d = ii(d, a, b, c, k[11], 10, -1120210379);
|
|
76
|
-
c = ii(c, d, a, b, k[2], 15, 718787259);
|
|
77
|
-
b = ii(b, c, d, a, k[9], 21, -343485551);
|
|
78
|
-
|
|
79
|
-
x[0] = add32(a, x[0]);
|
|
80
|
-
x[1] = add32(b, x[1]);
|
|
81
|
-
x[2] = add32(c, x[2]);
|
|
82
|
-
x[3] = add32(d, x[3]);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function cmn(q, a, b, x, s, t) {
|
|
86
|
-
a = add32(add32(a, q), add32(x, t));
|
|
87
|
-
return add32((a << s) | (a >>> (32 - s)), b);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function ff(a, b, c, d, x, s, t) {
|
|
91
|
-
return cmn((b & c) | (~b & d), a, b, x, s, t);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function gg(a, b, c, d, x, s, t) {
|
|
95
|
-
return cmn((b & d) | (c & ~d), a, b, x, s, t);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function hh(a, b, c, d, x, s, t) {
|
|
99
|
-
return cmn(b ^ c ^ d, a, b, x, s, t);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function ii(a, b, c, d, x, s, t) {
|
|
103
|
-
return cmn(c ^ (b | ~d), a, b, x, s, t);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function md51(s) {
|
|
107
|
-
var txt = '';
|
|
108
|
-
var n = s.length,
|
|
109
|
-
state = [1732584193, -271733879, -1732584194, 271733878],
|
|
110
|
-
i;
|
|
111
|
-
for (i = 64; i <= s.length; i += 64) {
|
|
112
|
-
md5cycle(state, md5blk(s.substring(i - 64, i)));
|
|
113
|
-
}
|
|
114
|
-
s = s.substring(i - 64);
|
|
115
|
-
var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
116
|
-
for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
|
|
117
|
-
tail[i >> 2] |= 0x80 << (i % 4 << 3);
|
|
118
|
-
if (i > 55) {
|
|
119
|
-
md5cycle(state, tail);
|
|
120
|
-
for (i = 0; i < 16; i++) tail[i] = 0;
|
|
121
|
-
}
|
|
122
|
-
tail[14] = n * 8;
|
|
123
|
-
md5cycle(state, tail);
|
|
124
|
-
return state;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/* there needs to be support for Unicode here,
|
|
128
|
-
* unless we pretend that we can redefine the MD-5
|
|
129
|
-
* algorithm for multi-byte characters (perhaps
|
|
130
|
-
* by adding every four 16-bit characters and
|
|
131
|
-
* shortening the sum to 32 bits). Otherwise
|
|
132
|
-
* I suggest performing MD-5 as if every character
|
|
133
|
-
* was two bytes--e.g., 0040 0025 = @%--but then
|
|
134
|
-
* how will an ordinary MD-5 sum be matched?
|
|
135
|
-
* There is no way to standardize text to something
|
|
136
|
-
* like UTF-8 before transformation; speed cost is
|
|
137
|
-
* utterly prohibitive. The JavaScript standard
|
|
138
|
-
* itself needs to look at this: it should start
|
|
139
|
-
* providing access to strings as preformed UTF-8
|
|
140
|
-
* 8-bit unsigned value arrays.
|
|
141
|
-
*/
|
|
142
|
-
function md5blk(s) {
|
|
143
|
-
/* I figured global was faster. */
|
|
144
|
-
var md5blks = [],
|
|
145
|
-
i; /* Andy King said do it this way. */
|
|
146
|
-
for (i = 0; i < 64; i += 4) {
|
|
147
|
-
md5blks[i >> 2] =
|
|
148
|
-
s.charCodeAt(i) +
|
|
149
|
-
(s.charCodeAt(i + 1) << 8) +
|
|
150
|
-
(s.charCodeAt(i + 2) << 16) +
|
|
151
|
-
(s.charCodeAt(i + 3) << 24);
|
|
152
|
-
}
|
|
153
|
-
return md5blks;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
var hex_chr = '0123456789abcdef'.split('');
|
|
157
|
-
|
|
158
|
-
function rhex(n) {
|
|
159
|
-
var s = '',
|
|
160
|
-
j = 0;
|
|
161
|
-
for (; j < 4; j++) s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f];
|
|
162
|
-
return s;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function hex(x) {
|
|
166
|
-
for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]);
|
|
167
|
-
return x.join('');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function add32(a, b) {
|
|
171
|
-
return (a + b) & 0xffffffff;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function md5(s) {
|
|
175
|
-
return hex(md51(s));
|
|
176
|
-
}
|
package/src/clientjs/preact.ts
DELETED