@draftlab/auth 0.14.0 → 0.15.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/dist/adapters/node.d.mts +0 -1
- package/dist/client.d.mts +293 -287
- package/dist/client.mjs +1 -0
- package/dist/core.d.mts +23 -24
- package/dist/core.mjs +6 -6
- package/dist/error.d.mts +53 -53
- package/dist/keys.d.mts +0 -1
- package/dist/mutex.d.mts +14 -14
- package/dist/provider/apple.d.mts +34 -35
- package/dist/provider/code.d.mts +75 -85
- package/dist/provider/code.mjs +83 -0
- package/dist/provider/discord.d.mts +49 -50
- package/dist/provider/facebook.d.mts +49 -50
- package/dist/provider/github.d.mts +50 -51
- package/dist/provider/gitlab.d.mts +34 -35
- package/dist/provider/google.d.mts +49 -50
- package/dist/provider/linkedin.d.mts +47 -48
- package/dist/provider/magiclink.d.mts +28 -38
- package/dist/provider/magiclink.mjs +57 -0
- package/dist/provider/microsoft.d.mts +67 -68
- package/dist/provider/oauth2.d.mts +75 -76
- package/dist/provider/oauth2.mjs +57 -0
- package/dist/provider/passkey.d.mts +20 -21
- package/dist/provider/password.d.mts +174 -202
- package/dist/provider/provider.d.mts +107 -109
- package/dist/provider/reddit.d.mts +33 -34
- package/dist/provider/slack.d.mts +34 -35
- package/dist/provider/spotify.d.mts +34 -35
- package/dist/provider/totp.d.mts +43 -44
- package/dist/provider/twitch.d.mts +33 -34
- package/dist/provider/vercel.d.mts +65 -66
- package/dist/revocation.d.mts +29 -30
- package/dist/router/context.d.mts +21 -0
- package/dist/router/context.mjs +193 -0
- package/dist/router/cookies.d.mts +8 -0
- package/dist/router/cookies.mjs +13 -0
- package/dist/router/index.d.mts +21 -0
- package/dist/router/index.mjs +107 -0
- package/dist/router/matcher.d.mts +15 -0
- package/dist/router/matcher.mjs +76 -0
- package/dist/router/middleware/cors.d.mts +15 -0
- package/dist/router/middleware/cors.mjs +114 -0
- package/dist/router/safe-request.d.mts +52 -0
- package/dist/router/safe-request.mjs +160 -0
- package/dist/router/types.d.mts +67 -0
- package/dist/router/types.mjs +1 -0
- package/dist/router/variables.d.mts +12 -0
- package/dist/router/variables.mjs +20 -0
- package/dist/storage/memory.d.mts +11 -12
- package/dist/storage/storage.d.mts +110 -110
- package/dist/storage/turso.d.mts +0 -1
- package/dist/storage/unstorage.d.mts +0 -1
- package/dist/subject.d.mts +0 -1
- package/dist/themes/theme.d.mts +101 -101
- package/dist/toolkit/client.d.mts +56 -57
- package/dist/toolkit/providers/facebook.d.mts +0 -1
- package/dist/toolkit/providers/github.d.mts +0 -1
- package/dist/toolkit/providers/google.d.mts +0 -1
- package/dist/toolkit/storage.d.mts +8 -8
- package/dist/ui/base.d.mts +0 -1
- package/dist/ui/code.d.mts +5 -6
- package/dist/ui/form.d.mts +6 -7
- package/dist/ui/icon.d.mts +0 -1
- package/dist/ui/magiclink.d.mts +5 -6
- package/dist/ui/passkey.d.mts +0 -1
- package/dist/ui/password.d.mts +2 -3
- package/dist/ui/select.d.mts +0 -1
- package/dist/ui/totp.d.mts +0 -1
- package/dist/util.d.mts +1 -2
- package/package.json +6 -7
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CompiledRoute, MatchResult, RouterOptions } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/router/matcher.d.ts
|
|
4
|
+
declare class RouteMatcher {
|
|
5
|
+
private readonly compiledRoutes;
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(options?: RouterOptions);
|
|
8
|
+
compile(pattern: string): CompiledRoute;
|
|
9
|
+
match(pattern: string, pathname: string): MatchResult | null;
|
|
10
|
+
sortRoutesBySpecificity(patterns: string[]): string[];
|
|
11
|
+
private calculateSpecificity;
|
|
12
|
+
normalizePath(pathname: string): string;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { RouteMatcher };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region src/router/matcher.ts
|
|
2
|
+
var RouteMatcher = class {
|
|
3
|
+
compiledRoutes = /* @__PURE__ */ new Map();
|
|
4
|
+
options;
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.options = {
|
|
7
|
+
caseSensitive: false,
|
|
8
|
+
strict: false,
|
|
9
|
+
basePath: "",
|
|
10
|
+
...options
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
compile(pattern) {
|
|
14
|
+
const cacheKey = `${pattern}:${this.options.caseSensitive}:${this.options.strict}`;
|
|
15
|
+
const cached = this.compiledRoutes.get(cacheKey);
|
|
16
|
+
if (cached) return cached;
|
|
17
|
+
const paramNames = [];
|
|
18
|
+
let regexPattern = pattern.replace(/:([^/]+)/g, (_, name) => {
|
|
19
|
+
paramNames.push(name);
|
|
20
|
+
return "([^/]+)";
|
|
21
|
+
});
|
|
22
|
+
regexPattern = regexPattern.replace(/\*/g, "(.*)");
|
|
23
|
+
const finalPattern = this.options.strict || pattern === "/" ? regexPattern : `${regexPattern.replace(/\/$/, "")}/?`;
|
|
24
|
+
const compiled = {
|
|
25
|
+
regex: new RegExp(`^${finalPattern}$`, this.options.caseSensitive ? "" : "i"),
|
|
26
|
+
paramNames,
|
|
27
|
+
pattern
|
|
28
|
+
};
|
|
29
|
+
this.compiledRoutes.set(cacheKey, compiled);
|
|
30
|
+
return compiled;
|
|
31
|
+
}
|
|
32
|
+
match(pattern, pathname) {
|
|
33
|
+
const compiled = this.compile(pattern);
|
|
34
|
+
const match = pathname.match(compiled.regex);
|
|
35
|
+
if (!match) return null;
|
|
36
|
+
const params = {};
|
|
37
|
+
for (let i = 0; i < compiled.paramNames.length; i++) {
|
|
38
|
+
const name = compiled.paramNames[i];
|
|
39
|
+
const value = match[i + 1];
|
|
40
|
+
if (name && value !== void 0) try {
|
|
41
|
+
params[name] = decodeURIComponent(value);
|
|
42
|
+
} catch {
|
|
43
|
+
params[name] = value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
params: Object.freeze(params),
|
|
48
|
+
pattern
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
sortRoutesBySpecificity(patterns) {
|
|
52
|
+
return [...patterns].sort((a, b) => {
|
|
53
|
+
const aScore = this.calculateSpecificity(a);
|
|
54
|
+
return this.calculateSpecificity(b) - aScore;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
calculateSpecificity(pattern) {
|
|
58
|
+
const segments = pattern.split("/").filter(Boolean);
|
|
59
|
+
let score = 0;
|
|
60
|
+
for (const segment of segments) if (segment.startsWith(":")) score += 2;
|
|
61
|
+
else if (segment === "*") score += 1;
|
|
62
|
+
else score += 4;
|
|
63
|
+
return score;
|
|
64
|
+
}
|
|
65
|
+
normalizePath(pathname) {
|
|
66
|
+
let normalized = pathname;
|
|
67
|
+
const { basePath, strict } = this.options;
|
|
68
|
+
if (basePath && normalized.startsWith(basePath)) normalized = normalized.slice(basePath.length) || "/";
|
|
69
|
+
if (!normalized.startsWith("/")) normalized = `/${normalized}`;
|
|
70
|
+
if (!strict && normalized.length > 1 && normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { RouteMatcher };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MiddlewareHandler, RouterContext, VariableMap } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/router/middleware/cors.d.ts
|
|
4
|
+
interface CORSOptions {
|
|
5
|
+
origin?: "*" | string | readonly string[] | ((origin: string, ctx: RouterContext<Record<string, string>, VariableMap>) => string | null);
|
|
6
|
+
allowMethods?: readonly string[] | ((origin: string, ctx: RouterContext<Record<string, string>, VariableMap>) => readonly string[]);
|
|
7
|
+
allowHeaders?: readonly string[];
|
|
8
|
+
maxAge?: number;
|
|
9
|
+
credentials?: boolean;
|
|
10
|
+
exposeHeaders?: readonly string[];
|
|
11
|
+
preflightHandler?: <TVariables extends VariableMap>(ctx: RouterContext<Record<string, string>, TVariables>) => Promise<Response> | Response;
|
|
12
|
+
}
|
|
13
|
+
declare const cors: <TVariables extends VariableMap = VariableMap>(options?: CORSOptions) => MiddlewareHandler<Record<string, string>, TVariables>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { CORSOptions, cors };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//#region src/router/middleware/cors.ts
|
|
2
|
+
const DEFAULT_CORS_OPTIONS = {
|
|
3
|
+
origin: "*",
|
|
4
|
+
allowMethods: [
|
|
5
|
+
"GET",
|
|
6
|
+
"HEAD",
|
|
7
|
+
"PUT",
|
|
8
|
+
"POST",
|
|
9
|
+
"DELETE",
|
|
10
|
+
"PATCH",
|
|
11
|
+
"OPTIONS"
|
|
12
|
+
],
|
|
13
|
+
allowHeaders: [],
|
|
14
|
+
maxAge: 86400,
|
|
15
|
+
credentials: false,
|
|
16
|
+
exposeHeaders: []
|
|
17
|
+
};
|
|
18
|
+
const createOriginResolver = (origin) => {
|
|
19
|
+
if (!origin || origin === "*") return () => "*";
|
|
20
|
+
if (typeof origin === "string") return (requestOrigin) => origin === requestOrigin ? origin : null;
|
|
21
|
+
if (typeof origin === "function") return origin;
|
|
22
|
+
if (Array.isArray(origin)) {
|
|
23
|
+
const allowedOrigins = new Set(origin);
|
|
24
|
+
return (requestOrigin) => allowedOrigins.has(requestOrigin) ? requestOrigin : null;
|
|
25
|
+
}
|
|
26
|
+
return () => null;
|
|
27
|
+
};
|
|
28
|
+
const createMethodsResolver = (methods) => {
|
|
29
|
+
if (typeof methods === "function") return methods;
|
|
30
|
+
if (Array.isArray(methods)) return () => methods;
|
|
31
|
+
return () => DEFAULT_CORS_OPTIONS.allowMethods;
|
|
32
|
+
};
|
|
33
|
+
const normalizeCORSOptions = (options = {}) => {
|
|
34
|
+
const opts = {
|
|
35
|
+
...DEFAULT_CORS_OPTIONS,
|
|
36
|
+
...options
|
|
37
|
+
};
|
|
38
|
+
if (opts.maxAge < 0) throw new Error("CORS maxAge must be non-negative");
|
|
39
|
+
if (Array.isArray(opts.allowMethods)) {
|
|
40
|
+
const validMethods = new Set([
|
|
41
|
+
"GET",
|
|
42
|
+
"HEAD",
|
|
43
|
+
"PUT",
|
|
44
|
+
"POST",
|
|
45
|
+
"DELETE",
|
|
46
|
+
"PATCH",
|
|
47
|
+
"OPTIONS"
|
|
48
|
+
]);
|
|
49
|
+
const invalidMethods = opts.allowMethods.filter((method) => !validMethods.has(method.toUpperCase()));
|
|
50
|
+
if (invalidMethods.length > 0) console.warn(`CORS: Invalid HTTP methods detected: ${invalidMethods.join(", ")}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
...opts,
|
|
54
|
+
_originResolver: createOriginResolver(options.origin),
|
|
55
|
+
_methodsResolver: createMethodsResolver(options.allowMethods)
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const cors = (options = {}) => {
|
|
59
|
+
const opts = normalizeCORSOptions(options);
|
|
60
|
+
return async (ctx, next) => {
|
|
61
|
+
const requestOrigin = ctx.header("origin") || "";
|
|
62
|
+
const requestMethod = ctx.request.method.toUpperCase();
|
|
63
|
+
const allowedOrigin = opts._originResolver(requestOrigin, ctx);
|
|
64
|
+
const corsHeaders = {};
|
|
65
|
+
if (allowedOrigin) corsHeaders["Access-Control-Allow-Origin"] = allowedOrigin;
|
|
66
|
+
if (opts.origin !== "*" && allowedOrigin) {
|
|
67
|
+
const existingVary = ctx.header("vary");
|
|
68
|
+
corsHeaders.Vary = existingVary ? `${existingVary}, Origin` : "Origin";
|
|
69
|
+
}
|
|
70
|
+
if (opts.credentials) corsHeaders["Access-Control-Allow-Credentials"] = "true";
|
|
71
|
+
if (opts.exposeHeaders && opts.exposeHeaders.length > 0) corsHeaders["Access-Control-Expose-Headers"] = opts.exposeHeaders.join(",");
|
|
72
|
+
if (requestMethod === "OPTIONS") {
|
|
73
|
+
if (opts.preflightHandler) return await opts.preflightHandler(ctx);
|
|
74
|
+
if (opts.maxAge != null) corsHeaders["Access-Control-Max-Age"] = opts.maxAge.toString();
|
|
75
|
+
const allowedMethods = opts._methodsResolver(requestOrigin, ctx);
|
|
76
|
+
if (allowedMethods.length > 0) corsHeaders["Access-Control-Allow-Methods"] = allowedMethods.join(",");
|
|
77
|
+
let allowedHeaders = opts.allowHeaders;
|
|
78
|
+
if (!allowedHeaders || allowedHeaders.length === 0) {
|
|
79
|
+
const requestHeaders = ctx.header("access-control-request-headers");
|
|
80
|
+
if (requestHeaders) allowedHeaders = requestHeaders.split(/\s*,\s*/);
|
|
81
|
+
}
|
|
82
|
+
if (allowedHeaders && allowedHeaders.length > 0) {
|
|
83
|
+
corsHeaders["Access-Control-Allow-Headers"] = allowedHeaders.join(",");
|
|
84
|
+
const existingVary = corsHeaders.Vary || ctx.header("vary");
|
|
85
|
+
corsHeaders.Vary = existingVary ? `${existingVary}, Access-Control-Request-Headers` : "Access-Control-Request-Headers";
|
|
86
|
+
}
|
|
87
|
+
const headers = new Headers();
|
|
88
|
+
Object.entries(corsHeaders).forEach(([key, value]) => {
|
|
89
|
+
headers.set(key, value);
|
|
90
|
+
});
|
|
91
|
+
return new Response(null, {
|
|
92
|
+
status: 204,
|
|
93
|
+
statusText: "No Content",
|
|
94
|
+
headers
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return applyCORSHeaders(await next(), corsHeaders);
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const applyCORSHeaders = (response, corsHeaders) => {
|
|
101
|
+
if (Object.keys(corsHeaders).length === 0) return response;
|
|
102
|
+
const newHeaders = new Headers(response.headers);
|
|
103
|
+
Object.entries(corsHeaders).forEach(([key, value]) => {
|
|
104
|
+
newHeaders.set(key, value);
|
|
105
|
+
});
|
|
106
|
+
return new Response(response.body, {
|
|
107
|
+
status: response.status,
|
|
108
|
+
statusText: response.statusText,
|
|
109
|
+
headers: newHeaders
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { cors };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/router/safe-request.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* SafeRequest wraps a Request to cache the body and allow multiple reads.
|
|
4
|
+
* This solves issues with Request implementations that don't support multiple body reads.
|
|
5
|
+
*/
|
|
6
|
+
interface SafeRequestOptions {
|
|
7
|
+
cache?: RequestCache;
|
|
8
|
+
credentials?: RequestCredentials;
|
|
9
|
+
destination?: RequestDestination;
|
|
10
|
+
integrity?: string;
|
|
11
|
+
keepalive?: boolean;
|
|
12
|
+
mode?: RequestMode;
|
|
13
|
+
redirect?: RequestRedirect;
|
|
14
|
+
referrer?: string;
|
|
15
|
+
referrerPolicy?: ReferrerPolicy;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
declare class SafeRequest implements Request {
|
|
19
|
+
private cachedBody;
|
|
20
|
+
private bodyBuffer;
|
|
21
|
+
private readonly textDecoder;
|
|
22
|
+
readonly cache: RequestCache;
|
|
23
|
+
readonly credentials: RequestCredentials;
|
|
24
|
+
readonly destination: RequestDestination;
|
|
25
|
+
readonly headers: Headers;
|
|
26
|
+
readonly integrity: string;
|
|
27
|
+
readonly keepalive: boolean;
|
|
28
|
+
readonly method: string;
|
|
29
|
+
readonly mode: RequestMode;
|
|
30
|
+
readonly redirect: RequestRedirect;
|
|
31
|
+
readonly referrer: string;
|
|
32
|
+
readonly referrerPolicy: ReferrerPolicy;
|
|
33
|
+
readonly signal: AbortSignal;
|
|
34
|
+
readonly url: string;
|
|
35
|
+
constructor(url: string, method: string, headers: Headers, body: ArrayBuffer | null, options?: SafeRequestOptions);
|
|
36
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
37
|
+
blob(): Promise<Blob>;
|
|
38
|
+
formData(): Promise<FormData>;
|
|
39
|
+
json<T = unknown>(): Promise<T>;
|
|
40
|
+
text(): Promise<string>;
|
|
41
|
+
bytes(): Promise<Uint8Array<ArrayBuffer>>;
|
|
42
|
+
get body(): ReadableStream<Uint8Array<ArrayBuffer>> | null;
|
|
43
|
+
get bodyUsed(): boolean;
|
|
44
|
+
clone(): Request;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extracts data from a Request and creates a SafeRequest.
|
|
48
|
+
* Uses ReadableStream to safely read the body and avoid issues with certain Request implementations.
|
|
49
|
+
*/
|
|
50
|
+
declare function makeSafeRequest(request: Request): Promise<Request>;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { SafeRequest, makeSafeRequest };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//#region src/router/safe-request.ts
|
|
2
|
+
var SafeRequest = class SafeRequest {
|
|
3
|
+
cachedBody = null;
|
|
4
|
+
bodyBuffer = null;
|
|
5
|
+
textDecoder = new TextDecoder();
|
|
6
|
+
cache;
|
|
7
|
+
credentials;
|
|
8
|
+
destination;
|
|
9
|
+
headers;
|
|
10
|
+
integrity;
|
|
11
|
+
keepalive;
|
|
12
|
+
method;
|
|
13
|
+
mode;
|
|
14
|
+
redirect;
|
|
15
|
+
referrer;
|
|
16
|
+
referrerPolicy;
|
|
17
|
+
signal;
|
|
18
|
+
url;
|
|
19
|
+
constructor(url, method, headers, body, options) {
|
|
20
|
+
this.url = url;
|
|
21
|
+
this.method = method;
|
|
22
|
+
this.headers = headers;
|
|
23
|
+
this.bodyBuffer = body;
|
|
24
|
+
this.cache = options?.cache ?? "default";
|
|
25
|
+
this.credentials = options?.credentials ?? "same-origin";
|
|
26
|
+
this.destination = options?.destination ?? "";
|
|
27
|
+
this.integrity = options?.integrity ?? "";
|
|
28
|
+
this.keepalive = options?.keepalive ?? false;
|
|
29
|
+
this.mode = options?.mode ?? "cors";
|
|
30
|
+
this.redirect = options?.redirect ?? "follow";
|
|
31
|
+
this.referrer = options?.referrer ?? "";
|
|
32
|
+
this.referrerPolicy = options?.referrerPolicy ?? "";
|
|
33
|
+
this.signal = options?.signal ?? new AbortController().signal;
|
|
34
|
+
}
|
|
35
|
+
async arrayBuffer() {
|
|
36
|
+
return this.bodyBuffer ?? /* @__PURE__ */ new ArrayBuffer(0);
|
|
37
|
+
}
|
|
38
|
+
async blob() {
|
|
39
|
+
const buffer = await this.arrayBuffer();
|
|
40
|
+
return new Blob([buffer]);
|
|
41
|
+
}
|
|
42
|
+
async formData() {
|
|
43
|
+
const buffer = await this.arrayBuffer();
|
|
44
|
+
const blob = new Blob([buffer], { type: this.headers.get("content-type") || "application/x-www-form-urlencoded" });
|
|
45
|
+
return new Request(this.url, {
|
|
46
|
+
method: this.method,
|
|
47
|
+
headers: this.headers,
|
|
48
|
+
body: blob
|
|
49
|
+
}).formData();
|
|
50
|
+
}
|
|
51
|
+
async json() {
|
|
52
|
+
const text = await this.text();
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
}
|
|
55
|
+
async text() {
|
|
56
|
+
const buffer = await this.arrayBuffer();
|
|
57
|
+
return this.textDecoder.decode(buffer);
|
|
58
|
+
}
|
|
59
|
+
async bytes() {
|
|
60
|
+
return this.arrayBuffer().then((buffer) => new Uint8Array(buffer));
|
|
61
|
+
}
|
|
62
|
+
get body() {
|
|
63
|
+
if (this.cachedBody) return this.cachedBody;
|
|
64
|
+
if (this.bodyBuffer) {
|
|
65
|
+
const buffer = this.bodyBuffer;
|
|
66
|
+
this.cachedBody = new ReadableStream({ start(controller) {
|
|
67
|
+
controller.enqueue(new Uint8Array(buffer));
|
|
68
|
+
controller.close();
|
|
69
|
+
} });
|
|
70
|
+
return this.cachedBody;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
get bodyUsed() {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
clone() {
|
|
78
|
+
return new SafeRequest(this.url, this.method, this.headers, this.bodyBuffer, {
|
|
79
|
+
cache: this.cache,
|
|
80
|
+
credentials: this.credentials,
|
|
81
|
+
destination: this.destination,
|
|
82
|
+
integrity: this.integrity,
|
|
83
|
+
keepalive: this.keepalive,
|
|
84
|
+
mode: this.mode,
|
|
85
|
+
redirect: this.redirect,
|
|
86
|
+
referrer: this.referrer,
|
|
87
|
+
referrerPolicy: this.referrerPolicy,
|
|
88
|
+
signal: this.signal
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Extracts data from a Request and creates a SafeRequest.
|
|
94
|
+
* Uses ReadableStream to safely read the body and avoid issues with certain Request implementations.
|
|
95
|
+
*/
|
|
96
|
+
async function makeSafeRequest(request) {
|
|
97
|
+
if (request instanceof SafeRequest) return request;
|
|
98
|
+
const url = request.url;
|
|
99
|
+
const method = request.method;
|
|
100
|
+
const headers = new Headers(request.headers);
|
|
101
|
+
let bodyBuffer = null;
|
|
102
|
+
const requestMethod = method.toUpperCase();
|
|
103
|
+
if (requestMethod === "POST" || requestMethod === "PUT" || requestMethod === "PATCH") try {
|
|
104
|
+
const body = request.body;
|
|
105
|
+
if (body) {
|
|
106
|
+
const reader = body.getReader();
|
|
107
|
+
const chunks = [];
|
|
108
|
+
while (true) {
|
|
109
|
+
const { done, value } = await reader.read();
|
|
110
|
+
if (done) break;
|
|
111
|
+
if (value) chunks.push(value);
|
|
112
|
+
}
|
|
113
|
+
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
114
|
+
const combined = new Uint8Array(totalLength);
|
|
115
|
+
let offset = 0;
|
|
116
|
+
for (const chunk of chunks) {
|
|
117
|
+
combined.set(chunk, offset);
|
|
118
|
+
offset += chunk.length;
|
|
119
|
+
}
|
|
120
|
+
bodyBuffer = combined.buffer;
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn("Failed to read request body via stream:", error);
|
|
124
|
+
}
|
|
125
|
+
const options = {};
|
|
126
|
+
try {
|
|
127
|
+
options.cache = request.cache;
|
|
128
|
+
} catch {}
|
|
129
|
+
try {
|
|
130
|
+
options.credentials = request.credentials;
|
|
131
|
+
} catch {}
|
|
132
|
+
try {
|
|
133
|
+
options.destination = request.destination;
|
|
134
|
+
} catch {}
|
|
135
|
+
try {
|
|
136
|
+
options.integrity = request.integrity;
|
|
137
|
+
} catch {}
|
|
138
|
+
try {
|
|
139
|
+
options.keepalive = request.keepalive;
|
|
140
|
+
} catch {}
|
|
141
|
+
try {
|
|
142
|
+
options.mode = request.mode;
|
|
143
|
+
} catch {}
|
|
144
|
+
try {
|
|
145
|
+
options.redirect = request.redirect;
|
|
146
|
+
} catch {}
|
|
147
|
+
try {
|
|
148
|
+
options.referrer = request.referrer;
|
|
149
|
+
} catch {}
|
|
150
|
+
try {
|
|
151
|
+
options.referrerPolicy = request.referrerPolicy;
|
|
152
|
+
} catch {}
|
|
153
|
+
try {
|
|
154
|
+
options.signal = request.signal;
|
|
155
|
+
} catch {}
|
|
156
|
+
return new SafeRequest(url, method, headers, bodyBuffer, options);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
export { SafeRequest, makeSafeRequest };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//#region src/router/types.d.ts
|
|
2
|
+
type ExtractParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? { readonly [K in Param]: string } & ExtractParams<`/${Rest}`> : T extends `${string}:${infer Param}` ? { readonly [K in Param]: string } : Record<string, never>;
|
|
3
|
+
type VariableMap = Record<string, unknown>;
|
|
4
|
+
interface RouterEnvironment<TVariables extends VariableMap = VariableMap> {
|
|
5
|
+
Variables: TVariables;
|
|
6
|
+
}
|
|
7
|
+
interface CookieOptions {
|
|
8
|
+
domain?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
expires?: Date;
|
|
11
|
+
maxAge?: number;
|
|
12
|
+
httpOnly?: boolean;
|
|
13
|
+
secure?: boolean;
|
|
14
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
15
|
+
}
|
|
16
|
+
interface RouterContext<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> {
|
|
17
|
+
readonly request: Request;
|
|
18
|
+
readonly params: Readonly<TParams>;
|
|
19
|
+
readonly searchParams: Readonly<URLSearchParams>;
|
|
20
|
+
query<K extends string>(key: K): string | undefined;
|
|
21
|
+
header<K extends string>(key: K): string | undefined;
|
|
22
|
+
cookie<K extends string>(key: K): string | undefined;
|
|
23
|
+
formData(): Promise<FormData>;
|
|
24
|
+
parseJson<T = unknown>(): Promise<T>;
|
|
25
|
+
json<T>(data: T, init?: ResponseInit): Response;
|
|
26
|
+
redirect(url: string | URL, status?: 300 | 301 | 302 | 303 | 307 | 308): Response;
|
|
27
|
+
text(data: string, init?: ResponseInit): Response;
|
|
28
|
+
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
29
|
+
deleteCookie(name: string, options?: Pick<CookieOptions, "domain" | "path">): void;
|
|
30
|
+
newResponse(body?: BodyInit, init?: ResponseInit): Response;
|
|
31
|
+
set<K extends keyof TVariables>(key: K, value: TVariables[K]): void;
|
|
32
|
+
get<K extends keyof TVariables>(key: K): TVariables[K];
|
|
33
|
+
has<K extends keyof TVariables>(key: K): key is K;
|
|
34
|
+
}
|
|
35
|
+
type RouteHandler<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> = (ctx: RouterContext<TParams, TVariables>) => Promise<Response> | Response;
|
|
36
|
+
type MiddlewareHandler<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> = (ctx: RouterContext<TParams, TVariables>, next: () => Promise<Response> | Response) => Promise<Response> | Response;
|
|
37
|
+
type EnhancedRouteHandler<TParams extends Record<string, string> = Record<string, string>, TVariables extends VariableMap = VariableMap> = {
|
|
38
|
+
handler: RouteHandler<TParams, TVariables>;
|
|
39
|
+
middleware?: MiddlewareHandler<TParams, TVariables>[];
|
|
40
|
+
};
|
|
41
|
+
type AnyHandler<TParams extends Record<string, string>, TVariables extends VariableMap = VariableMap> = RouteHandler<TParams, TVariables> | EnhancedRouteHandler<TParams, TVariables>;
|
|
42
|
+
type HttpMethod = "GET" | "POST";
|
|
43
|
+
interface CompiledRoute {
|
|
44
|
+
regex: RegExp;
|
|
45
|
+
paramNames: string[];
|
|
46
|
+
pattern: string;
|
|
47
|
+
}
|
|
48
|
+
interface MatchResult {
|
|
49
|
+
params: Record<string, string>;
|
|
50
|
+
pattern: string;
|
|
51
|
+
}
|
|
52
|
+
interface RouteDefinition<TVariables extends VariableMap = VariableMap> {
|
|
53
|
+
method: HttpMethod;
|
|
54
|
+
pattern: string;
|
|
55
|
+
handler: RouteHandler<Record<string, string>, TVariables>;
|
|
56
|
+
middleware: MiddlewareHandler<Record<string, string>, TVariables>[];
|
|
57
|
+
compiled: CompiledRoute;
|
|
58
|
+
}
|
|
59
|
+
interface RouterOptions {
|
|
60
|
+
caseSensitive?: boolean;
|
|
61
|
+
strict?: boolean;
|
|
62
|
+
basePath?: string;
|
|
63
|
+
}
|
|
64
|
+
type ErrorHandler<TVariables extends VariableMap = VariableMap> = (error: Error, ctx: RouterContext<Record<string, string>, TVariables>) => Promise<Response> | Response;
|
|
65
|
+
type GlobalMiddleware<TVariables extends VariableMap = VariableMap> = (ctx: RouterContext<Record<string, string>, TVariables>, next: () => Promise<Response> | Response) => Promise<Response> | Response;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { AnyHandler, CompiledRoute, CookieOptions, EnhancedRouteHandler, ErrorHandler, ExtractParams, GlobalMiddleware, HttpMethod, MatchResult, MiddlewareHandler, RouteDefinition, RouteHandler, RouterContext, RouterEnvironment, RouterOptions, VariableMap };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { VariableMap } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/router/variables.d.ts
|
|
4
|
+
declare class ContextVariableManager<TVariables extends VariableMap = VariableMap> {
|
|
5
|
+
private readonly state;
|
|
6
|
+
constructor(initialVariables?: Partial<TVariables>);
|
|
7
|
+
set<K extends keyof TVariables>(key: K, value: TVariables[K]): void;
|
|
8
|
+
get<K extends keyof TVariables>(key: K): TVariables[K];
|
|
9
|
+
has<K extends keyof TVariables>(key: K): key is K;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { ContextVariableManager };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/router/variables.ts
|
|
2
|
+
var ContextVariableManager = class {
|
|
3
|
+
state = /* @__PURE__ */ new Map();
|
|
4
|
+
constructor(initialVariables) {
|
|
5
|
+
if (initialVariables) for (const [key, value] of Object.entries(initialVariables)) this.state.set(key, value);
|
|
6
|
+
}
|
|
7
|
+
set(key, value) {
|
|
8
|
+
if (typeof key !== "string" || key.length === 0) throw new Error("Variable key must be a non-empty string");
|
|
9
|
+
this.state.set(key, value);
|
|
10
|
+
}
|
|
11
|
+
get(key) {
|
|
12
|
+
return this.state.get(key);
|
|
13
|
+
}
|
|
14
|
+
has(key) {
|
|
15
|
+
return this.state.has(key);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ContextVariableManager };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { StorageAdapter } from "./storage.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/storage/memory.d.ts
|
|
4
|
-
|
|
5
4
|
/**
|
|
6
5
|
* In-memory storage adapter for Draft Auth with optional file persistence.
|
|
7
6
|
*
|
|
@@ -40,17 +39,17 @@ import { StorageAdapter } from "./storage.mjs";
|
|
|
40
39
|
*/
|
|
41
40
|
interface MemoryStorageOptions {
|
|
42
41
|
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
* File path for persisting the in-memory store to disk.
|
|
43
|
+
* When specified, the store will be saved to this file on changes
|
|
44
|
+
* and loaded from it on startup if it exists.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* {
|
|
49
|
+
* persist: "./data/auth-storage.json"
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
54
53
|
readonly persist?: string;
|
|
55
54
|
}
|
|
56
55
|
/**
|