@vesk/runtime 0.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.
- package/README.md +23 -0
- package/dist/action.d.ts +43 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +84 -0
- package/dist/bindings.d.ts +4 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +137 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +34 -0
- package/dist/experiment.d.ts +17 -0
- package/dist/experiment.d.ts.map +1 -0
- package/dist/experiment.js +71 -0
- package/dist/form.d.ts +36 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +278 -0
- package/dist/hmr-client.d.ts +1 -0
- package/dist/hmr-client.d.ts.map +1 -0
- package/dist/hmr-client.js +273 -0
- package/dist/hydrate.d.ts +34 -0
- package/dist/hydrate.d.ts.map +1 -0
- package/dist/hydrate.js +248 -0
- package/dist/image.d.ts +19 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +51 -0
- package/dist/index-client.d.ts +16 -0
- package/dist/index-client.d.ts.map +1 -0
- package/dist/index-client.js +15 -0
- package/dist/index-server.d.ts +15 -0
- package/dist/index-server.d.ts.map +1 -0
- package/dist/index-server.js +14 -0
- package/dist/isr.d.ts +27 -0
- package/dist/isr.d.ts.map +1 -0
- package/dist/isr.js +143 -0
- package/dist/md.d.ts +17 -0
- package/dist/md.d.ts.map +1 -0
- package/dist/md.js +507 -0
- package/dist/package.json +23 -0
- package/dist/portal.d.ts +8 -0
- package/dist/portal.d.ts.map +1 -0
- package/dist/portal.js +20 -0
- package/dist/reconcile.d.ts +3 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +66 -0
- package/dist/request.d.ts +188 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +624 -0
- package/dist/resource.d.ts +47 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +502 -0
- package/dist/ripple-blocks.d.ts +18 -0
- package/dist/ripple-blocks.d.ts.map +1 -0
- package/dist/ripple-blocks.js +184 -0
- package/dist/ripple-constants.d.ts +42 -0
- package/dist/ripple-constants.d.ts.map +1 -0
- package/dist/ripple-constants.js +41 -0
- package/dist/ripple-runtime.d.ts +125 -0
- package/dist/ripple-runtime.d.ts.map +1 -0
- package/dist/ripple-runtime.js +891 -0
- package/dist/ripple-utils.d.ts +12 -0
- package/dist/ripple-utils.d.ts.map +1 -0
- package/dist/ripple-utils.js +10 -0
- package/dist/router-components.d.ts +83 -0
- package/dist/router-components.d.ts.map +1 -0
- package/dist/router-components.js +320 -0
- package/dist/router-match.d.ts +40 -0
- package/dist/router-match.d.ts.map +1 -0
- package/dist/router-match.js +158 -0
- package/dist/router.d.ts +56 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +1330 -0
- package/dist/seo.d.ts +94 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +138 -0
- package/dist/suspense.d.ts +1 -0
- package/dist/suspense.d.ts.map +1 -0
- package/dist/suspense.js +14 -0
- package/dist/track.d.ts +20 -0
- package/dist/track.d.ts.map +1 -0
- package/dist/track.js +124 -0
- package/package.json +55 -0
package/dist/request.js
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
function getRequest() {
|
|
2
|
+
return globalThis.__vesk_request || null;
|
|
3
|
+
}
|
|
4
|
+
function getHooks() {
|
|
5
|
+
if (!globalThis.__vesk_hooks)
|
|
6
|
+
globalThis.__vesk_hooks = {};
|
|
7
|
+
return globalThis.__vesk_hooks;
|
|
8
|
+
}
|
|
9
|
+
export function defineHook(name, fn) {
|
|
10
|
+
const hooks = getHooks();
|
|
11
|
+
if (!hooks[name])
|
|
12
|
+
hooks[name] = [];
|
|
13
|
+
hooks[name].push(fn);
|
|
14
|
+
}
|
|
15
|
+
export function removeHook(name, fn) {
|
|
16
|
+
const hooks = getHooks();
|
|
17
|
+
if (!hooks[name])
|
|
18
|
+
return;
|
|
19
|
+
hooks[name] = hooks[name].filter(h => h !== fn);
|
|
20
|
+
}
|
|
21
|
+
export async function runHooks(name, ...args) {
|
|
22
|
+
const hooks = getHooks()[name] || [];
|
|
23
|
+
for (const fn of hooks) {
|
|
24
|
+
const result = await fn(...args);
|
|
25
|
+
if (result instanceof Response)
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function useParams() {
|
|
30
|
+
const ctx = getRequest();
|
|
31
|
+
return ctx?.params || {};
|
|
32
|
+
}
|
|
33
|
+
export function useRequest() {
|
|
34
|
+
return getRequest();
|
|
35
|
+
}
|
|
36
|
+
export async function useBody() {
|
|
37
|
+
const ctx = getRequest();
|
|
38
|
+
if (!ctx)
|
|
39
|
+
throw new Error('useBody() called outside request context');
|
|
40
|
+
if (ctx._parsedBody !== undefined)
|
|
41
|
+
return ctx._parsedBody;
|
|
42
|
+
const req = ctx._request;
|
|
43
|
+
if (!req)
|
|
44
|
+
return null;
|
|
45
|
+
const ct = (req.headers.get && req.headers.get('content-type')) || '';
|
|
46
|
+
try {
|
|
47
|
+
if (ct.includes('json')) {
|
|
48
|
+
ctx._parsedBody = await req.json();
|
|
49
|
+
}
|
|
50
|
+
else if (ct.includes('x-www-form-urlencoded')) {
|
|
51
|
+
const text = await req.text();
|
|
52
|
+
const obj = {};
|
|
53
|
+
for (const pair of text.split('&')) {
|
|
54
|
+
const [k, v] = pair.split('=').map(s => decodeURIComponent(s || ''));
|
|
55
|
+
if (k)
|
|
56
|
+
obj[k] = v;
|
|
57
|
+
}
|
|
58
|
+
ctx._parsedBody = obj;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
ctx._parsedBody = await req.text();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
ctx._parsedBody = null;
|
|
66
|
+
}
|
|
67
|
+
return ctx._parsedBody;
|
|
68
|
+
}
|
|
69
|
+
export function cors(options = {}) {
|
|
70
|
+
const { origin = '*', methods = 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS', allowedHeaders = 'Content-Type, Authorization', credentials = true, maxAge = 86400, exposeHeaders, } = options;
|
|
71
|
+
const corsHeaders = {
|
|
72
|
+
'Access-Control-Allow-Origin': origin,
|
|
73
|
+
'Access-Control-Allow-Methods': methods,
|
|
74
|
+
'Access-Control-Allow-Headers': allowedHeaders,
|
|
75
|
+
'Access-Control-Max-Age': String(maxAge),
|
|
76
|
+
};
|
|
77
|
+
if (credentials)
|
|
78
|
+
corsHeaders['Access-Control-Allow-Credentials'] = 'true';
|
|
79
|
+
if (exposeHeaders?.length)
|
|
80
|
+
corsHeaders['Access-Control-Expose-Headers'] = exposeHeaders.join(', ');
|
|
81
|
+
function applyCors(response) {
|
|
82
|
+
const headers = new Headers(response.headers);
|
|
83
|
+
for (const [k, v] of Object.entries(corsHeaders)) {
|
|
84
|
+
headers.set(k, v);
|
|
85
|
+
}
|
|
86
|
+
return new Response(response.body, {
|
|
87
|
+
status: response.status,
|
|
88
|
+
statusText: response.statusText,
|
|
89
|
+
headers,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const middleware = function (request) {
|
|
93
|
+
if (request.method === 'OPTIONS') {
|
|
94
|
+
return new Response(null, {
|
|
95
|
+
status: 204,
|
|
96
|
+
headers: corsHeaders,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
middleware._pending = corsHeaders;
|
|
100
|
+
};
|
|
101
|
+
middleware.applyCors = applyCors;
|
|
102
|
+
middleware._corsHeaders = corsHeaders;
|
|
103
|
+
return middleware;
|
|
104
|
+
}
|
|
105
|
+
export function webhook(options) {
|
|
106
|
+
const { secret, handler, headerName = 'x-webhook-signature', signaturePrefix = 'sha256=', } = options;
|
|
107
|
+
if (!secret)
|
|
108
|
+
throw new Error('webhook() requires a secret');
|
|
109
|
+
if (!handler)
|
|
110
|
+
throw new Error('webhook() requires a handler function');
|
|
111
|
+
async function computeSignature(body) {
|
|
112
|
+
const key = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret;
|
|
113
|
+
const data = typeof body === 'string' ? new TextEncoder().encode(body) : body;
|
|
114
|
+
const crypto = globalThis.crypto;
|
|
115
|
+
if (!crypto?.subtle) {
|
|
116
|
+
throw new Error('Web Crypto API not available (required for webhook signature verification)');
|
|
117
|
+
}
|
|
118
|
+
return crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
|
|
119
|
+
.then(k => crypto.subtle.sign('HMAC', k, data))
|
|
120
|
+
.then(sig => {
|
|
121
|
+
const hex = Array.from(new Uint8Array(sig))
|
|
122
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
123
|
+
.join('');
|
|
124
|
+
return hex;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async function webhookHandler(request) {
|
|
128
|
+
const signature = request.headers.get(headerName);
|
|
129
|
+
if (!signature) {
|
|
130
|
+
return new Response(JSON.stringify({ error: 'Missing signature header' }), {
|
|
131
|
+
status: 401,
|
|
132
|
+
headers: { 'Content-Type': 'application/json' },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const body = await request.text();
|
|
136
|
+
const expectedSig = await computeSignature(body);
|
|
137
|
+
const prefixLen = signaturePrefix.length;
|
|
138
|
+
const providedSig = signature.startsWith(signaturePrefix)
|
|
139
|
+
? signature.slice(prefixLen)
|
|
140
|
+
: signature;
|
|
141
|
+
if (providedSig.length !== expectedSig.length) {
|
|
142
|
+
return new Response(JSON.stringify({ error: 'Invalid signature' }), {
|
|
143
|
+
status: 401,
|
|
144
|
+
headers: { 'Content-Type': 'application/json' },
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
let match = true;
|
|
148
|
+
for (let i = 0; i < expectedSig.length; i++) {
|
|
149
|
+
if (providedSig[i] !== expectedSig[i])
|
|
150
|
+
match = false;
|
|
151
|
+
}
|
|
152
|
+
if (!match) {
|
|
153
|
+
return new Response(JSON.stringify({ error: 'Invalid signature' }), {
|
|
154
|
+
status: 401,
|
|
155
|
+
headers: { 'Content-Type': 'application/json' },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
let event;
|
|
159
|
+
try {
|
|
160
|
+
event = JSON.parse(body);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
event = body;
|
|
164
|
+
}
|
|
165
|
+
return handler(event, request);
|
|
166
|
+
}
|
|
167
|
+
return webhookHandler;
|
|
168
|
+
}
|
|
169
|
+
export function cookies() {
|
|
170
|
+
const req = getRequest();
|
|
171
|
+
let jar = {};
|
|
172
|
+
if (req && req.cookies) {
|
|
173
|
+
jar = { ...req.cookies };
|
|
174
|
+
}
|
|
175
|
+
else if (typeof document !== 'undefined') {
|
|
176
|
+
for (const pair of document.cookie.split(';')) {
|
|
177
|
+
const eq = pair.indexOf('=');
|
|
178
|
+
if (eq === -1)
|
|
179
|
+
continue;
|
|
180
|
+
const k = pair.slice(0, eq).trim();
|
|
181
|
+
const v = pair.slice(eq + 1).trim();
|
|
182
|
+
if (k)
|
|
183
|
+
jar[k] = decodeURIComponent(v);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return new Proxy(jar, {
|
|
187
|
+
get(target, prop) {
|
|
188
|
+
if (prop === 'get')
|
|
189
|
+
return (name) => target[name] || undefined;
|
|
190
|
+
if (prop === 'getAll')
|
|
191
|
+
return () => Object.entries(target).map(([name, value]) => ({ name, value }));
|
|
192
|
+
if (prop === 'toString')
|
|
193
|
+
return () => Object.entries(target).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
194
|
+
if (prop in target)
|
|
195
|
+
return target[prop];
|
|
196
|
+
return undefined;
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
export function locals() {
|
|
201
|
+
const req = getRequest();
|
|
202
|
+
if (req && req.locals)
|
|
203
|
+
return req.locals;
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
export function headers() {
|
|
207
|
+
const req = getRequest();
|
|
208
|
+
const map = new Map();
|
|
209
|
+
if (req && req.headers) {
|
|
210
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
211
|
+
map.set(k.toLowerCase(), Array.isArray(v) ? v.join(', ') : String(v));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return new Proxy({}, {
|
|
215
|
+
get(_target, prop) {
|
|
216
|
+
if (prop === 'get')
|
|
217
|
+
return (name) => map.get(name.toLowerCase()) || null;
|
|
218
|
+
if (prop === 'has')
|
|
219
|
+
return (name) => map.has(name.toLowerCase());
|
|
220
|
+
if (prop === 'entries')
|
|
221
|
+
return () => map.entries();
|
|
222
|
+
if (typeof prop === 'string')
|
|
223
|
+
return map.get(prop.toLowerCase()) ?? undefined;
|
|
224
|
+
return undefined;
|
|
225
|
+
},
|
|
226
|
+
ownKeys() {
|
|
227
|
+
return [...map.keys()];
|
|
228
|
+
},
|
|
229
|
+
getOwnPropertyDescriptor() {
|
|
230
|
+
return { enumerable: true, configurable: true };
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export class ServerRequest extends Request {
|
|
235
|
+
_cookies;
|
|
236
|
+
_params;
|
|
237
|
+
_locals;
|
|
238
|
+
constructor(input, init) {
|
|
239
|
+
super(input, init);
|
|
240
|
+
this._cookies = {};
|
|
241
|
+
this._params = {};
|
|
242
|
+
this._locals = {};
|
|
243
|
+
}
|
|
244
|
+
get cookies() {
|
|
245
|
+
const target = this._cookies;
|
|
246
|
+
return new Proxy(target, {
|
|
247
|
+
get(t, prop) {
|
|
248
|
+
if (prop === 'get')
|
|
249
|
+
return (name) => t[name] || undefined;
|
|
250
|
+
if (prop === 'getAll')
|
|
251
|
+
return () => Object.entries(t).map(([name, value]) => ({ name, value }));
|
|
252
|
+
if (prop === 'toString')
|
|
253
|
+
return () => Object.entries(t).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
254
|
+
if (prop in t)
|
|
255
|
+
return t[prop];
|
|
256
|
+
return undefined;
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
set cookies(v) { this._cookies = v; }
|
|
261
|
+
get params() { return this._params; }
|
|
262
|
+
set params(v) { this._params = v; }
|
|
263
|
+
get locals() { return this._locals; }
|
|
264
|
+
set locals(v) { this._locals = v; }
|
|
265
|
+
}
|
|
266
|
+
export class ServerResponse extends Response {
|
|
267
|
+
static json(body, init) {
|
|
268
|
+
return new ServerResponse(JSON.stringify(body), {
|
|
269
|
+
...init,
|
|
270
|
+
headers: { 'Content-Type': 'application/json', ...init?.headers },
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
static redirect(url, status = 307) {
|
|
274
|
+
return new ServerResponse(null, {
|
|
275
|
+
status,
|
|
276
|
+
headers: { Location: url },
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
static rewrite(url) {
|
|
280
|
+
return new ServerResponse(null, {
|
|
281
|
+
status: 200,
|
|
282
|
+
headers: { 'x-vesk-rewrite': url },
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
static next() {
|
|
286
|
+
return new ServerResponse(null, {
|
|
287
|
+
status: 200,
|
|
288
|
+
headers: { 'x-vesk-next': '1' },
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export class VeskRequest extends ServerRequest {
|
|
293
|
+
_query;
|
|
294
|
+
_ip;
|
|
295
|
+
_protocol;
|
|
296
|
+
_hostname;
|
|
297
|
+
_body;
|
|
298
|
+
_bodyPromise;
|
|
299
|
+
_parsedUrl;
|
|
300
|
+
__security;
|
|
301
|
+
constructor(input, init) {
|
|
302
|
+
super(input, init);
|
|
303
|
+
this._query = null;
|
|
304
|
+
this._ip = null;
|
|
305
|
+
this._protocol = null;
|
|
306
|
+
this._hostname = null;
|
|
307
|
+
this._body = null;
|
|
308
|
+
this._bodyPromise = null;
|
|
309
|
+
this._parsedUrl = null;
|
|
310
|
+
Object.defineProperty(this, 'body', {
|
|
311
|
+
get: () => {
|
|
312
|
+
if (!this._bodyPromise) {
|
|
313
|
+
this._bodyPromise = this._parseBody();
|
|
314
|
+
}
|
|
315
|
+
return this._bodyPromise;
|
|
316
|
+
},
|
|
317
|
+
configurable: true,
|
|
318
|
+
enumerable: true,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
get parsedUrl() {
|
|
322
|
+
if (!this._parsedUrl) {
|
|
323
|
+
this._parsedUrl = new URL(this.url);
|
|
324
|
+
}
|
|
325
|
+
return this._parsedUrl;
|
|
326
|
+
}
|
|
327
|
+
get query() {
|
|
328
|
+
if (!this._query) {
|
|
329
|
+
const params = {};
|
|
330
|
+
for (const [k, v] of this.parsedUrl.searchParams) {
|
|
331
|
+
params[k] = v;
|
|
332
|
+
}
|
|
333
|
+
this._query = params;
|
|
334
|
+
}
|
|
335
|
+
return this._query;
|
|
336
|
+
}
|
|
337
|
+
set query(v) { this._query = v; }
|
|
338
|
+
get ip() {
|
|
339
|
+
if (!this._ip) {
|
|
340
|
+
const fwd = this.headers?.get('x-forwarded-for');
|
|
341
|
+
if (fwd)
|
|
342
|
+
this._ip = fwd.split(',')[0].trim();
|
|
343
|
+
else
|
|
344
|
+
this._ip = 'unknown';
|
|
345
|
+
}
|
|
346
|
+
return this._ip;
|
|
347
|
+
}
|
|
348
|
+
set ip(v) { this._ip = v; }
|
|
349
|
+
get protocol() {
|
|
350
|
+
if (!this._protocol) {
|
|
351
|
+
const proto = this.headers?.get('x-forwarded-proto');
|
|
352
|
+
this._protocol = proto ? proto.split(',')[0].trim() : 'http';
|
|
353
|
+
}
|
|
354
|
+
return this._protocol;
|
|
355
|
+
}
|
|
356
|
+
set protocol(v) { this._protocol = v; }
|
|
357
|
+
get hostname() {
|
|
358
|
+
if (!this._hostname) {
|
|
359
|
+
this._hostname = this.headers?.get('host')?.split(':')[0] || 'localhost';
|
|
360
|
+
}
|
|
361
|
+
return this._hostname;
|
|
362
|
+
}
|
|
363
|
+
set hostname(v) { this._hostname = v; }
|
|
364
|
+
async _parseBody() {
|
|
365
|
+
const ct = this.headers?.get('content-type') || '';
|
|
366
|
+
if (ct.includes('json')) {
|
|
367
|
+
return await this.json();
|
|
368
|
+
}
|
|
369
|
+
else if (ct.includes('form')) {
|
|
370
|
+
const fd = await this.formData();
|
|
371
|
+
return Object.fromEntries(fd.entries());
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
const text = await this.text();
|
|
375
|
+
try {
|
|
376
|
+
return JSON.parse(text);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return text;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
get _security() {
|
|
384
|
+
if (!this.__security)
|
|
385
|
+
this.__security = {};
|
|
386
|
+
return this.__security;
|
|
387
|
+
}
|
|
388
|
+
setCsp(policy) {
|
|
389
|
+
this._security.contentSecurityPolicy = policy;
|
|
390
|
+
}
|
|
391
|
+
setRateLimit(options) {
|
|
392
|
+
this._security.rateLimit = options;
|
|
393
|
+
}
|
|
394
|
+
setCsrf(enable) {
|
|
395
|
+
this._security.csrf = enable;
|
|
396
|
+
}
|
|
397
|
+
setSecurityHeader(name, value) {
|
|
398
|
+
if (!this._security.customHeaders)
|
|
399
|
+
this._security.customHeaders = {};
|
|
400
|
+
this._security.customHeaders[name] = value;
|
|
401
|
+
}
|
|
402
|
+
setTrustProxy(enable) {
|
|
403
|
+
this._security.trustProxy = enable;
|
|
404
|
+
}
|
|
405
|
+
getSecurityOverrides() {
|
|
406
|
+
return this._security;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
class _VeskResponse extends ServerResponse {
|
|
410
|
+
_cookieHeaders;
|
|
411
|
+
_secHeaders;
|
|
412
|
+
_finalStatus;
|
|
413
|
+
constructor(body, init) {
|
|
414
|
+
super(body, init);
|
|
415
|
+
this._cookieHeaders = [];
|
|
416
|
+
this._secHeaders = {};
|
|
417
|
+
this._finalStatus = null;
|
|
418
|
+
}
|
|
419
|
+
setStatus(code) {
|
|
420
|
+
this._finalStatus = code;
|
|
421
|
+
return this;
|
|
422
|
+
}
|
|
423
|
+
get status() {
|
|
424
|
+
return this._finalStatus !== null ? this._finalStatus : super.status;
|
|
425
|
+
}
|
|
426
|
+
build() {
|
|
427
|
+
this._flushSecurityHeaders();
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
async text() {
|
|
431
|
+
this._flushSecurityHeaders();
|
|
432
|
+
return super.text();
|
|
433
|
+
}
|
|
434
|
+
async json() {
|
|
435
|
+
this._flushSecurityHeaders();
|
|
436
|
+
return super.json();
|
|
437
|
+
}
|
|
438
|
+
setCookie(name, value, opts = {}) {
|
|
439
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
440
|
+
if (opts.httpOnly !== false)
|
|
441
|
+
parts.push('HttpOnly');
|
|
442
|
+
if (opts.secure !== false)
|
|
443
|
+
parts.push('Secure');
|
|
444
|
+
parts.push('SameSite=' + (opts.sameSite || 'Lax'));
|
|
445
|
+
parts.push('Path=' + (opts.path || '/'));
|
|
446
|
+
if (opts.maxAge !== undefined)
|
|
447
|
+
parts.push('Max-Age=' + opts.maxAge);
|
|
448
|
+
if (opts.domain)
|
|
449
|
+
parts.push('Domain=' + opts.domain);
|
|
450
|
+
this._cookieHeaders.push(parts.join('; '));
|
|
451
|
+
return this;
|
|
452
|
+
}
|
|
453
|
+
clearCookie(name, opts = {}) {
|
|
454
|
+
return this.setCookie(name, '', { ...opts, maxAge: 0 });
|
|
455
|
+
}
|
|
456
|
+
setCsp(policy) {
|
|
457
|
+
this._secHeaders['Content-Security-Policy'] = policy === false ? null : policy;
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
setSecurityHeader(name, value) {
|
|
461
|
+
this._secHeaders[name] = value === false ? null : value;
|
|
462
|
+
return this;
|
|
463
|
+
}
|
|
464
|
+
cache(ttlSeconds) {
|
|
465
|
+
this.headers?.set('Cache-Control', `public, max-age=${ttlSeconds}, s-maxage=${ttlSeconds}`);
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
noCache() {
|
|
469
|
+
this.headers?.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
470
|
+
this.headers?.set('Pragma', 'no-cache');
|
|
471
|
+
this.headers?.set('Expires', '0');
|
|
472
|
+
return this;
|
|
473
|
+
}
|
|
474
|
+
cors(opts = {}) {
|
|
475
|
+
this.headers?.set('Access-Control-Allow-Origin', opts.origin || '*');
|
|
476
|
+
if (opts.methods)
|
|
477
|
+
this.headers?.set('Access-Control-Allow-Methods', opts.methods);
|
|
478
|
+
if (opts.headers)
|
|
479
|
+
this.headers?.set('Access-Control-Allow-Headers', opts.headers);
|
|
480
|
+
if (opts.credentials !== false)
|
|
481
|
+
this.headers?.set('Access-Control-Allow-Credentials', 'true');
|
|
482
|
+
return this;
|
|
483
|
+
}
|
|
484
|
+
_flushSecurityHeaders() {
|
|
485
|
+
for (const [name, value] of Object.entries(this._secHeaders)) {
|
|
486
|
+
if (value === null)
|
|
487
|
+
continue;
|
|
488
|
+
const existing = this.headers?.get(name);
|
|
489
|
+
if (!existing)
|
|
490
|
+
this.headers?.set(name, value);
|
|
491
|
+
}
|
|
492
|
+
this._secHeaders = {};
|
|
493
|
+
if (this._cookieHeaders.length > 0) {
|
|
494
|
+
const existing = this.headers?.get('Set-Cookie');
|
|
495
|
+
if (existing) {
|
|
496
|
+
this.headers?.set('Set-Cookie', existing + ', ' + this._cookieHeaders.join(', '));
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
this.headers?.set('Set-Cookie', this._cookieHeaders.join(', '));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
this._cookieHeaders = [];
|
|
503
|
+
}
|
|
504
|
+
static json(body, init) {
|
|
505
|
+
return new _VeskResponse(JSON.stringify(body), {
|
|
506
|
+
...init,
|
|
507
|
+
headers: { 'Content-Type': 'application/json', ...init?.headers },
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
static redirect(url, status = 307) {
|
|
511
|
+
return new _VeskResponse(null, { status, headers: { Location: url } });
|
|
512
|
+
}
|
|
513
|
+
static rewrite(url) {
|
|
514
|
+
return new _VeskResponse(null, { status: 200, headers: { 'x-vesk-rewrite': url } });
|
|
515
|
+
}
|
|
516
|
+
static next() {
|
|
517
|
+
return new _VeskResponse(null, { status: 200, headers: { 'x-vesk-next': '1' } });
|
|
518
|
+
}
|
|
519
|
+
static html(html, init) {
|
|
520
|
+
return new _VeskResponse(html, {
|
|
521
|
+
...init,
|
|
522
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8', ...init?.headers },
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
export const VeskResponse = new Proxy(_VeskResponse, {
|
|
527
|
+
apply(target, _thisArg, args) {
|
|
528
|
+
return new target(...args);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
export function applyRequestSecurity(request, response) {
|
|
532
|
+
if (!request || !response)
|
|
533
|
+
return;
|
|
534
|
+
const overrides = request.getSecurityOverrides?.() || {};
|
|
535
|
+
if (overrides.contentSecurityPolicy !== undefined) {
|
|
536
|
+
response.setCsp(overrides.contentSecurityPolicy);
|
|
537
|
+
}
|
|
538
|
+
if (overrides.customHeaders) {
|
|
539
|
+
for (const [name, value] of Object.entries(overrides.customHeaders)) {
|
|
540
|
+
response.setSecurityHeader(name, value);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
response._flushSecurityHeaders();
|
|
544
|
+
}
|
|
545
|
+
export async function withValidation(request, schema, opts = {}) {
|
|
546
|
+
let data;
|
|
547
|
+
const contentType = request.headers.get('content-type') || '';
|
|
548
|
+
try {
|
|
549
|
+
if (contentType.includes('json')) {
|
|
550
|
+
data = await request.json();
|
|
551
|
+
}
|
|
552
|
+
else if (!opts.jsonOnly) {
|
|
553
|
+
const formData = await request.formData();
|
|
554
|
+
if (formData && typeof formData === 'object') {
|
|
555
|
+
data = Object.fromEntries([...formData.entries()].map(([k, v]) => [k, v]));
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
data = await request.text().then(t => t ? JSON.parse(t) : {});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
data = await request.json();
|
|
563
|
+
}
|
|
564
|
+
const result = schema.safeParse(data);
|
|
565
|
+
if (!result.success) {
|
|
566
|
+
return ServerResponse.json({
|
|
567
|
+
error: 'Validation failed',
|
|
568
|
+
issues: result.error.issues.map(i => ({
|
|
569
|
+
path: i.path.join('.'),
|
|
570
|
+
message: i.message,
|
|
571
|
+
})),
|
|
572
|
+
}, { status: 400 });
|
|
573
|
+
}
|
|
574
|
+
return result.data;
|
|
575
|
+
}
|
|
576
|
+
catch (e) {
|
|
577
|
+
return ServerResponse.json({
|
|
578
|
+
error: 'Invalid request body',
|
|
579
|
+
details: e.message,
|
|
580
|
+
}, { status: 400 });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
let _signImpl = null;
|
|
584
|
+
async function _getImpl() {
|
|
585
|
+
if (_signImpl)
|
|
586
|
+
return _signImpl;
|
|
587
|
+
try {
|
|
588
|
+
_signImpl = await import('@vesk/compiler/src/server-utils');
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
try {
|
|
592
|
+
const mod = await import('@vesk/compiler');
|
|
593
|
+
_signImpl = mod;
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
_signImpl = { signCookie: null, unsignCookie: null, setSignedCookie: null, readSignedCookie: null };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return _signImpl;
|
|
600
|
+
}
|
|
601
|
+
export async function signCookie(name, value, host) {
|
|
602
|
+
const impl = await _getImpl();
|
|
603
|
+
if (impl.signCookie)
|
|
604
|
+
return impl.signCookie(name, value, host);
|
|
605
|
+
throw new Error('signCookie: @vesk/compiler not available');
|
|
606
|
+
}
|
|
607
|
+
export async function unsignCookie(name, signedValue, host) {
|
|
608
|
+
const impl = await _getImpl();
|
|
609
|
+
if (impl.unsignCookie)
|
|
610
|
+
return impl.unsignCookie(name, signedValue, host);
|
|
611
|
+
throw new Error('unsignCookie: @vesk/compiler not available');
|
|
612
|
+
}
|
|
613
|
+
export async function setSignedCookie(name, value, options = {}, host) {
|
|
614
|
+
const impl = await _getImpl();
|
|
615
|
+
if (impl.setSignedCookie)
|
|
616
|
+
return impl.setSignedCookie(name, value, options, host);
|
|
617
|
+
throw new Error('setSignedCookie: @vesk/compiler not available');
|
|
618
|
+
}
|
|
619
|
+
export async function readSignedCookie(name, cookieString, host) {
|
|
620
|
+
const impl = await _getImpl();
|
|
621
|
+
if (impl.readSignedCookie)
|
|
622
|
+
return impl.readSignedCookie(name, cookieString, host);
|
|
623
|
+
throw new Error('readSignedCookie: @vesk/compiler not available');
|
|
624
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Tracked } from '@vesk/runtime/src/ripple-runtime';
|
|
2
|
+
export interface Resource<T> extends PromiseLike<T> {
|
|
3
|
+
loading: boolean;
|
|
4
|
+
error: unknown;
|
|
5
|
+
data: T | undefined;
|
|
6
|
+
_state?: Tracked;
|
|
7
|
+
refresh: () => void;
|
|
8
|
+
abort: () => void;
|
|
9
|
+
}
|
|
10
|
+
export interface UseFetchOptions<T> extends Omit<RequestInit, 'body'> {
|
|
11
|
+
key?: string;
|
|
12
|
+
into?: Tracked;
|
|
13
|
+
body?: unknown;
|
|
14
|
+
staleTime?: number;
|
|
15
|
+
keepPreviousData?: boolean;
|
|
16
|
+
retry?: number;
|
|
17
|
+
retryDelay?: number;
|
|
18
|
+
timeout?: number;
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
dedupe?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare class HttpError extends Error {
|
|
23
|
+
status: number;
|
|
24
|
+
constructor(status: number, statusText: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class TimeoutError extends Error {
|
|
27
|
+
constructor(timeout: number);
|
|
28
|
+
}
|
|
29
|
+
export interface SsrDataSink {
|
|
30
|
+
set(key: string, value: unknown): void;
|
|
31
|
+
get(key: string): unknown | undefined;
|
|
32
|
+
snapshot(): Record<string, unknown>;
|
|
33
|
+
clear(): void;
|
|
34
|
+
}
|
|
35
|
+
export declare function setSsrSink(sink: SsrDataSink | null): void;
|
|
36
|
+
export declare function setSsrData(key: string, value: unknown): void;
|
|
37
|
+
export declare function clearSsrData(): void;
|
|
38
|
+
export declare function mutate(key: string, data?: unknown): void;
|
|
39
|
+
export declare function createResource<T>(fn: () => Promise<T>, key?: string, into?: Tracked, options?: UseFetchOptions<T>): Resource<T>;
|
|
40
|
+
export declare function useFetch<T = unknown>(urlOrFn: string | (() => Promise<T>), options?: UseFetchOptions<T>): Resource<T>;
|
|
41
|
+
export declare namespace useFetch {
|
|
42
|
+
var text: <T = string>(url: string, options?: Omit<UseFetchOptions<T>, "body">) => Resource<T>;
|
|
43
|
+
var json: <T = unknown>(url: string, options?: Omit<UseFetchOptions<T>, "body">) => Resource<T>;
|
|
44
|
+
var arrayBuffer: <T = ArrayBuffer>(url: string, options?: Omit<UseFetchOptions<T>, "body">) => Resource<T>;
|
|
45
|
+
}
|
|
46
|
+
export declare function resolveSsrResources(): Promise<Record<string, unknown>>;
|
|
47
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|