@hyperspan/framework 2.0.0 → 2.0.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.
@@ -0,0 +1,370 @@
1
+ const MOCK_DOM_MARK = "__hyperspan_mock_dom";
2
+ function markedMockDocument(d) {
3
+ return Boolean(
4
+ d && typeof d === "object" && d[MOCK_DOM_MARK] === true
5
+ );
6
+ }
7
+ function stubElement(tag) {
8
+ const children = [];
9
+ const node = {
10
+ tagName: String(tag).toUpperCase(),
11
+ nodeType: 1,
12
+ nodeName: String(tag).toLowerCase(),
13
+ id: "",
14
+ className: "",
15
+ textContent: "",
16
+ innerHTML: "",
17
+ childNodes: children,
18
+ get children() {
19
+ return children;
20
+ },
21
+ style: {},
22
+ dataset: {},
23
+ parentNode: null,
24
+ parentElement: null,
25
+ classList: {
26
+ contains: () => false,
27
+ add() {
28
+ },
29
+ remove() {
30
+ },
31
+ toggle() {
32
+ return false;
33
+ }
34
+ },
35
+ appendChild(child) {
36
+ children.push(child);
37
+ child.parentNode = node;
38
+ child.parentElement = node;
39
+ return child;
40
+ },
41
+ insertBefore(child, ref) {
42
+ const ch = node.childNodes;
43
+ const refIx = ref == null ? -1 : ch.indexOf(ref);
44
+ if (refIx < 0) children.push(child);
45
+ else children.splice(refIx, 0, child);
46
+ child.parentNode = node;
47
+ child.parentElement = node;
48
+ return child;
49
+ },
50
+ removeChild(child) {
51
+ const ix = children.indexOf(child);
52
+ if (ix >= 0) children.splice(ix, 1);
53
+ const c = child;
54
+ c.parentElement = void 0;
55
+ c.parentNode = void 0;
56
+ return child;
57
+ },
58
+ addEventListener() {
59
+ },
60
+ removeEventListener() {
61
+ },
62
+ dispatchEvent() {
63
+ return true;
64
+ },
65
+ setAttribute() {
66
+ },
67
+ removeAttribute() {
68
+ },
69
+ hasAttribute() {
70
+ return false;
71
+ },
72
+ getAttribute() {
73
+ return null;
74
+ },
75
+ cloneNode() {
76
+ return stubElement(tag);
77
+ },
78
+ getBoundingClientRect: () => ({
79
+ x: 0,
80
+ y: 0,
81
+ width: 0,
82
+ height: 0,
83
+ top: 0,
84
+ left: 0,
85
+ right: 0,
86
+ bottom: 0,
87
+ toJSON() {
88
+ return "{}";
89
+ }
90
+ }),
91
+ blur() {
92
+ },
93
+ focus() {
94
+ },
95
+ click() {
96
+ }
97
+ };
98
+ return node;
99
+ }
100
+ function memoryStorage() {
101
+ const m = /* @__PURE__ */ new Map();
102
+ return {
103
+ get length() {
104
+ return m.size;
105
+ },
106
+ key(i) {
107
+ const keys = [...m.keys()];
108
+ return keys[i] ?? null;
109
+ },
110
+ clear() {
111
+ m.clear();
112
+ },
113
+ getItem(key) {
114
+ return m.get(String(key)) ?? null;
115
+ },
116
+ setItem(key, value) {
117
+ m.set(String(key), String(value));
118
+ },
119
+ removeItem(key) {
120
+ m.delete(String(key));
121
+ }
122
+ };
123
+ }
124
+ function installMockDom() {
125
+ const env = typeof process !== "undefined" ? process.env : {};
126
+ const disabled = env.HYPERSPAN_DISABLE_MOCK_DOM === "1" || env.HYPERSPAN_DISABLE_MOCK_DOM === "true";
127
+ if (disabled) return false;
128
+ const g = globalThis;
129
+ if (markedMockDocument(g.document)) return false;
130
+ if (typeof g.document !== "undefined" && g.document !== null && !markedMockDocument(g.document)) {
131
+ try {
132
+ const d = g.document;
133
+ if (d.body !== void 0 && typeof d.createElement === "function") {
134
+ return false;
135
+ }
136
+ } catch {
137
+ }
138
+ }
139
+ const htmlEl = stubElement("html");
140
+ const body = stubElement("body");
141
+ const head = stubElement("head");
142
+ htmlEl.appendChild(head);
143
+ htmlEl.appendChild(body);
144
+ const navigatorStub = {
145
+ userAgent: "HyperspanSSR/1.0",
146
+ language: "en-US",
147
+ languages: ["en-US"],
148
+ platform: "server",
149
+ onLine: true,
150
+ maxTouchPoints: 0
151
+ };
152
+ let rafId = 1;
153
+ const scheduleRaf = (cb) => {
154
+ queueMicrotask(() => cb(performance.now()));
155
+ return rafId++;
156
+ };
157
+ const win = {
158
+ name: "",
159
+ innerWidth: 1024,
160
+ innerHeight: 768,
161
+ outerWidth: 1024,
162
+ outerHeight: 768,
163
+ devicePixelRatio: 1,
164
+ scrollX: 0,
165
+ scrollY: 0,
166
+ scrollTo() {
167
+ },
168
+ navigator: navigatorStub,
169
+ localStorage: memoryStorage(),
170
+ sessionStorage: memoryStorage(),
171
+ history: {
172
+ length: 1,
173
+ state: null,
174
+ scrollRestoration: "auto",
175
+ replaceState() {
176
+ },
177
+ pushState() {
178
+ },
179
+ forward() {
180
+ },
181
+ back() {
182
+ },
183
+ go() {
184
+ }
185
+ },
186
+ location: new URL("http://localhost/ssr"),
187
+ resizeTo() {
188
+ },
189
+ resizeBy() {
190
+ },
191
+ addEventListener() {
192
+ },
193
+ removeEventListener() {
194
+ },
195
+ dispatchEvent() {
196
+ return true;
197
+ },
198
+ alert() {
199
+ },
200
+ matchMedia(query) {
201
+ const media = String(query);
202
+ return {
203
+ media,
204
+ matches: false,
205
+ addListener() {
206
+ },
207
+ removeListener() {
208
+ },
209
+ addEventListener() {
210
+ },
211
+ removeEventListener() {
212
+ },
213
+ dispatchEvent() {
214
+ return false;
215
+ },
216
+ onchange: null
217
+ };
218
+ },
219
+ getComputedStyle: () => ({
220
+ getPropertyValue: () => "",
221
+ setProperty() {
222
+ }
223
+ }),
224
+ requestIdleCallback(cb) {
225
+ queueMicrotask(() => cb({ didTimeout: false, timeRemaining: () => Number.MAX_SAFE_INTEGER }));
226
+ return 1;
227
+ },
228
+ cancelIdleCallback() {
229
+ },
230
+ requestAnimationFrame: scheduleRaf,
231
+ cancelAnimationFrame() {
232
+ },
233
+ MutationObserver: typeof globalThis.MutationObserver !== "undefined" ? globalThis.MutationObserver : class {
234
+ constructor(_callback) {
235
+ }
236
+ disconnect() {
237
+ }
238
+ observe() {
239
+ }
240
+ takeRecords() {
241
+ return [];
242
+ }
243
+ },
244
+ IntersectionObserver: typeof globalThis.IntersectionObserver !== "undefined" ? globalThis.IntersectionObserver : class {
245
+ constructor(_cb, _opts) {
246
+ }
247
+ unobserve() {
248
+ }
249
+ disconnect() {
250
+ }
251
+ observe() {
252
+ }
253
+ takeRecords() {
254
+ return [];
255
+ }
256
+ root = null;
257
+ rootMargin = "";
258
+ thresholds = [];
259
+ },
260
+ ResizeObserver: typeof globalThis.ResizeObserver !== "undefined" ? globalThis.ResizeObserver : class {
261
+ constructor(_callback) {
262
+ }
263
+ disconnect() {
264
+ }
265
+ observe() {
266
+ }
267
+ unobserve() {
268
+ }
269
+ }
270
+ };
271
+ win.self = win;
272
+ win.window = win;
273
+ const documentStub = {
274
+ [MOCK_DOM_MARK]: true,
275
+ nodeType: 9,
276
+ defaultView: null,
277
+ compatibilityMode: "CSS1Compat",
278
+ documentElement: htmlEl,
279
+ body,
280
+ head,
281
+ cookie: "",
282
+ readyState: "complete",
283
+ URL: "http://localhost/ssr/",
284
+ referrer: "",
285
+ hidden: false,
286
+ visibilityState: "visible",
287
+ parentElement: null,
288
+ appendChild(...args) {
289
+ return body.appendChild(...args);
290
+ },
291
+ querySelector(sel) {
292
+ const s = String(sel).toLowerCase();
293
+ if (s === "body" || s === "html body") return body;
294
+ if (s === "html") return htmlEl;
295
+ return null;
296
+ },
297
+ querySelectorAll() {
298
+ return {
299
+ length: 0,
300
+ item() {
301
+ return null;
302
+ },
303
+ forEach() {
304
+ },
305
+ *[Symbol.iterator]() {
306
+ }
307
+ };
308
+ },
309
+ getElementById(id) {
310
+ const sid = String(id);
311
+ const e = stubElement("div");
312
+ e.id = sid;
313
+ e.getAttribute = (k) => k === "id" ? sid : null;
314
+ e.hasAttribute = (k) => k === "id";
315
+ return e;
316
+ },
317
+ getElementsByTagName() {
318
+ return [];
319
+ },
320
+ getElementsByClassName() {
321
+ return [];
322
+ },
323
+ createElement(tag) {
324
+ return stubElement(String(tag ?? "div"));
325
+ },
326
+ createTextNode(data) {
327
+ const v = String(data ?? "");
328
+ return { nodeType: 3, nodeValue: v, nodeName: "#text", textContent: v };
329
+ },
330
+ createDocumentFragment() {
331
+ const frag = {
332
+ nodeType: 11,
333
+ childNodes: [],
334
+ appendChild(child) {
335
+ frag.childNodes.push(child);
336
+ return child;
337
+ }
338
+ };
339
+ return frag;
340
+ },
341
+ elementFromPoint: () => null,
342
+ caretRangeFromPoint: () => null,
343
+ addEventListener() {
344
+ },
345
+ removeEventListener() {
346
+ },
347
+ dispatchEvent() {
348
+ return true;
349
+ }
350
+ };
351
+ documentStub.defaultView = win;
352
+ win.document = documentStub;
353
+ g.window = win;
354
+ g.document = documentStub;
355
+ try {
356
+ globalThis.navigator = navigatorStub;
357
+ } catch {
358
+ }
359
+ if (typeof globalThis.requestAnimationFrame !== "function")
360
+ globalThis.requestAnimationFrame = scheduleRaf;
361
+ if (typeof globalThis.cancelAnimationFrame !== "function")
362
+ globalThis.cancelAnimationFrame = (() => {
363
+ });
364
+ return true;
365
+ }
366
+ export {
367
+ MOCK_DOM_MARK,
368
+ installMockDom,
369
+ stubElement
370
+ };
package/dist/utils.js ADDED
@@ -0,0 +1,157 @@
1
+ function fnv1a64(input) {
2
+ let hash = BigInt("0xcbf29ce484222325");
3
+ const prime = BigInt("0x100000001b3");
4
+ const mask = BigInt("0xffffffffffffffff");
5
+ for (let i = 0; i < input.length; i++) {
6
+ hash ^= BigInt(input.charCodeAt(i));
7
+ hash = hash * prime & mask;
8
+ }
9
+ return hash.toString(16).padStart(16, "0");
10
+ }
11
+ function assetHash(content) {
12
+ return fnv1a64(content);
13
+ }
14
+ function randomHash() {
15
+ const bytes = new Uint8Array(16);
16
+ crypto.getRandomValues(bytes);
17
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
18
+ return fnv1a64(hex);
19
+ }
20
+ const ROUTE_SEGMENT_REGEX = /(\[[a-zA-Z_\.]+\])/g;
21
+ function parsePath(urlPath) {
22
+ const params = [];
23
+ urlPath = urlPath.replace("index", "").replace(".ts", "").replace(".js", "");
24
+ if (urlPath.startsWith("/")) {
25
+ urlPath = urlPath.substring(1);
26
+ }
27
+ if (urlPath.endsWith("/")) {
28
+ urlPath = urlPath.substring(0, urlPath.length - 1);
29
+ }
30
+ if (!urlPath) {
31
+ return { path: "/", params: [] };
32
+ }
33
+ if (ROUTE_SEGMENT_REGEX.test(urlPath)) {
34
+ urlPath = urlPath.replace(ROUTE_SEGMENT_REGEX, (match) => {
35
+ const paramName = match.replace(/[^a-zA-Z_\.]+/g, "");
36
+ params.push(paramName);
37
+ if (match.includes("...")) {
38
+ return "*";
39
+ } else {
40
+ return ":" + paramName;
41
+ }
42
+ });
43
+ }
44
+ return {
45
+ path: "/" + urlPath.split("/").map(
46
+ (segment) => segment.startsWith(":") || segment === "*" ? segment : segment.toLowerCase()
47
+ ).join("/"),
48
+ params
49
+ };
50
+ }
51
+ function isValidRoutePath(path) {
52
+ const isHiddenRoute = path.includes("/__");
53
+ const isTestFile = path.includes(".test") || path.includes(".spec");
54
+ return !isHiddenRoute && !isTestFile && Boolean(path);
55
+ }
56
+ function formDataToJSON(formData) {
57
+ let object = {};
58
+ const parseKey = (key) => {
59
+ const subKeyIdx = key.indexOf("[");
60
+ if (subKeyIdx !== -1) {
61
+ const keys = [key.substring(0, subKeyIdx)];
62
+ key = key.substring(subKeyIdx);
63
+ for (const match of key.matchAll(/\[(?<key>.*?)]/gm)) {
64
+ if (match.groups) {
65
+ keys.push(match.groups.key);
66
+ }
67
+ }
68
+ return keys;
69
+ } else {
70
+ return [key];
71
+ }
72
+ };
73
+ const assign = (keys, value, object2) => {
74
+ const key = keys.shift();
75
+ if (key === "" || key === void 0) {
76
+ return object2.push(value);
77
+ }
78
+ if (Reflect.has(object2, key)) {
79
+ if (keys.length === 0) {
80
+ if (!Array.isArray(object2[key])) {
81
+ object2[key] = [object2[key], value];
82
+ return;
83
+ }
84
+ }
85
+ return assign(keys, value, object2[key]);
86
+ }
87
+ if (keys.length >= 1) {
88
+ object2[key] = keys[0] === "" ? [] : {};
89
+ return assign(keys, value, object2[key]);
90
+ } else {
91
+ object2[key] = value;
92
+ }
93
+ };
94
+ const arrayify = (value) => {
95
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
96
+ return value;
97
+ }
98
+ const keys = Object.keys(value);
99
+ const allNumeric = keys.length > 0 && keys.every((key) => /^\d+$/.test(key));
100
+ if (allNumeric) {
101
+ const array = [];
102
+ for (const key of keys.sort((a, b) => Number(a) - Number(b))) {
103
+ array[Number(key)] = arrayify(value[key]);
104
+ }
105
+ return array;
106
+ }
107
+ for (const key of keys) {
108
+ value[key] = arrayify(value[key]);
109
+ }
110
+ return value;
111
+ };
112
+ for (const pair of formData.entries()) {
113
+ assign(parseKey(pair[0]), pair[1], object);
114
+ }
115
+ return arrayify(object);
116
+ }
117
+ function removeUndefined(obj) {
118
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
119
+ }
120
+ function buildUrl(base, diff, options) {
121
+ const next = new URL(base);
122
+ if (options?.clean) {
123
+ next.search = "";
124
+ next.hash = "";
125
+ }
126
+ if (diff?.pathname === null) {
127
+ next.pathname = "/";
128
+ } else if (diff?.pathname !== void 0) {
129
+ next.pathname = diff.pathname;
130
+ }
131
+ if (diff?.searchParams === null) {
132
+ next.search = "";
133
+ } else if (diff?.searchParams !== void 0) {
134
+ for (const [key, value] of Object.entries(diff.searchParams)) {
135
+ if (value === null || value === void 0) {
136
+ next.searchParams.delete(key);
137
+ } else {
138
+ next.searchParams.set(key, String(value));
139
+ }
140
+ }
141
+ }
142
+ if (diff?.hash === null) {
143
+ next.hash = "";
144
+ } else if (diff?.hash !== void 0) {
145
+ next.hash = diff.hash;
146
+ }
147
+ return next;
148
+ }
149
+ export {
150
+ assetHash,
151
+ buildUrl,
152
+ formDataToJSON,
153
+ isValidRoutePath,
154
+ parsePath,
155
+ randomHash,
156
+ removeUndefined
157
+ };
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@hyperspan/framework",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Hyperspan Web Framework",
5
5
  "type": "module",
