@hyperspan/framework 1.0.29 → 2.0.0-alpha.2

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/src/utils.ts CHANGED
@@ -1,20 +1,36 @@
1
- import { createHash, randomBytes } from "node:crypto";
1
+ import type { Hyperspan as HS } from './types';
2
+
3
+ /**
4
+ * FNV-1a 64-bit hash — portable sync hash for asset IDs (works on Node, Bun, Workers).
5
+ */
6
+ function fnv1a64(input: string): string {
7
+ let hash = BigInt('0xcbf29ce484222325');
8
+ const prime = BigInt('0x100000001b3');
9
+ const mask = BigInt('0xffffffffffffffff');
10
+ for (let i = 0; i < input.length; i++) {
11
+ hash ^= BigInt(input.charCodeAt(i));
12
+ hash = (hash * prime) & mask;
13
+ }
14
+ return hash.toString(16).padStart(16, '0');
15
+ }
2
16
 
3
17
  export function assetHash(content: string): string {
4
- return createHash('md5').update(content).digest('hex');
18
+ return fnv1a64(content);
5
19
  }
6
20
 
7
21
  export function randomHash(): string {
8
- return createHash('md5').update(randomBytes(32).toString('hex')).digest('hex');
22
+ const bytes = new Uint8Array(16);
23
+ crypto.getRandomValues(bytes);
24
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
25
+ return fnv1a64(hex);
9
26
  }
10
27
 
11
-
12
28
  /**
13
29
  * Normalize URL path
14
30
  * Removes trailing slash and lowercases path
15
31
  */
16
32
  const ROUTE_SEGMENT_REGEX = /(\[[a-zA-Z_\.]+\])/g;
17
- export function parsePath(urlPath: string): { path: string, params: string[] } {
33
+ export function parsePath(urlPath: string): { path: string; params: string[] } {
18
34
  const params: string[] = [];
19
35
  urlPath = urlPath.replace('index', '').replace('.ts', '').replace('.js', '');
20
36
 
@@ -46,15 +62,14 @@ export function parsePath(urlPath: string): { path: string, params: string[] } {
46
62
 
47
63
  // Only lowercase non-param segments (do not lowercase after ':')
48
64
  return {
49
- path: (
65
+ path:
50
66
  '/' +
51
67
  urlPath
52
68
  .split('/')
53
69
  .map((segment) =>
54
70
  segment.startsWith(':') || segment === '*' ? segment : segment.toLowerCase()
55
71
  )
56
- .join('/')
57
- ),
72
+ .join('/'),
58
73
  params,
59
74
  };
60
75
  }
@@ -75,7 +90,9 @@ export function isValidRoutePath(path: string): boolean {
75
90
  *
76
91
  * @link https://stackoverflow.com/a/75406413
77
92
  */
78
- export function formDataToJSON(formData: FormData | URLSearchParams): Record<string, string | string[]> {
93
+ export function formDataToJSON(
94
+ formData: FormData | URLSearchParams
95
+ ): Record<string, string | string[]> {
79
96
  let object = {};
80
97
 
81
98
  /**
@@ -170,4 +187,43 @@ export function formDataToJSON(formData: FormData | URLSearchParams): Record<str
170
187
  */
171
188
  export function removeUndefined(obj: Record<string, any>): Record<string, any> {
172
189
  return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
173
- }
190
+ }
191
+
192
+ /**
193
+ * Build a new URL from a base URL and an optional diff.
194
+ * Does not mutate the base URL.
195
+ */
196
+ export function buildUrl(base: URL, diff?: HS.UrlDiff, options?: HS.UrlOptions): URL {
197
+ const next = new URL(base);
198
+
199
+ if (options?.clean) {
200
+ next.search = '';
201
+ next.hash = '';
202
+ }
203
+
204
+ if (diff?.pathname === null) {
205
+ next.pathname = '/';
206
+ } else if (diff?.pathname !== undefined) {
207
+ next.pathname = diff.pathname;
208
+ }
209
+
210
+ if (diff?.searchParams === null) {
211
+ next.search = '';
212
+ } else if (diff?.searchParams !== undefined) {
213
+ for (const [key, value] of Object.entries(diff.searchParams)) {
214
+ if (value === null || value === undefined) {
215
+ next.searchParams.delete(key);
216
+ } else {
217
+ next.searchParams.set(key, String(value));
218
+ }
219
+ }
220
+ }
221
+
222
+ if (diff?.hash === null) {
223
+ next.hash = '';
224
+ } else if (diff?.hash !== undefined) {
225
+ next.hash = diff.hash;
226
+ }
227
+
228
+ return next;
229
+ }
package/tsconfig.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "compileOnSave": true,
3
+ "include": ["src/**/*.ts"],
3
4
  "compilerOptions": {
4
5
  "outDir": "dist",
5
- "target": "es2019",
6
+ "target": "ES2020",
6
7
  "lib": ["ESNext", "dom", "dom.iterable"],
7
8
  "module": "esnext",
8
9
  "moduleResolution": "bundler",
@@ -10,7 +11,6 @@
10
11
  "esModuleInterop": true,
11
12
  "allowImportingTsExtensions": true,
12
13
  "noEmit": true,
13
- "composite": true,
14
14
  "strict": true,
15
15
  "declaration": true,
16
16
  "sourceMap": true,
@@ -19,11 +19,7 @@
19
19
  "allowSyntheticDefaultImports": true,
20
20
  "forceConsistentCasingInFileNames": true,
21
21
  "allowJs": true,
22
- "baseUrl": ".",
23
- "paths": {
24
- "@hyperspan/html": ["../html/src/html.ts"]
25
- }
22
+ "baseUrl": "."
26
23
  },
27
- "references": [{ "path": "../html" }],
28
- "exclude": ["node_modules", "__tests__", "*.test.ts"]
24
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
29
25
  }