@lengkapp/edge 0.0.47 → 0.0.49
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/README.md +36 -162
- package/dist/README.md +36 -162
- package/dist/edge-client.js +2 -2
- package/dist/edge-server.d.ts +48 -47
- package/dist/edge-server.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @lengkapp/edge
|
|
2
2
|
|
|
3
|
-
A minimal, high-performance framework for Cloudflare Workers with built-in server-side rendering, routing,
|
|
3
|
+
A minimal, high-performance framework for Cloudflare Workers with built-in server-side rendering, routing, and a declarative client-side partial-update library.
|
|
4
4
|
|
|
5
5
|
Inspired by [Hono](https://hono.dev), `@lengkapp/edge` targets the same class of performance while shipping with **zero runtime dependencies**.
|
|
6
6
|
|
|
@@ -13,10 +13,6 @@ Inspired by [Hono](https://hono.dev), `@lengkapp/edge` targets the same class of
|
|
|
13
13
|
- [Routing](#routing)
|
|
14
14
|
- [Context API](#context-api)
|
|
15
15
|
- [JSX & Server-Side Rendering](#jsx--server-side-rendering)
|
|
16
|
-
- [Route Options](#route-options)
|
|
17
|
-
- [Caching](#caching)
|
|
18
|
-
- [Compression](#compression)
|
|
19
|
-
- [CORS](#cors)
|
|
20
16
|
- [Security](#security)
|
|
21
17
|
- [Scheduled Handler](#scheduled-handler)
|
|
22
18
|
- [Client-Side Partial Updates](#client-side-partial-updates)
|
|
@@ -30,9 +26,6 @@ Inspired by [Hono](https://hono.dev), `@lengkapp/edge` targets the same class of
|
|
|
30
26
|
| ⚡ | **Fast router** | Static routes in a `Map`, dynamic routes in a trie — O(1) for static, O(segments) for params |
|
|
31
27
|
| 🧩 | **Async JSX SSR** | `renderToString` supports async function components and streams nothing it doesn't need |
|
|
32
28
|
| 🔒 | **Secure by default** | `nosniff`, `DENY` framing, strict referrer policy, permissions policy, URL/attribute sanitisation |
|
|
33
|
-
| 🗄️ | **Edge caching** | `caches.default` integration with `ttl` and `stale-while-revalidate` |
|
|
34
|
-
| 🗜️ | **Compression** | gzip / deflate via `CompressionStream`, content-type aware |
|
|
35
|
-
| 🌐 | **CORS** | Per-route, wildcard / allow-list / exact-match |
|
|
36
29
|
| 🍪 | **Cookies** | Lazy parsing, safe keys, full `Set-Cookie` option support |
|
|
37
30
|
| 🪶 | **Client runtime** | ~2 KB IIFE: fetch + swap HTML from HTML attributes. No build step |
|
|
38
31
|
| 📦 | **Zero deps** | Nothing but the platform |
|
|
@@ -107,8 +100,9 @@ npm run dev # http://localhost:8787
|
|
|
107
100
|
npm run deploy
|
|
108
101
|
```
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
### Why `export default app`?
|
|
104
|
+
|
|
105
|
+
The `Edge` instance exposes a `fetch(request, env, executionCtx)` method, which is exactly the Workers module-syntax contract.
|
|
112
106
|
|
|
113
107
|
### JSX pragma
|
|
114
108
|
|
|
@@ -125,20 +119,18 @@ That's all the configuration required — no Babel, no tsconfig, no plugin.
|
|
|
125
119
|
## Routing
|
|
126
120
|
|
|
127
121
|
```js
|
|
128
|
-
app.get(path,
|
|
129
|
-
app.post(path,
|
|
130
|
-
app.put(path,
|
|
131
|
-
app.delete(path,
|
|
132
|
-
app.patch(path,
|
|
133
|
-
app.options(path,
|
|
134
|
-
app.head(path,
|
|
122
|
+
app.get(path, handler);
|
|
123
|
+
app.post(path, handler);
|
|
124
|
+
app.put(path, handler);
|
|
125
|
+
app.delete(path, handler);
|
|
126
|
+
app.patch(path, handler);
|
|
127
|
+
app.options(path, handler);
|
|
128
|
+
app.head(path, handler);
|
|
135
129
|
```
|
|
136
130
|
|
|
137
|
-
`options` may be omitted entirely, or passed as the second argument:
|
|
138
|
-
|
|
139
131
|
```js
|
|
140
132
|
app.get('/health', (c) => c.text('ok'));
|
|
141
|
-
app.get('/api/users/:id',
|
|
133
|
+
app.get('/api/users/:id', (c) => c.json({ id: c.params.id }));
|
|
142
134
|
```
|
|
143
135
|
|
|
144
136
|
### Static vs. dynamic paths
|
|
@@ -161,6 +153,20 @@ app.get('/users/:id', (c) => {
|
|
|
161
153
|
|
|
162
154
|
Unmatched requests receive `404 Not Found` as `text/plain`.
|
|
163
155
|
|
|
156
|
+
### Mounting sub-apps
|
|
157
|
+
|
|
158
|
+
Assign an `Edge` (or array of `Edge`s) to `app.routes` to merge its routes into the parent, prefixed with the parent's own prefix:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
const signIn = new Edge('/sign-in');
|
|
162
|
+
signIn.get('/login', handler); // → /sign-in/login
|
|
163
|
+
|
|
164
|
+
const app = new Edge();
|
|
165
|
+
app.routes = [signIn];
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> **Note:** The merge is eager and one-way: routes registered on a sub-app *after* it was mounted do not propagate.
|
|
169
|
+
|
|
164
170
|
## Context API
|
|
165
171
|
|
|
166
172
|
Every handler receives a `Context` instance.
|
|
@@ -171,7 +177,6 @@ Every handler receives a `Context` instance.
|
|
|
171
177
|
|---|---|---|
|
|
172
178
|
| `c.req` | `Request` | The incoming request |
|
|
173
179
|
| `c.env` | `object` | Worker bindings / environment |
|
|
174
|
-
| `c.executionCtx` | `ExecutionContext` | For `waitUntil()` |
|
|
175
180
|
| `c.params` | `object` | Route parameters |
|
|
176
181
|
| `c.url` | `URL` | Parsed request URL |
|
|
177
182
|
| `c.query` | `URLSearchParams` | `c.url.searchParams` |
|
|
@@ -195,7 +200,7 @@ c.redirect('https://example.com', 301);
|
|
|
195
200
|
- a JSX node or array of nodes (rendered with `renderToString`)
|
|
196
201
|
- anything else (`String(data)`)
|
|
197
202
|
|
|
198
|
-
Both are async because component trees may contain async components.
|
|
203
|
+
Both are `async` because component trees may contain async components.
|
|
199
204
|
|
|
200
205
|
### Headers
|
|
201
206
|
|
|
@@ -352,108 +357,9 @@ This bypasses escaping. **Only use it with content you fully control.**
|
|
|
352
357
|
- `href`, `src`, `xlink:href`, `action`, `formaction` are rejected if they resolve to `javascript:`, `vbscript:`, or `data:text/html` (whitespace and control characters are stripped before the check).
|
|
353
358
|
- Props named `__proto__`, `constructor`, or `prototype` are ignored.
|
|
354
359
|
|
|
355
|
-
## Route Options
|
|
356
|
-
|
|
357
|
-
```js
|
|
358
|
-
app.get('/path', {
|
|
359
|
-
cors: true,
|
|
360
|
-
validate: async (c) => true,
|
|
361
|
-
cache: { ttl: 300, staleWhileRevalidate: 60 },
|
|
362
|
-
compress: true,
|
|
363
|
-
log: true,
|
|
364
|
-
}, handler);
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
| Option | Type | Description |
|
|
368
|
-
|---|---|---|
|
|
369
|
-
| `cors` | `boolean \| object` | Enable CORS. `true` uses the global default (`origin: '*'`). |
|
|
370
|
-
| `validate` | `async (c) => boolean` | Runs before the handler. Returning falsy yields `400 Validation failed`. |
|
|
371
|
-
| `cache` | `boolean \| { ttl, staleWhileRevalidate }` | Cache successful GET responses in `caches.default`. Defaults: `ttl = 3600`, `swr = 0`. |
|
|
372
|
-
| `compress` | `boolean` | gzip / deflate the response when the client accepts it. |
|
|
373
|
-
| `log` | `boolean` | Log `METHOD URL - STATUS` to the console. |
|
|
374
|
-
|
|
375
|
-
### `validate` example
|
|
376
|
-
|
|
377
|
-
```js
|
|
378
|
-
app.post('/api/items', {
|
|
379
|
-
validate: async (c) => {
|
|
380
|
-
const auth = c.req.headers.get('Authorization');
|
|
381
|
-
return auth === `Bearer ${c.env.API_TOKEN}`;
|
|
382
|
-
},
|
|
383
|
-
}, async (c) => {
|
|
384
|
-
const body = await c.req.json();
|
|
385
|
-
return c.json({ created: true, body }, 201);
|
|
386
|
-
});
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
A thrown error inside `validate` is treated as a failure (and logged as a security event).
|
|
390
|
-
|
|
391
|
-
## Caching
|
|
392
|
-
|
|
393
|
-
```js
|
|
394
|
-
app.get('/expensive', { cache: { ttl: 600, staleWhileRevalidate: 120 } }, async (c) => {
|
|
395
|
-
const data = await computeSomethingSlow();
|
|
396
|
-
return c.json(data);
|
|
397
|
-
});
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
- Only `GET` requests are cached.
|
|
401
|
-
- Only `200` responses are stored.
|
|
402
|
-
- The cache key is the full `Request` (URL + method).
|
|
403
|
-
- `Set-Cookie` is stripped from the cached response.
|
|
404
|
-
- A `Cache-Control: max-age=<ttl>[, stale-while-revalidate=<swr>]` header is written.
|
|
405
|
-
- Writes happen in `ctx.executionCtx.waitUntil(...)`, so they never block the response.
|
|
406
|
-
- On a hit, the stored response is returned directly (with CORS and log processing still applied).
|
|
407
|
-
|
|
408
|
-
## Compression
|
|
409
|
-
|
|
410
|
-
```js
|
|
411
|
-
app.get('/big', { compress: true }, (c) => c.html(hugeMarkup));
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
- Skipped when `Content-Type` is not compressible (`text/*`, `application/json`, `application/xml`, `application/javascript`, `application/xhtml`, `application/ld+json`, `application/manifest+json`, `image/svg*`).
|
|
415
|
-
- Skipped when `Content-Length` is known and `< 1024`.
|
|
416
|
-
- Chooses `gzip` first, then `deflate`, based on `Accept-Encoding`.
|
|
417
|
-
- Sets `Content-Encoding` and `Vary: Accept-Encoding`, removes `Content-Length`.
|
|
418
|
-
|
|
419
|
-
## CORS
|
|
420
|
-
|
|
421
|
-
Global default (used when `cors: true`):
|
|
422
|
-
|
|
423
|
-
```js
|
|
424
|
-
app.defaults.cors = {
|
|
425
|
-
origin: '*',
|
|
426
|
-
methods: 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD',
|
|
427
|
-
};
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
Per-route:
|
|
431
|
-
|
|
432
|
-
```js
|
|
433
|
-
app.get('/api/public', { cors: true }, handler);
|
|
434
|
-
|
|
435
|
-
app.get('/api/private', {
|
|
436
|
-
cors: {
|
|
437
|
-
origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
438
|
-
methods: 'GET, POST',
|
|
439
|
-
headers: 'Content-Type, Authorization',
|
|
440
|
-
},
|
|
441
|
-
}, handler);
|
|
442
|
-
|
|
443
|
-
app.get('/api/exact', { cors: { origin: 'https://example.com' } }, handler);
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
| `origin` value | Result |
|
|
447
|
-
|---|---|
|
|
448
|
-
| `'*'` | `Access-Control-Allow-Origin: *` |
|
|
449
|
-
| `string` | Echoes the request origin only if it matches exactly, plus `Vary: Origin` and `Allow-Credentials: true` |
|
|
450
|
-
| `string[]` | Echoes the request origin only if it is in the list, plus `Vary: Origin` and `Allow-Credentials: true` |
|
|
451
|
-
|
|
452
|
-
`Access-Control-Max-Age: 86400` is always set. Remember to register an `OPTIONS` route if you need preflight handling.
|
|
453
|
-
|
|
454
360
|
## Security
|
|
455
361
|
|
|
456
|
-
|
|
362
|
+
Base headers are applied to every response:
|
|
457
363
|
|
|
458
364
|
```text
|
|
459
365
|
X-Content-Type-Options: nosniff
|
|
@@ -462,24 +368,7 @@ Referrer-Policy: strict-origin-when-cross-origin
|
|
|
462
368
|
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
|
463
369
|
```
|
|
464
370
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
```js
|
|
468
|
-
app.security.extraHeaders = {
|
|
469
|
-
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
470
|
-
'Content-Security-Policy': "default-src 'self'",
|
|
471
|
-
};
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### Security event logging
|
|
475
|
-
|
|
476
|
-
Enabled by default. Events are sampled to at most one per `(event, ip, path)` per 60 seconds.
|
|
477
|
-
|
|
478
|
-
```js
|
|
479
|
-
app.security.logSecurityEvents = false; // disable
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
Logged events: `validation_error`, `handler_error`. Each entry is a JSON line containing timestamp, event, detail, IP (`CF-Connecting-IP`), method, path, and user agent.
|
|
371
|
+
Add or override headers per-response via `c.headers` or the third argument to `c.text` / `c.json` / `c.html` / `c.page`.
|
|
483
372
|
|
|
484
373
|
## Scheduled Handler
|
|
485
374
|
|
|
@@ -522,7 +411,7 @@ Serve it as a static asset:
|
|
|
522
411
|
|---|---|---|
|
|
523
412
|
| `_get` | URL | Perform a `GET` request to this URL. |
|
|
524
413
|
| `_post` | URL | Perform a `POST` request to this URL. |
|
|
525
|
-
| `_trigger` | `load
|
|
414
|
+
| `_trigger` | `load`, `visible`, `click`, `submit`, `change`, `input`, `keyup`, `dblclick` | When to fire. Default: `submit` on a `<form>`, `click` on anything else. |
|
|
526
415
|
| `_in` | CSS selector | Element whose `innerHTML` receives the response. Default: the element itself. |
|
|
527
416
|
| `_form` | CSS selector | A `<form>` whose `FormData` becomes the URL-encoded POST body. |
|
|
528
417
|
| `_json` | CSS selector | A `<form>` or input whose values become a JSON POST body. |
|
|
@@ -747,16 +636,12 @@ app.post('/todos', async (c) => {
|
|
|
747
636
|
return c.html(<TodoList />);
|
|
748
637
|
});
|
|
749
638
|
|
|
750
|
-
/* Dynamic params + JSON
|
|
751
|
-
app.get(
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
(
|
|
755
|
-
|
|
756
|
-
if (!todo) return c.json({ error: 'Not found' }, 404);
|
|
757
|
-
return c.json(todo);
|
|
758
|
-
}
|
|
759
|
-
);
|
|
639
|
+
/* Dynamic params + JSON */
|
|
640
|
+
app.get('/api/todos/:id', (c) => {
|
|
641
|
+
const todo = todos.get(c.params.id);
|
|
642
|
+
if (!todo) return c.json({ error: 'Not found' }, 404);
|
|
643
|
+
return c.json(todo);
|
|
644
|
+
});
|
|
760
645
|
|
|
761
646
|
/* Cookie round-trip */
|
|
762
647
|
app.get('/theme/:name', (c) => {
|
|
@@ -772,17 +657,6 @@ app.get('/theme/:name', (c) => {
|
|
|
772
657
|
|
|
773
658
|
app.get('/api/theme', (c) => c.json({ theme: c.getCookie('theme') || 'light' }));
|
|
774
659
|
|
|
775
|
-
/* Compressed, validated, cached API */
|
|
776
|
-
app.get(
|
|
777
|
-
'/api/large',
|
|
778
|
-
{
|
|
779
|
-
compress: true,
|
|
780
|
-
cache: { ttl: 120 },
|
|
781
|
-
validate: async (c) => c.req.headers.get('X-Api-Key') === c.env.API_KEY,
|
|
782
|
-
},
|
|
783
|
-
(c) => c.json({ items: Array.from({ length: 500 }, (_, i) => ({ i })) })
|
|
784
|
-
);
|
|
785
|
-
|
|
786
660
|
/* ------------------------------------------------------------------ *
|
|
787
661
|
* Export *
|
|
788
662
|
* ------------------------------------------------------------------ */
|
package/dist/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @lengkapp/edge
|
|
2
2
|
|
|
3
|
-
A minimal, high-performance framework for Cloudflare Workers with built-in server-side rendering, routing,
|
|
3
|
+
A minimal, high-performance framework for Cloudflare Workers with built-in server-side rendering, routing, and a declarative client-side partial-update library.
|
|
4
4
|
|
|
5
5
|
Inspired by [Hono](https://hono.dev), `@lengkapp/edge` targets the same class of performance while shipping with **zero runtime dependencies**.
|
|
6
6
|
|
|
@@ -13,10 +13,6 @@ Inspired by [Hono](https://hono.dev), `@lengkapp/edge` targets the same class of
|
|
|
13
13
|
- [Routing](#routing)
|
|
14
14
|
- [Context API](#context-api)
|
|
15
15
|
- [JSX & Server-Side Rendering](#jsx--server-side-rendering)
|
|
16
|
-
- [Route Options](#route-options)
|
|
17
|
-
- [Caching](#caching)
|
|
18
|
-
- [Compression](#compression)
|
|
19
|
-
- [CORS](#cors)
|
|
20
16
|
- [Security](#security)
|
|
21
17
|
- [Scheduled Handler](#scheduled-handler)
|
|
22
18
|
- [Client-Side Partial Updates](#client-side-partial-updates)
|
|
@@ -30,9 +26,6 @@ Inspired by [Hono](https://hono.dev), `@lengkapp/edge` targets the same class of
|
|
|
30
26
|
| ⚡ | **Fast router** | Static routes in a `Map`, dynamic routes in a trie — O(1) for static, O(segments) for params |
|
|
31
27
|
| 🧩 | **Async JSX SSR** | `renderToString` supports async function components and streams nothing it doesn't need |
|
|
32
28
|
| 🔒 | **Secure by default** | `nosniff`, `DENY` framing, strict referrer policy, permissions policy, URL/attribute sanitisation |
|
|
33
|
-
| 🗄️ | **Edge caching** | `caches.default` integration with `ttl` and `stale-while-revalidate` |
|
|
34
|
-
| 🗜️ | **Compression** | gzip / deflate via `CompressionStream`, content-type aware |
|
|
35
|
-
| 🌐 | **CORS** | Per-route, wildcard / allow-list / exact-match |
|
|
36
29
|
| 🍪 | **Cookies** | Lazy parsing, safe keys, full `Set-Cookie` option support |
|
|
37
30
|
| 🪶 | **Client runtime** | ~2 KB IIFE: fetch + swap HTML from HTML attributes. No build step |
|
|
38
31
|
| 📦 | **Zero deps** | Nothing but the platform |
|
|
@@ -107,8 +100,9 @@ npm run dev # http://localhost:8787
|
|
|
107
100
|
npm run deploy
|
|
108
101
|
```
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
### Why `export default app`?
|
|
104
|
+
|
|
105
|
+
The `Edge` instance exposes a `fetch(request, env, executionCtx)` method, which is exactly the Workers module-syntax contract.
|
|
112
106
|
|
|
113
107
|
### JSX pragma
|
|
114
108
|
|
|
@@ -125,20 +119,18 @@ That's all the configuration required — no Babel, no tsconfig, no plugin.
|
|
|
125
119
|
## Routing
|
|
126
120
|
|
|
127
121
|
```js
|
|
128
|
-
app.get(path,
|
|
129
|
-
app.post(path,
|
|
130
|
-
app.put(path,
|
|
131
|
-
app.delete(path,
|
|
132
|
-
app.patch(path,
|
|
133
|
-
app.options(path,
|
|
134
|
-
app.head(path,
|
|
122
|
+
app.get(path, handler);
|
|
123
|
+
app.post(path, handler);
|
|
124
|
+
app.put(path, handler);
|
|
125
|
+
app.delete(path, handler);
|
|
126
|
+
app.patch(path, handler);
|
|
127
|
+
app.options(path, handler);
|
|
128
|
+
app.head(path, handler);
|
|
135
129
|
```
|
|
136
130
|
|
|
137
|
-
`options` may be omitted entirely, or passed as the second argument:
|
|
138
|
-
|
|
139
131
|
```js
|
|
140
132
|
app.get('/health', (c) => c.text('ok'));
|
|
141
|
-
app.get('/api/users/:id',
|
|
133
|
+
app.get('/api/users/:id', (c) => c.json({ id: c.params.id }));
|
|
142
134
|
```
|
|
143
135
|
|
|
144
136
|
### Static vs. dynamic paths
|
|
@@ -161,6 +153,20 @@ app.get('/users/:id', (c) => {
|
|
|
161
153
|
|
|
162
154
|
Unmatched requests receive `404 Not Found` as `text/plain`.
|
|
163
155
|
|
|
156
|
+
### Mounting sub-apps
|
|
157
|
+
|
|
158
|
+
Assign an `Edge` (or array of `Edge`s) to `app.routes` to merge its routes into the parent, prefixed with the parent's own prefix:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
const signIn = new Edge('/sign-in');
|
|
162
|
+
signIn.get('/login', handler); // → /sign-in/login
|
|
163
|
+
|
|
164
|
+
const app = new Edge();
|
|
165
|
+
app.routes = [signIn];
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> **Note:** The merge is eager and one-way: routes registered on a sub-app *after* it was mounted do not propagate.
|
|
169
|
+
|
|
164
170
|
## Context API
|
|
165
171
|
|
|
166
172
|
Every handler receives a `Context` instance.
|
|
@@ -171,7 +177,6 @@ Every handler receives a `Context` instance.
|
|
|
171
177
|
|---|---|---|
|
|
172
178
|
| `c.req` | `Request` | The incoming request |
|
|
173
179
|
| `c.env` | `object` | Worker bindings / environment |
|
|
174
|
-
| `c.executionCtx` | `ExecutionContext` | For `waitUntil()` |
|
|
175
180
|
| `c.params` | `object` | Route parameters |
|
|
176
181
|
| `c.url` | `URL` | Parsed request URL |
|
|
177
182
|
| `c.query` | `URLSearchParams` | `c.url.searchParams` |
|
|
@@ -195,7 +200,7 @@ c.redirect('https://example.com', 301);
|
|
|
195
200
|
- a JSX node or array of nodes (rendered with `renderToString`)
|
|
196
201
|
- anything else (`String(data)`)
|
|
197
202
|
|
|
198
|
-
Both are async because component trees may contain async components.
|
|
203
|
+
Both are `async` because component trees may contain async components.
|
|
199
204
|
|
|
200
205
|
### Headers
|
|
201
206
|
|
|
@@ -352,108 +357,9 @@ This bypasses escaping. **Only use it with content you fully control.**
|
|
|
352
357
|
- `href`, `src`, `xlink:href`, `action`, `formaction` are rejected if they resolve to `javascript:`, `vbscript:`, or `data:text/html` (whitespace and control characters are stripped before the check).
|
|
353
358
|
- Props named `__proto__`, `constructor`, or `prototype` are ignored.
|
|
354
359
|
|
|
355
|
-
## Route Options
|
|
356
|
-
|
|
357
|
-
```js
|
|
358
|
-
app.get('/path', {
|
|
359
|
-
cors: true,
|
|
360
|
-
validate: async (c) => true,
|
|
361
|
-
cache: { ttl: 300, staleWhileRevalidate: 60 },
|
|
362
|
-
compress: true,
|
|
363
|
-
log: true,
|
|
364
|
-
}, handler);
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
| Option | Type | Description |
|
|
368
|
-
|---|---|---|
|
|
369
|
-
| `cors` | `boolean \| object` | Enable CORS. `true` uses the global default (`origin: '*'`). |
|
|
370
|
-
| `validate` | `async (c) => boolean` | Runs before the handler. Returning falsy yields `400 Validation failed`. |
|
|
371
|
-
| `cache` | `boolean \| { ttl, staleWhileRevalidate }` | Cache successful GET responses in `caches.default`. Defaults: `ttl = 3600`, `swr = 0`. |
|
|
372
|
-
| `compress` | `boolean` | gzip / deflate the response when the client accepts it. |
|
|
373
|
-
| `log` | `boolean` | Log `METHOD URL - STATUS` to the console. |
|
|
374
|
-
|
|
375
|
-
### `validate` example
|
|
376
|
-
|
|
377
|
-
```js
|
|
378
|
-
app.post('/api/items', {
|
|
379
|
-
validate: async (c) => {
|
|
380
|
-
const auth = c.req.headers.get('Authorization');
|
|
381
|
-
return auth === `Bearer ${c.env.API_TOKEN}`;
|
|
382
|
-
},
|
|
383
|
-
}, async (c) => {
|
|
384
|
-
const body = await c.req.json();
|
|
385
|
-
return c.json({ created: true, body }, 201);
|
|
386
|
-
});
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
A thrown error inside `validate` is treated as a failure (and logged as a security event).
|
|
390
|
-
|
|
391
|
-
## Caching
|
|
392
|
-
|
|
393
|
-
```js
|
|
394
|
-
app.get('/expensive', { cache: { ttl: 600, staleWhileRevalidate: 120 } }, async (c) => {
|
|
395
|
-
const data = await computeSomethingSlow();
|
|
396
|
-
return c.json(data);
|
|
397
|
-
});
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
- Only `GET` requests are cached.
|
|
401
|
-
- Only `200` responses are stored.
|
|
402
|
-
- The cache key is the full `Request` (URL + method).
|
|
403
|
-
- `Set-Cookie` is stripped from the cached response.
|
|
404
|
-
- A `Cache-Control: max-age=<ttl>[, stale-while-revalidate=<swr>]` header is written.
|
|
405
|
-
- Writes happen in `ctx.executionCtx.waitUntil(...)`, so they never block the response.
|
|
406
|
-
- On a hit, the stored response is returned directly (with CORS and log processing still applied).
|
|
407
|
-
|
|
408
|
-
## Compression
|
|
409
|
-
|
|
410
|
-
```js
|
|
411
|
-
app.get('/big', { compress: true }, (c) => c.html(hugeMarkup));
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
- Skipped when `Content-Type` is not compressible (`text/*`, `application/json`, `application/xml`, `application/javascript`, `application/xhtml`, `application/ld+json`, `application/manifest+json`, `image/svg*`).
|
|
415
|
-
- Skipped when `Content-Length` is known and `< 1024`.
|
|
416
|
-
- Chooses `gzip` first, then `deflate`, based on `Accept-Encoding`.
|
|
417
|
-
- Sets `Content-Encoding` and `Vary: Accept-Encoding`, removes `Content-Length`.
|
|
418
|
-
|
|
419
|
-
## CORS
|
|
420
|
-
|
|
421
|
-
Global default (used when `cors: true`):
|
|
422
|
-
|
|
423
|
-
```js
|
|
424
|
-
app.defaults.cors = {
|
|
425
|
-
origin: '*',
|
|
426
|
-
methods: 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD',
|
|
427
|
-
};
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
Per-route:
|
|
431
|
-
|
|
432
|
-
```js
|
|
433
|
-
app.get('/api/public', { cors: true }, handler);
|
|
434
|
-
|
|
435
|
-
app.get('/api/private', {
|
|
436
|
-
cors: {
|
|
437
|
-
origin: ['https://app.example.com', 'https://admin.example.com'],
|
|
438
|
-
methods: 'GET, POST',
|
|
439
|
-
headers: 'Content-Type, Authorization',
|
|
440
|
-
},
|
|
441
|
-
}, handler);
|
|
442
|
-
|
|
443
|
-
app.get('/api/exact', { cors: { origin: 'https://example.com' } }, handler);
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
| `origin` value | Result |
|
|
447
|
-
|---|---|
|
|
448
|
-
| `'*'` | `Access-Control-Allow-Origin: *` |
|
|
449
|
-
| `string` | Echoes the request origin only if it matches exactly, plus `Vary: Origin` and `Allow-Credentials: true` |
|
|
450
|
-
| `string[]` | Echoes the request origin only if it is in the list, plus `Vary: Origin` and `Allow-Credentials: true` |
|
|
451
|
-
|
|
452
|
-
`Access-Control-Max-Age: 86400` is always set. Remember to register an `OPTIONS` route if you need preflight handling.
|
|
453
|
-
|
|
454
360
|
## Security
|
|
455
361
|
|
|
456
|
-
|
|
362
|
+
Base headers are applied to every response:
|
|
457
363
|
|
|
458
364
|
```text
|
|
459
365
|
X-Content-Type-Options: nosniff
|
|
@@ -462,24 +368,7 @@ Referrer-Policy: strict-origin-when-cross-origin
|
|
|
462
368
|
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
|
463
369
|
```
|
|
464
370
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
```js
|
|
468
|
-
app.security.extraHeaders = {
|
|
469
|
-
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
470
|
-
'Content-Security-Policy': "default-src 'self'",
|
|
471
|
-
};
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### Security event logging
|
|
475
|
-
|
|
476
|
-
Enabled by default. Events are sampled to at most one per `(event, ip, path)` per 60 seconds.
|
|
477
|
-
|
|
478
|
-
```js
|
|
479
|
-
app.security.logSecurityEvents = false; // disable
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
Logged events: `validation_error`, `handler_error`. Each entry is a JSON line containing timestamp, event, detail, IP (`CF-Connecting-IP`), method, path, and user agent.
|
|
371
|
+
Add or override headers per-response via `c.headers` or the third argument to `c.text` / `c.json` / `c.html` / `c.page`.
|
|
483
372
|
|
|
484
373
|
## Scheduled Handler
|
|
485
374
|
|
|
@@ -522,7 +411,7 @@ Serve it as a static asset:
|
|
|
522
411
|
|---|---|---|
|
|
523
412
|
| `_get` | URL | Perform a `GET` request to this URL. |
|
|
524
413
|
| `_post` | URL | Perform a `POST` request to this URL. |
|
|
525
|
-
| `_trigger` | `load
|
|
414
|
+
| `_trigger` | `load`, `visible`, `click`, `submit`, `change`, `input`, `keyup`, `dblclick` | When to fire. Default: `submit` on a `<form>`, `click` on anything else. |
|
|
526
415
|
| `_in` | CSS selector | Element whose `innerHTML` receives the response. Default: the element itself. |
|
|
527
416
|
| `_form` | CSS selector | A `<form>` whose `FormData` becomes the URL-encoded POST body. |
|
|
528
417
|
| `_json` | CSS selector | A `<form>` or input whose values become a JSON POST body. |
|
|
@@ -747,16 +636,12 @@ app.post('/todos', async (c) => {
|
|
|
747
636
|
return c.html(<TodoList />);
|
|
748
637
|
});
|
|
749
638
|
|
|
750
|
-
/* Dynamic params + JSON
|
|
751
|
-
app.get(
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
(
|
|
755
|
-
|
|
756
|
-
if (!todo) return c.json({ error: 'Not found' }, 404);
|
|
757
|
-
return c.json(todo);
|
|
758
|
-
}
|
|
759
|
-
);
|
|
639
|
+
/* Dynamic params + JSON */
|
|
640
|
+
app.get('/api/todos/:id', (c) => {
|
|
641
|
+
const todo = todos.get(c.params.id);
|
|
642
|
+
if (!todo) return c.json({ error: 'Not found' }, 404);
|
|
643
|
+
return c.json(todo);
|
|
644
|
+
});
|
|
760
645
|
|
|
761
646
|
/* Cookie round-trip */
|
|
762
647
|
app.get('/theme/:name', (c) => {
|
|
@@ -772,17 +657,6 @@ app.get('/theme/:name', (c) => {
|
|
|
772
657
|
|
|
773
658
|
app.get('/api/theme', (c) => c.json({ theme: c.getCookie('theme') || 'light' }));
|
|
774
659
|
|
|
775
|
-
/* Compressed, validated, cached API */
|
|
776
|
-
app.get(
|
|
777
|
-
'/api/large',
|
|
778
|
-
{
|
|
779
|
-
compress: true,
|
|
780
|
-
cache: { ttl: 120 },
|
|
781
|
-
validate: async (c) => c.req.headers.get('X-Api-Key') === c.env.API_KEY,
|
|
782
|
-
},
|
|
783
|
-
(c) => c.json({ items: Array.from({ length: 500 }, (_, i) => ({ i })) })
|
|
784
|
-
);
|
|
785
|
-
|
|
786
660
|
/* ------------------------------------------------------------------ *
|
|
787
661
|
* Export *
|
|
788
662
|
* ------------------------------------------------------------------ */
|
package/dist/edge-client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @lengkapp/edge v0.0.
|
|
2
|
+
* @lengkapp/edge v0.0.49
|
|
3
3
|
* MIT License
|
|
4
4
|
*
|
|
5
5
|
* LengkApp Edge License
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
31
31
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
32
32
|
*/
|
|
33
|
-
!function(){if(!window.__ec){window.__ec=1;for(var n=document,r=window,
|
|
33
|
+
!function(){if(!window.__ec){window.__ec=1;for(var n=document,r=window,i=0,t=n.currentScript,o=n.querySelector.bind(n),e=n.createElement.bind(n),a=n.addEventListener.bind(n),f=["click","submit","change","input","keyup","dblclick"],c=new WeakSet,u=new WeakSet,v=new WeakMap,l={},s=r.IntersectionObserver?new IntersectionObserver(function(n){for(var r=0;r<n.length;r++){var i=n[r];i.isIntersecting&&(s.unobserve(i.target),u.delete(i.target),b(i.target,"visible"))}}):null,w=0;w<f.length;w++)(function(n){a(n,function(r){var i=h(r.target,n);i&&(r.cancelable&&r.preventDefault(),l={},b(i,n))},!0)})(f[w]);new MutationObserver(function(n){for(var r=0;r<n.length;r++){var i,t=n[r].addedNodes,o=n[r].removedNodes;for(i=0;i<t.length;i++)1===t[i].nodeType&&g(t[i]);if(s)for(i=0;i<o.length;i++){var e=o[i];1===e.nodeType&&u.has(e)&&(s.unobserve(e),u.delete(e))}}}).observe(n.documentElement,{childList:!0,subtree:!0}),"loading"===n.readyState?a("DOMContentLoaded",function(){g(n.body)}):g(n.body)}function _(n){return n.hasAttribute("_get")||n.hasAttribute("_post")}function d(n){if(!c.has(n)){c.add(n),!_(n)||n.getAttribute("_in")&&"this"!==n.getAttribute("_in")||n.id||(n.id="_g"+ ++i);var r=n.getAttribute("_trigger");"load"===r?b(n,"load"):"visible"===r&&s&&(u.add(n),s.observe(n))}}function g(n){if(n&&1===n.nodeType){_(n)&&d(n);for(var r=n.querySelectorAll("[_get],[_post]"),i=0;i<r.length;i++)d(r[i])}}function m(n,r){var t=n.getAttribute(r);return t&&"this"!==t?t:(n.id||(n.id="_g"+ ++i),"#"+n.id)}function b(n,r){var i=n.getAttribute("_get"),t=n.getAttribute("_post");if(i||t){var e=i||t,a=i?"GET":"POST";if("load"!==r&&"visible"!==r||!((l[e]=(l[e]||0)+1)>8)){var f=v.get(n);f&&f.abort(),f=new AbortController,v.set(n,f);var c=null,u={"x-edge":"1"},s=(o('meta[name="_csrf"]')||{}).content,w=(o('meta[name="_auth"]')||{}).content;if(s&&(u["x-csrf-token"]=s),w&&(u["x-auth-token"]=w),"POST"===a){var _=n.getAttribute("_form"),d=n.getAttribute("_json");if(_){var g=o(_);g&&(c=new URLSearchParams(new FormData(g)).toString(),u["Content-Type"]="application/x-www-form-urlencoded")}else if(d){var b=o(d),h={};b&&("FORM"===b.tagName?new FormData(b).forEach(function(n,r){h[r]=n}):h[b.name||b.id]=b.value),c=JSON.stringify(h),u["Content-Type"]="application/json"}}var O=m(n,"_in"),k=n.hasAttribute("_error");fetch(e,{method:a,headers:u,body:c,signal:f.signal}).then(function(r){return r.text().then(function(i){r.ok?p(O,i):k&&p(m(n,"_error"),i)})}).catch(function(n){if(!n||"AbortError"!==n.name)throw n})}}}function p(n,r){var i=o(n);i&&(i.innerHTML=r,function(n){for(var r=n.querySelectorAll("script,style"),i=0;i<r.length;i++){var o=r[i];if(!(o===t||t&&t.src&&o.src===t.src)){for(var a=e(o.tagName),f=0;f<o.attributes.length;f++)a.setAttribute(o.attributes[f].name,o.attributes[f].value);a.textContent=o.textContent,o.parentNode.replaceChild(a,o)}}}(i),g(i))}function h(n,r){for(;n&&1===n.nodeType;){if(_(n)&&(n.getAttribute("_trigger")||("FORM"===n.tagName?"submit":"click"))===r)return n;n=n.parentNode}return null}}();
|
package/dist/edge-server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @lengkapp/edge v0.0.
|
|
2
|
+
* @lengkapp/edge v0.0.49
|
|
3
3
|
* MIT License
|
|
4
4
|
*
|
|
5
5
|
* LengkApp Edge License
|
|
@@ -94,27 +94,6 @@ export interface CookieOptions {
|
|
|
94
94
|
sameSite?: 'Strict' | 'Lax' | 'None' | string;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
export interface CorsOptions {
|
|
98
|
-
origin?: string | string[];
|
|
99
|
-
methods?: string;
|
|
100
|
-
headers?: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface CacheOptions {
|
|
104
|
-
/** TTL in seconds. Default: 3600 */
|
|
105
|
-
ttl?: number;
|
|
106
|
-
/** stale-while-revalidate in seconds. Default: 0 */
|
|
107
|
-
staleWhileRevalidate?: number;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export interface RouteOptions {
|
|
111
|
-
cors?: boolean | CorsOptions;
|
|
112
|
-
cache?: boolean | CacheOptions;
|
|
113
|
-
compress?: boolean;
|
|
114
|
-
log?: boolean;
|
|
115
|
-
validate?: (ctx: Context) => boolean | Promise<boolean>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
97
|
export class Context {
|
|
119
98
|
constructor(
|
|
120
99
|
request: Request,
|
|
@@ -126,7 +105,6 @@ export class Context {
|
|
|
126
105
|
|
|
127
106
|
req: Request;
|
|
128
107
|
env: Record<string, any>;
|
|
129
|
-
executionCtx: ExecutionContext;
|
|
130
108
|
params: Record<string, string>;
|
|
131
109
|
status: number;
|
|
132
110
|
headers: Headers;
|
|
@@ -192,24 +170,31 @@ export type ErrorHandler = (
|
|
|
192
170
|
err: unknown
|
|
193
171
|
) => any | Promise<any>;
|
|
194
172
|
|
|
195
|
-
export interface SecurityOptions {
|
|
196
|
-
logSecurityEvents?: boolean;
|
|
197
|
-
extraHeaders?: Record<string, string> | null;
|
|
198
|
-
trustedProxies?: string[] | null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export interface EdgeDefaults {
|
|
202
|
-
cors: CorsOptions & { methods: string };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
173
|
export class Edge {
|
|
206
|
-
|
|
174
|
+
/**
|
|
175
|
+
* @param prefix Optional mount prefix for this Edge. `''` and `'/'`
|
|
176
|
+
* both mean "no prefix". Joined with every registered route path at
|
|
177
|
+
* registration time — never stripped or re-applied per request.
|
|
178
|
+
*/
|
|
179
|
+
constructor(prefix?: string);
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Normalized mount prefix for this Edge. `''` means "no prefix".
|
|
183
|
+
* Set once at construction; applied to every route registered on this
|
|
184
|
+
* Edge (and merged from sub-apps) so lookups at request time are
|
|
185
|
+
* plain map/trie hits with no prefix stripping.
|
|
186
|
+
*/
|
|
187
|
+
prefix: string;
|
|
207
188
|
|
|
208
|
-
readonly staticRoutes: Map<string, Map<string,
|
|
189
|
+
readonly staticRoutes: Map<string, Map<string, RouteHandler>>;
|
|
209
190
|
readonly dynamicTries: Record<string, any>;
|
|
210
|
-
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Set via `.scheduled()`. Not initialized in the constructor — read
|
|
194
|
+
* it only from the Worker entry (`export default { fetch, scheduled }`)
|
|
195
|
+
* if you wire that up yourself.
|
|
196
|
+
*/
|
|
211
197
|
scheduledHandler: ScheduledHandler | null;
|
|
212
|
-
security: SecurityOptions;
|
|
213
198
|
|
|
214
199
|
/* ---- Fallback handlers ------------------------------------------ *
|
|
215
200
|
* Exposed as public fields to mirror the runtime shape; normally *
|
|
@@ -218,20 +203,35 @@ export class Edge {
|
|
|
218
203
|
notFoundHandler: NotFoundHandler | null;
|
|
219
204
|
errorHandler: ErrorHandler | null;
|
|
220
205
|
|
|
221
|
-
/* ----
|
|
222
|
-
|
|
206
|
+
/* ---- Sub-app mounting ------------------------------------------- */
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Assigning runs a one-time merge: every route on each sub-app is
|
|
210
|
+
* copied into this Edge's own tables, prefixed with `this.prefix`
|
|
211
|
+
* combined with the sub-app's own prefix. After the merge the hot
|
|
212
|
+
* path is unchanged — no per-request dispatch, no path rewriting.
|
|
213
|
+
*
|
|
214
|
+
* const signIn = new Edge('/sign-in');
|
|
215
|
+
* signIn.get('/login', h);
|
|
216
|
+
* const app = new Edge();
|
|
217
|
+
* app.routes = [signIn]; // GET /sign-in/login
|
|
218
|
+
*
|
|
219
|
+
* Non-`Edge` entries throw. Mount is eager and one-way: routes
|
|
220
|
+
* registered on a sub-app *after* it was mounted do not propagate.
|
|
221
|
+
*/
|
|
222
|
+
set routes(apps: Edge | Edge[]);
|
|
223
|
+
|
|
224
|
+
/* ---- Route registration ----------------------------------------- *
|
|
225
|
+
* Paths are joined with this Edge's `prefix` (if any) at *
|
|
226
|
+
* registration time; the resulting full path is what the router *
|
|
227
|
+
* matches against on each request. */
|
|
228
|
+
|
|
223
229
|
get(path: string, handler: RouteHandler): void;
|
|
224
|
-
post(path: string, options: RouteOptions | boolean, handler: RouteHandler): void;
|
|
225
230
|
post(path: string, handler: RouteHandler): void;
|
|
226
|
-
put(path: string, options: RouteOptions | boolean, handler: RouteHandler): void;
|
|
227
231
|
put(path: string, handler: RouteHandler): void;
|
|
228
|
-
delete(path: string, options: RouteOptions | boolean, handler: RouteHandler): void;
|
|
229
232
|
delete(path: string, handler: RouteHandler): void;
|
|
230
|
-
patch(path: string, options: RouteOptions | boolean, handler: RouteHandler): void;
|
|
231
233
|
patch(path: string, handler: RouteHandler): void;
|
|
232
|
-
options(path: string, options: RouteOptions | boolean, handler: RouteHandler): void;
|
|
233
234
|
options(path: string, handler: RouteHandler): void;
|
|
234
|
-
head(path: string, options: RouteOptions | boolean, handler: RouteHandler): void;
|
|
235
235
|
head(path: string, handler: RouteHandler): void;
|
|
236
236
|
|
|
237
237
|
/* ---- Scheduled handler ------------------------------------------ */
|
|
@@ -242,8 +242,7 @@ export class Edge {
|
|
|
242
242
|
/**
|
|
243
243
|
* Register a handler invoked when no route matches the incoming
|
|
244
244
|
* request. The handler runs with a full Context (headers, cookies,
|
|
245
|
-
* `c.page`, `c.html`)
|
|
246
|
-
* compression, or caching. Its own errors flow into `onError`.
|
|
245
|
+
* `c.page`, `c.html`).
|
|
247
246
|
*
|
|
248
247
|
* `OPTIONS` requests are short-circuited to a 204 (with base security
|
|
249
248
|
* headers, no body) before this handler runs, so preflights never
|
|
@@ -268,6 +267,8 @@ export class Edge {
|
|
|
268
267
|
): Promise<Response>;
|
|
269
268
|
}
|
|
270
269
|
|
|
270
|
+
export default Edge;
|
|
271
|
+
|
|
271
272
|
/* ---------------- JSX typing for this runtime ---------------- */
|
|
272
273
|
type ComponentReturn =
|
|
273
274
|
| JSXNode
|
package/dist/edge-server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @lengkapp/edge v0.0.
|
|
2
|
+
* @lengkapp/edge v0.0.49
|
|
3
3
|
* MIT License
|
|
4
4
|
*
|
|
5
5
|
* LengkApp Edge License
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
31
31
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
32
32
|
*/
|
|
33
|
-
var t=Object.defineProperty,e=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,r=Object.prototype.hasOwnProperty,o=(e,n)=>{for(var r in n)t(e,r,{get:n[r],enumerable:!0})},i=(o,i,s,c)=>{if(i&&"object"==typeof i||"function"==typeof i)for(let a of n(i))r.call(o,a)||a===s||t(o,a,{get:()=>i[a],enumerable:!(c=e(i,a))||c.enumerable});return o},s=e=>i(t({},"__esModule",{value:!0}),e),c={};function a(t){const e=[],n=t.length;let r=1;for(let o=1;o<=n;o++)o!==n&&47!==t.charCodeAt(o)||(o>r&&e.push(t.slice(r,o)),r=o+1);return e}function l(t){const e=t.length;return e>1&&47===t.charCodeAt(e-1)?t.slice(0,-1):t}o(c,{Context:()=>k,Edge:()=>Edge,Fragment:()=>Fragment,jsx:()=>jsx,renderToString:()=>renderToString,useCtx:()=>useCtx}),module.exports=s(c);var u=new Set(["__proto__","constructor","prototype"]);function f(t){return"string"==typeof t&&!u.has(t)}function h(t){const e=t.length;let n=0;for(;n<e;){const e=t.charCodeAt(n);if(38===e||60===e||62===e||34===e||39===e)break;n++}if(n===e)return t;let r=t.slice(0,n),o=n;for(;n<e;n++){const e=t.charCodeAt(n);let i;if(38===e)i="&";else if(60===e)i="<";else if(62===e)i=">";else if(34===e)i=""";else{if(39!==e)continue;i="'"}n>o&&(r+=t.slice(o,n)),r+=i,o=n+1}return o<e&&(r+=t.slice(o)),r}function p(t){return t>=65&&t<=90||t>=97&&t<=122}function d(t){return t>=48&&t<=57}function y(t){const e=t.length;if(0===e||!p(t.charCodeAt(0)))return!1;for(let n=1;n<e;n++){const e=t.charCodeAt(n);if(!p(e)&&!d(e)&&45!==e)return!1}return!0}function w(t){const e=t.length;if(0===e)return!1;const n=t.charCodeAt(0);if(!p(n)&&95!==n&&58!==n)return!1;for(let n=1;n<e;n++){const e=t.charCodeAt(n);if(!p(e)&&!d(e)&&95!==e&&58!==e&&46!==e&&45!==e)return!1}return!0}function g(t){const e=t.charCodeAt(0),n=t.charCodeAt(1);return!(111!==e&&79!==e||110!==n&&78!==n)}var Fragment=Symbol("Fragment");function jsx(t,e,...n){return{type:t,props:e||{},children:n,__isJSX:!0}}var m=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),C=new Set(["allowfullscreen","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]),b=new Set(["animation-iteration-count","border-image-outset","border-image-slice","border-image-width","box-flex","box-flex-group","box-ordinal-group","column-count","columns","flex","flex-grow","flex-positive","flex-shrink","flex-negative","flex-order","grid-row","grid-row-end","grid-row-span","grid-row-start","grid-column","grid-column-end","grid-column-span","grid-column-start","font-weight","line-clamp","line-height","opacity","order","orphans","tab-size","widows","z-index","zoom","fill-opacity","flood-opacity","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-miterlimit","stroke-opacity","stroke-width"]),T=new Map,S=2e3;function O(t){const e=T.get(t);if(void 0!==e)return e;const n=t.replace(/([A-Z])/g,"-$1").toLowerCase().replace(/^-/,"");return T.size>=S&&T.clear(),T.set(t,n),n}function E(t){if(!t||"object"!=typeof t)return"";let e="";for(const n in t){if(!Object.prototype.hasOwnProperty.call(t,n))continue;const r=O(n);let o=t[n];"number"!=typeof o||b.has(r)||(o+="px"),e&&(e+=";"),e+=r+":"+o}return e}var x=new Set(["href","src","xlink:href","action","formaction"]),v=[];function useCtx(){const t=v[v.length-1];if(!t)throw new Error("useCtx() must be called synchronously at the top of a component (before any await). It is not available after an await, nor outside a render.");return t}async function renderToString(t,e){if(void 0===e&&(e=v[v.length-1]),null==t||"boolean"==typeof t)return"";const n=typeof t;if("string"===n)return h(t);if("number"===n)return h(String(t));if("object"===n&&"function"==typeof t.then)return renderToString(await t,e);if(Array.isArray(t)){let n="";for(let r=0;r<t.length;r++)n+=await renderToString(t[r],e);return n}if(!t.__isJSX)return h(String(t));const{type:r,props:o,children:i}=t;if(r===Fragment){let t="";for(let n=0;n<i.length;n++)t+=await renderToString(i[n],e);return t}if("symbol"==typeof r)return"";if("function"==typeof r){let t;if(e){v.push(e);try{t=r({...o,children:i})}finally{v.pop()}}else t=r({...o,children:i});return renderToString(await t,e)}if("string"!=typeof r||!y(r))return"";const s=m.has(r);let c="";for(const t in o){if(!Object.prototype.hasOwnProperty.call(o,t))continue;if("children"===t||"key"===t||"ref"===t)continue;const e=o[t];if(null==e||!1===e)continue;if("dangerouslySetInnerHTML"===t)continue;let n=t;if("className"===t||"class"===t?n="class":"htmlFor"!==t&&"for"!==t||(n="for"),f(n)&&!g(n)&&w(n)){if(x.has(n)){const t=String(e).replace(/[\u0000-\u0020]/g,"").toLowerCase();if(t.startsWith("javascript:")||t.startsWith("vbscript:")||t.startsWith("data:text/html"))continue}if(C.has(n))!0===e&&(c+=" "+n);else if(!0!==e){if("style"===n&&"object"==typeof e){const t=E(e);t&&(c+=' style="'+h(t)+'"');continue}c+=" "+n+'="'+h(String(e))+'"'}else c+=" "+n+'="true"'}}let a="";const l=o.dangerouslySetInnerHTML;if(l&&null!=l.__html)a=l.__html;else for(let t=0;t<i.length;t++)a+=await renderToString(i[t],e);return s?"<"+r+c+"/>":"<"+r+c+">"+a+"</"+r+">"}var A=Object.freeze([["X-Content-Type-Options","nosniff"],["X-Frame-Options","DENY"],["Referrer-Policy","strict-origin-when-cross-origin"],["Permissions-Policy","geolocation=(), microphone=(), camera=()"]]),j=Object.freeze(Object.create(null)),k=class{constructor(t,e,n,r={},o=null){this.req=t,this.env=e,this.executionCtx=n,this.params=r,this.status=200,this.headers=new Headers(A),this.t=null,this.url=o||new URL(t.url)}o(){if(null!==this.t)return this.t;const t=this.req.headers.get("Cookie");if(!t)return this.t=j,j;const e=Object.create(null);let n=0;const r=t.length;for(;n<r;){let o=t.indexOf(";",n);-1===o&&(o=r);let i=n,s=o;for(;i<s&&32===t.charCodeAt(i);)i++;for(;s>i&&32===t.charCodeAt(s-1);)s--;if(s>i){const n=t.indexOf("=",i);if(n>i&&n<s)try{const r=decodeURIComponent(t.slice(i,n));f(r)&&(e[r]=decodeURIComponent(t.slice(n+1,s)))}catch{}}n=o+1}return this.t=e,e}getCookie(t){const e=this.o()[t];return void 0===e?null:e}get query(){return this.url.searchParams}setCookie(t,e,n={}){if("string"!=typeof t||!f(t)||/[\r\n;=]/.test(t))return;let r=encodeURIComponent(t)+"="+encodeURIComponent(e);n.path&&(r+="; Path="+n.path),n.domain&&(r+="; Domain="+n.domain),void 0!==n.maxAge&&(r+="; Max-Age="+n.maxAge),n.expires&&(r+="; Expires="+n.expires.toUTCString()),n.secure&&(r+="; Secure"),n.httpOnly&&(r+="; HttpOnly"),n.sameSite&&(r+="; SameSite="+n.sameSite),this.headers.append("Set-Cookie",r)}deleteCookie(t,e={}){this.setCookie(t,"",{...e,maxAge:0,expires:new Date(0)})}i(t){if(!t)return this.headers;const e=Object.keys(t);if(0===e.length)return this.headers;const n=new Headers(this.headers);for(let r=0;r<e.length;r++)n.set(e[r],t[e[r]]);return n}text(t,e=this.status,n={}){const r=this.i(n);return r.has("Content-Type")||r.set("Content-Type","text/plain; charset=utf-8"),new Response(t,{status:e,headers:r})}json(t,e=this.status,n={}){const r=this.i(n);return r.has("Content-Type")||r.set("Content-Type","application/json; charset=utf-8"),new Response(JSON.stringify(t),{status:e,headers:r})}async html(t,e=this.status,n={}){const r=this.i(n);let o;return r.has("Content-Type")||r.set("Content-Type","text/html; charset=utf-8"),o="string"==typeof t?t:t&&t.__isJSX||Array.isArray(t)?await renderToString(t,this):null==t?"":String(t),new Response(o,{status:e,headers:r})}async page(t,e=this.status,n={}){const r=this.i(n);let o;return r.has("Content-Type")||r.set("Content-Type","text/html; charset=utf-8"),o="string"==typeof t?t:t&&t.__isJSX||Array.isArray(t)?await renderToString(t,this):null==t?"":String(t),/^\s*<!doctype\s/i.test(o)||(o="<!DOCTYPE html>"+o),new Response(o,{status:e,headers:r})}redirect(t,e=302){const n=new Headers(this.headers);return n.set("Location",String(t)),n.delete("Content-Type"),new Response(null,{status:e,headers:n})}},_=class{constructor(){this.children=null,this.paramChild=null,this.handler=null}},P=class{constructor(){this.root=new _}add(t,e,n){let r=this.root;const o=[];for(let e=0;e<t.length;e++){const n=t[e];if(58===n.charCodeAt(0)){const t=n.slice(1);o.push(f(t)?t:"_p"+e),r.paramChild||(r.paramChild=new _),r=r.paramChild}else{r.children||(r.children=new Map);let t=r.children.get(n);t||(t=new _,r.children.set(n,t)),r=t}}r.handler={handler:e,options:n,paramNames:o}}match(t){const e=[],n=this.l(this.root,t,0,e);if(!n)return null;const{handler:r,options:o,paramNames:i}=n,s=Object.create(null);for(let t=0;t<i.length;t++)s[i[t]]=e[t];return{handler:r,options:o,params:s}}l(t,e,n,r){if(n===e.length)return t.handler;const o=e[n],i=t.children;if(i){const t=i.get(o);if(t){const o=this.l(t,e,n+1,r);if(o)return o}}const s=t.paramChild;if(s){r.push(o);const t=this.l(s,e,n+1,r);if(t)return t;r.pop()}return null}},R=["GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD"],H=["text/","application/json","application/xml","application/javascript","application/xhtml","application/ld+json","application/manifest+json","image/svg"];function I(t){if(!t)return!0;const e=t.toLowerCase();for(let t=0;t<H.length;t++)if(e.startsWith(H[t]))return!0;return!1}var D=new Map,N=0;function L(t,e,n){const r=t+"|"+(e||"")+"|"+(n||""),o=Date.now(),i=D.get(r);if(i&&o-i<6e4)return!1;if(D.set(r,o),o-N>3e4){N=o;for(const[t,e]of D)o-e>6e4&&D.delete(t)}return!0}var Edge=class{constructor(){this.staticRoutes=new Map;for(let t=0;t<R.length;t++)this.staticRoutes.set(R[t],new Map);this.dynamicTries={};for(let t=0;t<R.length;t++)this.dynamicTries[R[t]]=new P;this.defaults={cors:{origin:"*",methods:"GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD"}},this.scheduledHandler=null,this.security={logSecurityEvents:!0,extraHeaders:null,trustedProxies:null},this.notFoundHandler=null,this.errorHandler=null}u(t,e,n,r){if("boolean"==typeof n?n={}:"function"==typeof n&&(r=n,n={}),"function"!=typeof r)throw new Error("Handler for "+t+" "+e+" must be a function");const o={...this.defaults,...n||{}};-1===e.indexOf(":")?this.staticRoutes.get(t).set(l(e),{handler:r,options:o}):this.dynamicTries[t].add(a(e),r,o)}get(t,e,n){this.u("GET",t,e,n)}post(t,e,n){this.u("POST",t,e,n)}put(t,e,n){this.u("PUT",t,e,n)}delete(t,e,n){this.u("DELETE",t,e,n)}patch(t,e,n){this.u("PATCH",t,e,n)}options(t,e,n){this.u("OPTIONS",t,e,n)}head(t,e,n){this.u("HEAD",t,e,n)}scheduled(t){this.scheduledHandler=t}notFound(t){if("function"!=typeof t)throw new Error("notFound(handler) requires a function");this.notFoundHandler=t}onError(t){if("function"!=typeof t)throw new Error("onError(handler) requires a function");this.errorHandler=t}h(t,e){const n=new Headers(e.headers);t.headers.forEach((t,e)=>{"set-cookie"!==e.toLowerCase()&&(n.has(e)||n.set(e,t))});const r="function"==typeof t.headers.getSetCookie?t.headers.getSetCookie():t.headers.get("Set-Cookie")?[t.headers.get("Set-Cookie")]:[];if(r.length){const t="function"==typeof n.getSetCookie?n.getSetCookie():n.get("Set-Cookie")?[n.get("Set-Cookie")]:[],e=new Set(t);for(let t=0;t<r.length;t++){const o=r[t];e.has(o)||(n.append("Set-Cookie",o),e.add(o))}}const o=204===e.status||304===e.status?null:e.body;return new Response(o,{status:e.status,statusText:e.statusText,headers:n})}p(t,e,n){if(this.security.logSecurityEvents)try{const r=t.req.headers.get("CF-Connecting-IP")||"",o=t.url?t.url.pathname:"";if(!L(e,r,o))return;console.log(JSON.stringify({ts:(new Date).toISOString(),kind:"security",event:e,detail:"string"==typeof n?n:void 0,ip:r||void 0,method:t.req.method,path:o,ua:t.req.headers.get("User-Agent")||void 0}))}catch{}}m(t,e){if(!e)return;const n="object"==typeof e?e:this.defaults.cors,r=t.req.headers.get("Origin"),o=n.origin;"*"===o?t.headers.set("Access-Control-Allow-Origin","*"):Array.isArray(o)?r&&-1!==o.indexOf(r)&&(t.headers.set("Access-Control-Allow-Origin",r),t.headers.set("Vary","Origin"),t.headers.set("Access-Control-Allow-Credentials","true")):"string"==typeof o&&r===o&&(t.headers.set("Access-Control-Allow-Origin",r),t.headers.set("Vary","Origin"),t.headers.set("Access-Control-Allow-Credentials","true")),t.headers.set("Access-Control-Allow-Methods",n.methods||"GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD"),t.headers.set("Access-Control-Allow-Headers",n.headers||"Content-Type, Authorization, X-CSRF-Token"),t.headers.set("Access-Control-Max-Age","86400")}C(t,e){e&&console.log(t.req.method+" "+t.req.url+" - "+t.status)}T(t,e){if(!I(e.headers.get("Content-Type")))return e;const n=e.headers.get("Content-Length");if(null!==n&&+n<1024)return e;const r=t.headers.get("Accept-Encoding")||"";let o=null;if(-1!==r.indexOf("gzip")?o="gzip":-1!==r.indexOf("deflate")&&(o="deflate"),!o||!e.body)return e;const i=e.body.pipeThrough(new CompressionStream(o)),s=new Headers(e.headers);return s.set("Content-Encoding",o),s.set("Vary","Accept-Encoding"),s.delete("Content-Length"),new Response(i,{status:e.status,statusText:e.statusText,headers:s})}async S(t,e){return!e||"function"!=typeof e||!!await e(t)}async O(t){return await caches.default.match(t)||null}async v(t,e,n){if(!n)return;const r=!0===n?{}:n,o=r.ttl??3600,i=r.staleWhileRevalidate??0,s=new Headers(e.headers);s.set("Cache-Control","max-age="+o+(i>0?", stale-while-revalidate="+i:"")),s.delete("Set-Cookie"),await caches.default.put(t,new Response(e.body,{status:e.status,statusText:e.statusText,headers:s}))}async fetch(t,e,n){const r=new URL(t.url),o=l(r.pathname),i=t.method,s=this.staticRoutes.get(i);if(s){const i=s.get(o);if(i)return this.A(i.handler,i.options,t,e,n,Object.create(null),r)}const c=this.dynamicTries[i];if(c){const i=c.match(a(o));if(i)return this.A(i.handler,i.options,t,e,n,i.params,r)}if("OPTIONS"===i){const o=new k(t,e,n,Object.create(null),r);return this.h(o,new Response(null,{status:204}))}if(this.notFoundHandler)return this.A(this.notFoundHandler,{},t,e,n,Object.create(null),r);const u=new k(t,e,n,Object.create(null),r);return this.h(u,new Response("Not Found",{status:404}))}async j(t,e){const n=this.errorHandler;if(!n)return t.text("Internal Server Error",500);try{const r=await n(t,e);return r instanceof Response?this.h(t,r):r&&r.__isJSX||Array.isArray(r)?t.html(r,500):t.text("Internal Server Error",500)}catch(e){return this.p(t,"error_handler_failure",e&&e.message),t.text("Internal Server Error",500)}}async A(t,e,n,r,o,i,s){const c=new k(n,r,o,i,s),a=this.security.extraHeaders;if(a)for(const t in a)Object.prototype.hasOwnProperty.call(a,t)&&c.headers.set(t,a[t]);let l,u=!1;try{u=await this.S(c,e.validate)}catch(t){this.p(c,"validation_error",t&&t.message),u=!1}if(!u)return this.h(c,c.text("Validation failed",400));if(e.cache&&"GET"===n.method){const t=await this.O(n);if(t)return this.m(c,e.cors),this.C(c,e.log),this.h(c,t)}this.m(c,e.cors);try{const e=await t(c);l=e instanceof Response?this.h(c,e):e&&e.__isJSX?await c.html(e):c.text("OK")}catch(t){this.p(c,"handler_error",t&&t.message),l=await this.j(c,t)}return e.cache&&"GET"===n.method&&200===l.status&&c.executionCtx.waitUntil(this.v(n,l.clone(),e.cache)),e.compress&&(l=this.T(n,l)),this.C(c,e.log),l}};
|
|
33
|
+
var t=Object.defineProperty,e=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,r=Object.prototype.hasOwnProperty,o=(e,n)=>{for(var r in n)t(e,r,{get:n[r],enumerable:!0})},s=(o,s,i,c)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let a of n(s))r.call(o,a)||a===i||t(o,a,{get:()=>s[a],enumerable:!(c=e(s,a))||c.enumerable});return o},i=e=>s(t({},"__esModule",{value:!0}),e),c={};function a(t){const e=[],n=t.length;let r=1;for(let o=1;o<=n;o++)o!==n&&47!==t.charCodeAt(o)||(o>r&&e.push(t.slice(r,o)),r=o+1);return e}function l(t){const e=t.length;return e>1&&47===t.charCodeAt(e-1)?t.slice(0,-1):t}function u(t,e){if(!e)return t||"/";if(!t||"/"===t)return 47===e.charCodeAt(0)?e:"/"+e;const n=t.charCodeAt(t.length-1),r=e.charCodeAt(0);let o;o=47===n?47===r?t+e.slice(1):t+e:47===r?t+e:t+"/"+e;const s=o.length;return s>1&&47===o.charCodeAt(s-1)?o.slice(0,-1):o}o(c,{Context:()=>Context,Edge:()=>Edge,Fragment:()=>Fragment,default:()=>H,jsx:()=>jsx,renderToString:()=>renderToString,useCtx:()=>useCtx}),module.exports=i(c);var f=new Set(["__proto__","constructor","prototype"]);function h(t){return"string"==typeof t&&!f.has(t)}function p(t){const e=t.length;let n=0;for(;n<e;){const e=t.charCodeAt(n);if(38===e||60===e||62===e||34===e||39===e)break;n++}if(n===e)return t;let r=t.slice(0,n),o=n;for(;n<e;n++){const e=t.charCodeAt(n);let s;if(38===e)s="&";else if(60===e)s="<";else if(62===e)s=">";else if(34===e)s=""";else{if(39!==e)continue;s="'"}n>o&&(r+=t.slice(o,n)),r+=s,o=n+1}return o<e&&(r+=t.slice(o)),r}function d(t){return t>=65&&t<=90||t>=97&&t<=122}function y(t){return t>=48&&t<=57}function w(t){const e=t.length;if(0===e||!d(t.charCodeAt(0)))return!1;for(let n=1;n<e;n++){const e=t.charCodeAt(n);if(!d(e)&&!y(e)&&45!==e)return!1}return!0}function m(t){const e=t.length;if(0===e)return!1;const n=t.charCodeAt(0);if(!d(n)&&95!==n&&58!==n)return!1;for(let n=1;n<e;n++){const e=t.charCodeAt(n);if(!d(e)&&!y(e)&&95!==e&&58!==e&&46!==e&&45!==e)return!1}return!0}function g(t){const e=t.charCodeAt(0),n=t.charCodeAt(1);return!(111!==e&&79!==e||110!==n&&78!==n)}var Fragment=Symbol("Fragment");function jsx(t,e,...n){return{type:t,props:e||{},children:n,__isJSX:!0}}var b=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),S=new Set(["allowfullscreen","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","inert","ismap","itemscope","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]),C=new Set(["animation-iteration-count","border-image-outset","border-image-slice","border-image-width","box-flex","box-flex-group","box-ordinal-group","column-count","columns","flex","flex-grow","flex-positive","flex-shrink","flex-negative","flex-order","grid-row","grid-row-end","grid-row-span","grid-row-start","grid-column","grid-column-end","grid-column-span","grid-column-start","font-weight","line-clamp","line-height","opacity","order","orphans","tab-size","widows","z-index","zoom","fill-opacity","flood-opacity","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-miterlimit","stroke-opacity","stroke-width"]),O=new Map,x=2e3;function T(t){const e=O.get(t);if(void 0!==e)return e;const n=t.replace(/([A-Z])/g,"-$1").toLowerCase().replace(/^-/,"");return O.size>=x&&O.clear(),O.set(t,n),n}function E(t){if(!t||"object"!=typeof t)return"";let e="";for(const n in t){if(!Object.prototype.hasOwnProperty.call(t,n))continue;const r=T(n);let o=t[n];"number"!=typeof o||C.has(r)||(o+="px"),e&&(e+=";"),e+=r+":"+o}return e}var k=new Set(["href","src","xlink:href","action","formaction"]),j=[];function useCtx(){const t=j[j.length-1];if(!t)throw new Error("useCtx() must be called synchronously at the top of a component (before any await). It is not available after an await, nor outside a render.");return t}async function renderToString(t,e){if(void 0===e&&(e=j[j.length-1]),null==t||"boolean"==typeof t)return"";const n=typeof t;if("string"===n)return p(t);if("number"===n)return p(String(t));if("object"===n&&"function"==typeof t.then)return renderToString(await t,e);if(Array.isArray(t)){let n="";for(let r=0;r<t.length;r++)n+=await renderToString(t[r],e);return n}if(!t.__isJSX)return p(String(t));const{type:r,props:o,children:s}=t;if(r===Fragment){let t="";for(let n=0;n<s.length;n++)t+=await renderToString(s[n],e);return t}if("symbol"==typeof r)return"";if("function"==typeof r){let t;if(e){j.push(e);try{t=r({...o,children:s})}finally{j.pop()}}else t=r({...o,children:s});return renderToString(await t,e)}if("string"!=typeof r||!w(r))return"";const i=b.has(r);let c="";for(const t in o){if(!Object.prototype.hasOwnProperty.call(o,t))continue;if("children"===t||"key"===t||"ref"===t)continue;const e=o[t];if(null==e||!1===e)continue;if("dangerouslySetInnerHTML"===t)continue;let n=t;if("className"===t||"class"===t?n="class":"htmlFor"!==t&&"for"!==t||(n="for"),h(n)&&!g(n)&&m(n)){if(k.has(n)){const t=String(e).replace(/[\u0000-\u0020]/g,"").toLowerCase();if(t.startsWith("javascript:")||t.startsWith("vbscript:")||t.startsWith("data:text/html"))continue}if(S.has(n))!0===e&&(c+=" "+n);else if(!0!==e){if("style"===n&&"object"==typeof e){const t=E(e);t&&(c+=' style="'+p(t)+'"');continue}c+=" "+n+'="'+p(String(e))+'"'}else c+=" "+n+'="true"'}}let a="";const l=o.dangerouslySetInnerHTML;if(l&&null!=l.__html)a=l.__html;else for(let t=0;t<s.length;t++)a+=await renderToString(s[t],e);return i?"<"+r+c+"/>":"<"+r+c+">"+a+"</"+r+">"}var v=Object.freeze([["X-Content-Type-Options","nosniff"],["X-Frame-Options","DENY"],["Referrer-Policy","strict-origin-when-cross-origin"],["Permissions-Policy","geolocation=(), microphone=(), camera=()"]]),R=Object.freeze(Object.create(null)),Context=class{constructor(t,e,n,r={},o=null){this.req=t,this.env=e,this.params=r,this.status=200,this.headers=new Headers(v),this.t=null,this.url=o||new URL(t.url)}o(){if(null!==this.t)return this.t;const t=this.req.headers.get("Cookie");if(!t)return this.t=R,R;const e=Object.create(null);let n=0;const r=t.length;for(;n<r;){let o=t.indexOf(";",n);-1===o&&(o=r);let s=n,i=o;for(;s<i&&32===t.charCodeAt(s);)s++;for(;i>s&&32===t.charCodeAt(i-1);)i--;if(i>s){const n=t.indexOf("=",s);if(n>s&&n<i)try{const r=decodeURIComponent(t.slice(s,n));h(r)&&(e[r]=decodeURIComponent(t.slice(n+1,i)))}catch{}}n=o+1}return this.t=e,e}getCookie(t){const e=this.o()[t];return void 0===e?null:e}get query(){return this.url.searchParams}setCookie(t,e,n={}){if("string"!=typeof t||!h(t)||/[\r\n;=]/.test(t))return;let r=encodeURIComponent(t)+"="+encodeURIComponent(e);n.path&&(r+="; Path="+n.path),n.domain&&(r+="; Domain="+n.domain),void 0!==n.maxAge&&(r+="; Max-Age="+n.maxAge),n.expires&&(r+="; Expires="+n.expires.toUTCString()),n.secure&&(r+="; Secure"),n.httpOnly&&(r+="; HttpOnly"),n.sameSite&&(r+="; SameSite="+n.sameSite),this.headers.append("Set-Cookie",r)}deleteCookie(t,e={}){this.setCookie(t,"",{...e,maxAge:0,expires:new Date(0)})}i(t){if(!t)return this.headers;const e=Object.keys(t);if(0===e.length)return this.headers;const n=new Headers(this.headers);for(let r=0;r<e.length;r++)n.set(e[r],t[e[r]]);return n}text(t,e=this.status,n={}){const r=this.i(n);return r.has("Content-Type")||r.set("Content-Type","text/plain; charset=utf-8"),new Response(t,{status:e,headers:r})}json(t,e=this.status,n={}){const r=this.i(n);return r.has("Content-Type")||r.set("Content-Type","application/json; charset=utf-8"),new Response(JSON.stringify(t),{status:e,headers:r})}async html(t,e=this.status,n={}){const r=this.i(n);let o;return r.has("Content-Type")||r.set("Content-Type","text/html; charset=utf-8"),o="string"==typeof t?t:t&&t.__isJSX||Array.isArray(t)?await renderToString(t,this):null==t?"":String(t),new Response(o,{status:e,headers:r})}async page(t,e=this.status,n={}){const r=this.i(n);let o;return r.has("Content-Type")||r.set("Content-Type","text/html; charset=utf-8"),o="string"==typeof t?t:t&&t.__isJSX||Array.isArray(t)?await renderToString(t,this):null==t?"":String(t),/^\s*<!doctype\s/i.test(o)||(o="<!DOCTYPE html>"+o),new Response(o,{status:e,headers:r})}redirect(t,e=302){const n=new Headers(this.headers);return n.set("Location",String(t)),n.delete("Content-Type"),new Response(null,{status:e,headers:n})}},_=class{constructor(){this.children=null,this.paramChild=null,this.handler=null}},P=class{constructor(){this.root=new _}add(t,e){let n=this.root;const r=[];for(let e=0;e<t.length;e++){const o=t[e];if(58===o.charCodeAt(0)){const t=o.slice(1);r.push(h(t)?t:"_p"+e),n.paramChild||(n.paramChild=new _),n=n.paramChild}else{n.children||(n.children=new Map);let t=n.children.get(o);t||(t=new _,n.children.set(o,t)),n=t}}n.handler={handler:e,paramNames:r}}match(t){const e=[],n=this.l(this.root,t,0,e);if(!n)return null;const{handler:r,paramNames:o}=n,s=Object.create(null);for(let t=0;t<o.length;t++)s[o[t]]=e[t];return{handler:r,params:s}}l(t,e,n,r){if(n===e.length)return t.handler;const o=e[n],s=t.children;if(s){const t=s.get(o);if(t){const o=this.l(t,e,n+1,r);if(o)return o}}const i=t.paramChild;if(i){r.push(o);const t=this.l(i,e,n+1,r);if(t)return t;r.pop()}return null}},A=["GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD"],Edge=class t{constructor(t=""){this.prefix=t&&"/"!==t?l(t):"",this.staticRoutes=new Map;for(let t=0;t<A.length;t++)this.staticRoutes.set(A[t],new Map);this.dynamicTries={};for(let t=0;t<A.length;t++)this.dynamicTries[A[t]]=new P;this.u=[],this.notFoundHandler=null,this.errorHandler=null}set routes(e){const n=Array.isArray(e)?e:e?[e]:[];for(let e=0;e<n.length;e++){const r=n[e];if(!(r instanceof t))throw new Error("app.routes entries must be Edge instances");this.h(r)}}h(t){const e=t.u,n=this.prefix;for(let t=0;t<e.length;t++){const r=e[t],o=u(n,r.path);this.u.push({method:r.method,path:o,handler:r.handler}),this.p(r.method,o,r.handler)}}m(t,e,n,r){if("function"==typeof n&&(r=n),"function"!=typeof r)throw new Error("Handler for "+t+" "+e+" must be a function");const o=u(this.prefix,e);this.u.push({method:t,path:o,handler:r}),this.p(t,o,r)}p(t,e,n){-1===e.indexOf(":")?this.staticRoutes.get(t).set(e,n):this.dynamicTries[t].add(a(e),n)}get(t,e,n){this.m("GET",t,e,n)}post(t,e,n){this.m("POST",t,e,n)}put(t,e,n){this.m("PUT",t,e,n)}delete(t,e,n){this.m("DELETE",t,e,n)}patch(t,e,n){this.m("PATCH",t,e,n)}options(t,e,n){this.m("OPTIONS",t,e,n)}head(t,e,n){this.m("HEAD",t,e,n)}scheduled(t){this.scheduledHandler=t}notFound(t){if("function"!=typeof t)throw new Error("notFound(handler) requires a function");this.notFoundHandler=t}onError(t){if("function"!=typeof t)throw new Error("onError(handler) requires a function");this.errorHandler=t}S(t,e){const n=new Headers(e.headers);t.headers.forEach((t,e)=>{"set-cookie"!==e.toLowerCase()&&(n.has(e)||n.set(e,t))});const r="function"==typeof t.headers.getSetCookie?t.headers.getSetCookie():t.headers.get("Set-Cookie")?[t.headers.get("Set-Cookie")]:[];if(r.length){const t="function"==typeof n.getSetCookie?n.getSetCookie():n.get("Set-Cookie")?[n.get("Set-Cookie")]:[],e=new Set(t);for(let t=0;t<r.length;t++){const o=r[t];e.has(o)||(n.append("Set-Cookie",o),e.add(o))}}const o=204===e.status||304===e.status?null:e.body;return new Response(o,{status:e.status,statusText:e.statusText,headers:n})}async fetch(t,e,n){const r=new URL(t.url),o=l(r.pathname),s=t.method,i=this.staticRoutes.get(s);if(i){const s=i.get(o);if(s)return this.C(s,t,e,n,Object.create(null),r)}const c=this.dynamicTries[s];if(c){const s=c.match(a(o));if(s)return this.C(s.handler,t,e,n,s.params,r)}if("OPTIONS"===s){const o=new Context(t,e,n,Object.create(null),r);return this.S(o,new Response(null,{status:204}))}if(this.notFoundHandler)return this.C(this.notFoundHandler,t,e,n,Object.create(null),r);const u=new Context(t,e,n,Object.create(null),r);return this.S(u,new Response("Not Found",{status:404}))}async O(t,e){const n=this.errorHandler;if(!n)return t.text("Internal Server Error",500);try{const r=await n(t,e);return r instanceof Response?this.S(t,r):r&&r.__isJSX||Array.isArray(r)?t.html(r,500):t.text("Internal Server Error",500)}catch{return t.text("Internal Server Error",500)}}async C(t,e,n,r,o,s){const i=new Context(e,n,r,o,s);let c;try{const e=await t(i);c=e instanceof Response?this.S(i,e):e&&e.__isJSX?await i.html(e):i.text("OK")}catch(t){c=await this.O(i,t)}return c}},H=Edge;
|