6
- "main": "src/server.ts",
7
- "types": "src/server.ts",
6
+ "main": "./dist/server.js",
7
+ "types": "./src/server.ts",
8
8
  "public": true,
9
+ "files": [
10
+ "dist",
11
+ "src",
12
+ "LICENSE.txt"
13
+ ],
9
14
  "publishConfig": {
10
15
  "access": "public",
11
16
  "tag": "latest"
@@ -13,59 +18,59 @@
13
18
  "exports": {
14
19
  ".": {
15
20
  "types": "./src/index.ts",
16
- "default": "./src/index.ts"
21
+ "default": "./dist/index.js"
17
22
  },
18
23
  "./server": {
19
24
  "types": "./src/server.ts",
20
- "default": "./src/server.ts"
25
+ "default": "./dist/server.js"
21
26
  },
22
27
  "./middleware": {
23
28
  "types": "./src/middleware.ts",
24
- "default": "./src/middleware.ts"
29
+ "default": "./dist/middleware.js"
25
30
  },
26
31
  "./utils": {
27
32
  "types": "./src/utils.ts",
28
- "default": "./src/utils.ts"
33
+ "default": "./dist/utils.js"
29
34
  },
30
35
  "./routes": {
31
36
  "types": "./src/register-routes.ts",
32
- "default": "./src/register-routes.ts"
37
+ "default": "./dist/register-routes.js"
33
38
  },
34
39
  "./layout": {
35
40
  "types": "./src/layout.ts",
36
- "default": "./src/layout.ts"
41
+ "default": "./dist/layout.js"
37
42
  },
38
43
  "./dev-bindings": {
39
44
  "types": "./src/dev-bindings.ts",
40
- "default": "./src/dev-bindings.ts"
45
+ "default": "./dist/dev-bindings.js"
41
46
  },
42
47
  "./client/css": {
43
48
  "types": "./src/client/css.ts",
44
- "default": "./src/client/css.ts"
49
+ "default": "./dist/client/css.js"
45
50
  },
46
51
  "./client/js": {
47
52
  "types": "./src/client/js.ts",
48
- "default": "./src/client/js.ts"
53
+ "default": "./dist/client/js.js"
49
54
  },
50
55
  "./client/manifest": {
51
56
  "types": "./src/client/manifest.ts",
52
- "default": "./src/client/manifest.ts"
57
+ "default": "./dist/client/manifest.js"
53
58
  },
54
59
  "./actions": {
55
60
  "types": "./src/actions.ts",
56
- "default": "./src/actions.ts"
61
+ "default": "./dist/actions.js"
57
62
  },
58
63
  "./html": {
59
64
  "types": "./src/html.ts",
60
- "default": "./src/html.ts"
65
+ "default": "./dist/html.js"
61
66
  },
62
67
  "./ssr/install-server-dom-mock": {
63
68
  "types": "./src/ssr/install-server-dom-mock.ts",
64
- "default": "./src/ssr/install-server-dom-mock.ts"
69
+ "default": "./dist/ssr/install-server-dom-mock.js"
65
70
  },
66
71
  "./ssr/mock-dom": {
67
72
  "types": "./src/ssr/mock-dom.ts",
68
- "default": "./src/ssr/mock-dom.ts"
73
+ "default": "./dist/ssr/mock-dom.js"
69
74
  },
70
75
  "./client/_hs/hyperspan-streaming.client.ts": {
71
76
  "default": "./src/client/_hs/hyperspan-streaming.client.ts"
@@ -98,6 +103,8 @@
98
103
  "url": "https://github.com/vlucas/hyperspan/issues"
99
104
  },
100
105
  "scripts": {
106
+ "build": "node ../../scripts/build-node-packages.mjs framework",
107
+ "prepack": "npm run build",
101
108
  "test": "vitest run"
102
109
  },
103
110
  "devDependencies": {
@@ -108,7 +115,7 @@
108
115
  "vitest": "^4.1.10"
109
116
  },
110
117
  "dependencies": {
111
- "@hyperspan/html": "^2.0.0",
118
+ "@hyperspan/html": "^2.0.1",
112
119
  "debug": "^4.4.3",
113
120
  "isbot": "^5.1.32",
114
121
  "zod": "^4.4.3"
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(bun test:*)",
5
- "Bash(npx tsc *)",
6
- "Bash(echo \"exit: $?\")"
7
- ]
8
- }
9
- }
package/tsconfig.json DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "compileOnSave": true,
3
- "include": ["src/**/*.ts"],
4
- "compilerOptions": {
5
- "outDir": "dist",
6
- "target": "ES2020",
7
- "lib": ["ESNext", "dom", "dom.iterable"],
8
- "module": "esnext",
9
- "moduleResolution": "bundler",
10
- "moduleDetection": "force",
11
- "esModuleInterop": true,
12
- "allowImportingTsExtensions": true,
13
- "noEmit": true,
14
- "strict": true,
15
- "declaration": true,
16
- "sourceMap": true,
17
- "downlevelIteration": true,
18
- "skipLibCheck": true,
19
- "allowSyntheticDefaultImports": true,
20
- "forceConsistentCasingInFileNames": true,
21
- "allowJs": true,
22
- "baseUrl": "."
23
- },
24
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
25
- }