1tube 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ export type CallEdgeAuth = "service" | "user" | {
2
+ bearer: string;
3
+ };
4
+ export interface CallEdgeFunctionOptions {
5
+ /** HTTP method. Defaults to `"POST"`. */
6
+ method?: string;
7
+ /** Optional route path inside the target function, e.g. `/sync`. */
8
+ path?: string;
9
+ /** Query parameters appended to the function URL. */
10
+ query?: Record<string, string | number | boolean | null | undefined>;
11
+ body?: unknown;
12
+ auth?: CallEdgeAuth;
13
+ headers?: Record<string, string>;
14
+ signal?: AbortSignal;
15
+ }
16
+ export interface EdgeCallerRuntime {
17
+ getBaseUrl(): string;
18
+ /** Public prefix in front of the function name. Defaults to `/functions/v1`. */
19
+ pathPrefix?: string;
20
+ getServiceRoleToken(): string | null | undefined;
21
+ getAnonKey?(): string | null | undefined;
22
+ getUserToken(): string | null | undefined;
23
+ fetch?: typeof fetch;
24
+ waitUntil?: (promise: Promise<unknown>) => void;
25
+ }
26
+ export interface EdgeCaller {
27
+ callEdgeFunction(name: string, options?: CallEdgeFunctionOptions): Promise<Response>;
28
+ callEdgeFunctionInBackground(name: string, options?: CallEdgeFunctionOptions): void;
29
+ }
30
+ export declare function createEdgeCaller(runtime: EdgeCallerRuntime): EdgeCaller;
@@ -0,0 +1,77 @@
1
+ function resolveBearerToken(runtime, auth) {
2
+ if (typeof auth === "object" && auth !== null && "bearer" in auth) {
3
+ return auth.bearer;
4
+ }
5
+ if (auth === "user") {
6
+ const token = runtime.getUserToken();
7
+ if (!token) {
8
+ throw new Error("[callEdgeFunction] auth: 'user' requires an authenticated caller " +
9
+ "(no user token available).");
10
+ }
11
+ return token;
12
+ }
13
+ const serviceToken = runtime.getServiceRoleToken();
14
+ if (!serviceToken) {
15
+ throw new Error("[callEdgeFunction] service auth requires a service role token.");
16
+ }
17
+ return serviceToken;
18
+ }
19
+ function serializeBody(body) {
20
+ if (body === undefined || body === null)
21
+ return { body: undefined };
22
+ if (typeof body === "string") {
23
+ return { body, contentType: "text/plain;charset=UTF-8" };
24
+ }
25
+ if (body instanceof Uint8Array ||
26
+ body instanceof ArrayBuffer ||
27
+ body instanceof Blob ||
28
+ body instanceof FormData ||
29
+ body instanceof ReadableStream) {
30
+ return { body: body };
31
+ }
32
+ return { body: JSON.stringify(body), contentType: "application/json" };
33
+ }
34
+ function buildRequest(runtime, name, opts) {
35
+ const pathPrefix = runtime.pathPrefix ?? "/functions/v1";
36
+ const routePath = opts.path ? `/${opts.path.replace(/^\/+/, "")}` : "";
37
+ const url = new URL(`${runtime.getBaseUrl().replace(/\/+$/, "")}/${pathPrefix.replace(/^\/+|\/+$/g, "")}/${name}${routePath}`);
38
+ for (const [key, value] of Object.entries(opts.query ?? {})) {
39
+ if (value !== undefined && value !== null) {
40
+ url.searchParams.set(key, String(value));
41
+ }
42
+ }
43
+ const token = resolveBearerToken(runtime, opts.auth ?? "service");
44
+ const { body, contentType } = serializeBody(opts.body);
45
+ const anonKey = runtime.getAnonKey?.();
46
+ const headers = {
47
+ Authorization: `Bearer ${token}`,
48
+ ...(contentType ? { "Content-Type": contentType } : {}),
49
+ ...(anonKey ? { apikey: anonKey } : {}),
50
+ ...opts.headers,
51
+ };
52
+ return {
53
+ url: url.toString(),
54
+ init: {
55
+ method: opts.method ?? "POST",
56
+ headers,
57
+ body: body ?? null,
58
+ signal: opts.signal,
59
+ },
60
+ };
61
+ }
62
+ export function createEdgeCaller(runtime) {
63
+ const fetchImpl = runtime.fetch ?? fetch;
64
+ return {
65
+ async callEdgeFunction(name, options = {}) {
66
+ const { url, init } = buildRequest(runtime, name, options);
67
+ return await fetchImpl(url, init);
68
+ },
69
+ callEdgeFunctionInBackground(name, options = {}) {
70
+ const { url, init } = buildRequest(runtime, name, options);
71
+ const promise = fetchImpl(url, init).catch((err) => {
72
+ console.error(`[callEdgeFunctionInBackground] ${name} failed:`, err);
73
+ });
74
+ runtime.waitUntil?.(promise);
75
+ },
76
+ };
77
+ }
package/dist/edge.d.ts ADDED
@@ -0,0 +1,176 @@
1
+ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
+ export interface SchemaLike<T = unknown> {
3
+ safeParse(value: unknown): {
4
+ success: true;
5
+ data: T;
6
+ } | {
7
+ success: false;
8
+ error: unknown;
9
+ };
10
+ }
11
+ export interface JsonSchemaAdapter {
12
+ toJsonSchema(schema: SchemaLike): unknown;
13
+ }
14
+ type SchemaOutput<TSchema> = TSchema extends SchemaLike<infer T> ? T : undefined;
15
+ type ParamsOutput<TSchema> = TSchema extends SchemaLike<infer T> ? T : Record<string, string>;
16
+ type QueryOutput<TSchema> = TSchema extends SchemaLike<infer T> ? T : Record<string, string>;
17
+ export declare enum HttpStatus {
18
+ OK = 200,
19
+ CREATED = 201,
20
+ ACCEPTED = 202,
21
+ NO_CONTENT = 204,
22
+ RESET_CONTENT = 205,
23
+ BAD_REQUEST = 400,
24
+ UNAUTHORIZED = 401,
25
+ FORBIDDEN = 403,
26
+ NOT_FOUND = 404,
27
+ METHOD_NOT_ALLOWED = 405,
28
+ REQUEST_TIMEOUT = 408,
29
+ CONFLICT = 409,
30
+ GONE = 410,
31
+ PRECONDITION_FAILED = 412,
32
+ PAYLOAD_TOO_LARGE = 413,
33
+ UNSUPPORTED_MEDIA_TYPE = 415,
34
+ UNPROCESSABLE_CONTENT = 422,
35
+ LOCKED = 423,
36
+ PRECONDITION_REQUIRED = 428,
37
+ TOO_MANY_REQUESTS = 429,
38
+ INTERNAL_SERVER_ERROR = 500,
39
+ NOT_IMPLEMENTED = 501,
40
+ BAD_GATEWAY = 502,
41
+ SERVICE_UNAVAILABLE = 503,
42
+ GATEWAY_TIMEOUT = 504
43
+ }
44
+ export type ErrorHttpStatus = HttpStatus.BAD_REQUEST | HttpStatus.UNAUTHORIZED | HttpStatus.FORBIDDEN | HttpStatus.NOT_FOUND | HttpStatus.METHOD_NOT_ALLOWED | HttpStatus.REQUEST_TIMEOUT | HttpStatus.CONFLICT | HttpStatus.GONE | HttpStatus.PRECONDITION_FAILED | HttpStatus.PAYLOAD_TOO_LARGE | HttpStatus.UNSUPPORTED_MEDIA_TYPE | HttpStatus.UNPROCESSABLE_CONTENT | HttpStatus.LOCKED | HttpStatus.PRECONDITION_REQUIRED | HttpStatus.TOO_MANY_REQUESTS | HttpStatus.INTERNAL_SERVER_ERROR | HttpStatus.NOT_IMPLEMENTED | HttpStatus.BAD_GATEWAY | HttpStatus.SERVICE_UNAVAILABLE | HttpStatus.GATEWAY_TIMEOUT;
45
+ export type RouteResponses = Partial<Record<HttpStatus, SchemaLike>>;
46
+ export interface RouteRequest<TParams extends SchemaLike | undefined, TQuery extends SchemaLike | undefined, TBody extends SchemaLike | undefined, TContext> {
47
+ req: Request;
48
+ ctx: TContext;
49
+ params: ParamsOutput<TParams>;
50
+ query: QueryOutput<TQuery>;
51
+ body: SchemaOutput<TBody>;
52
+ }
53
+ export type RouteHandler<TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TContext = unknown> = (input: RouteRequest<TParams, TQuery, TBody, TContext>) => Response | Promise<Response>;
54
+ export interface RouteOptions<TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TResponses extends RouteResponses = RouteResponses, TContext = unknown> {
55
+ params?: TParams;
56
+ query?: TQuery;
57
+ body?: TBody;
58
+ responses: TResponses;
59
+ summary?: string;
60
+ description?: string;
61
+ handler: RouteHandler<TParams, TQuery, TBody, TContext>;
62
+ }
63
+ export interface RouteDefinition<TParams extends SchemaLike | undefined = SchemaLike | undefined, TQuery extends SchemaLike | undefined = SchemaLike | undefined, TBody extends SchemaLike | undefined = SchemaLike | undefined, TResponses extends RouteResponses = RouteResponses, TContext = unknown> extends RouteOptions<TParams, TQuery, TBody, TResponses, TContext> {
64
+ method: HttpMethod;
65
+ path: string;
66
+ operationId: string;
67
+ }
68
+ export interface JsonRouteOptions<TBody extends SchemaLike, TResponses extends RouteResponses, TContext> {
69
+ body: TBody;
70
+ responses: TResponses;
71
+ summary?: string;
72
+ description?: string;
73
+ handler: RouteHandler<undefined, undefined, TBody, TContext>;
74
+ }
75
+ export declare function createRouteBuilder<TDefaultContext = unknown>(): {
76
+ get: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TResponses extends RouteResponses = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, undefined, TResponses, TDefaultContext>) => RouteDefinition<TParams, TQuery, undefined, TResponses, TDefaultContext>;
77
+ post: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TResponses extends RouteResponses = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, TBody, TResponses, TDefaultContext>) => RouteDefinition<TParams, TQuery, TBody, TResponses, TDefaultContext>;
78
+ put: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TResponses extends RouteResponses = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, TBody, TResponses, TDefaultContext>) => RouteDefinition<TParams, TQuery, TBody, TResponses, TDefaultContext>;
79
+ patch: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TResponses extends RouteResponses = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, TBody, TResponses, TDefaultContext>) => RouteDefinition<TParams, TQuery, TBody, TResponses, TDefaultContext>;
80
+ delete: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TResponses extends RouteResponses = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, undefined, TResponses, TDefaultContext>) => RouteDefinition<TParams, TQuery, undefined, TResponses, TDefaultContext>;
81
+ postJson: <TBody extends SchemaLike, TResponses extends RouteResponses>(path: string, operationId: string, options: JsonRouteOptions<TBody, TResponses, TDefaultContext>) => RouteDefinition<undefined, undefined, TBody, TResponses, TDefaultContext>;
82
+ putJson: <TBody extends SchemaLike, TResponses extends RouteResponses>(path: string, operationId: string, options: JsonRouteOptions<TBody, TResponses, TDefaultContext>) => RouteDefinition<undefined, undefined, TBody, TResponses, TDefaultContext>;
83
+ patchJson: <TBody extends SchemaLike, TResponses extends RouteResponses>(path: string, operationId: string, options: JsonRouteOptions<TBody, TResponses, TDefaultContext>) => RouteDefinition<undefined, undefined, TBody, TResponses, TDefaultContext>;
84
+ };
85
+ export declare const route: {
86
+ get: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>> = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, undefined, TResponses, unknown>) => RouteDefinition<TParams, TQuery, undefined, TResponses, unknown>;
87
+ post: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>> = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, TBody, TResponses, unknown>) => RouteDefinition<TParams, TQuery, TBody, TResponses, unknown>;
88
+ put: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>> = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, TBody, TResponses, unknown>) => RouteDefinition<TParams, TQuery, TBody, TResponses, unknown>;
89
+ patch: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TBody extends SchemaLike | undefined = undefined, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>> = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, TBody, TResponses, unknown>) => RouteDefinition<TParams, TQuery, TBody, TResponses, unknown>;
90
+ delete: <TParams extends SchemaLike | undefined = undefined, TQuery extends SchemaLike | undefined = undefined, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>> = Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: RouteOptions<TParams, TQuery, undefined, TResponses, unknown>) => RouteDefinition<TParams, TQuery, undefined, TResponses, unknown>;
91
+ postJson: <TBody extends SchemaLike, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: JsonRouteOptions<TBody, TResponses, unknown>) => RouteDefinition<undefined, undefined, TBody, TResponses, unknown>;
92
+ putJson: <TBody extends SchemaLike, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: JsonRouteOptions<TBody, TResponses, unknown>) => RouteDefinition<undefined, undefined, TBody, TResponses, unknown>;
93
+ patchJson: <TBody extends SchemaLike, TResponses extends Partial<Record<HttpStatus, SchemaLike<unknown>>>>(path: string, operationId: string, options: JsonRouteOptions<TBody, TResponses, unknown>) => RouteDefinition<undefined, undefined, TBody, TResponses, unknown>;
94
+ };
95
+ export declare function json(body: unknown, init?: number | ResponseInit): Response;
96
+ export declare function jsonError(status: ErrorHttpStatus, message: string, details?: unknown, init?: Omit<ResponseInit, "status">): Response;
97
+ export declare function empty(status?: HttpStatus, init?: Omit<ResponseInit, "status">): Response;
98
+ export declare function text(body: string, init?: number | ResponseInit): Response;
99
+ export declare function created(body: unknown, init?: Omit<ResponseInit, "status">): Response;
100
+ export declare function accepted(body: unknown, init?: Omit<ResponseInit, "status">): Response;
101
+ export declare function noContent(init?: Omit<ResponseInit, "status">): Response;
102
+ export declare function badRequest(message?: string, details?: unknown): Response;
103
+ export declare function unauthorized(message?: string, details?: unknown): Response;
104
+ export declare function forbidden(message?: string, details?: unknown): Response;
105
+ export declare function notFound(message?: string, details?: unknown): Response;
106
+ export declare function methodNotAllowed(allowedMethods: readonly string[], message?: string): Response;
107
+ export declare function conflict(message?: string, details?: unknown): Response;
108
+ export declare function gone(message?: string, details?: unknown): Response;
109
+ export declare function payloadTooLarge(message?: string, details?: unknown): Response;
110
+ export declare function unsupportedMediaType(message?: string, details?: unknown): Response;
111
+ export declare function unprocessableContent(message?: string, details?: unknown): Response;
112
+ export declare function tooManyRequests(input?: {
113
+ message?: string;
114
+ retryAfterSeconds?: number;
115
+ }): Response;
116
+ export declare function internalServerError(message?: string, details?: unknown): Response;
117
+ export declare function badGateway(message?: string, details?: unknown): Response;
118
+ export declare function serviceUnavailable(message?: string, details?: unknown): Response;
119
+ export declare function gatewayTimeout(message?: string, details?: unknown): Response;
120
+ export interface ServeConfig<TContext = unknown> {
121
+ name: string;
122
+ require: unknown;
123
+ timeoutMs?: number;
124
+ routes: RouteDefinition<SchemaLike | undefined, SchemaLike | undefined, SchemaLike | undefined, RouteResponses, TContext>[];
125
+ /**
126
+ * Public prefix in front of the function name. Supabase-compatible hosts use
127
+ * `/functions/v1`; ASP.NET Core or other reverse proxies may expose a
128
+ * different prefix while keeping the same function modules.
129
+ */
130
+ pathPrefix?: string;
131
+ }
132
+ export interface MatchedRoute {
133
+ route: RouteDefinition<SchemaLike | undefined, SchemaLike | undefined, SchemaLike | undefined, RouteResponses, any>;
134
+ params: Record<string, string>;
135
+ }
136
+ export interface RouteDispatcherContext<TContext> {
137
+ req: Request;
138
+ ctx: TContext;
139
+ matched: MatchedRoute;
140
+ getCorsHeaders?: (req: Request) => HeadersInit;
141
+ }
142
+ export type RouteDispatcher<TContext> = (input: RouteDispatcherContext<TContext>) => Promise<Response>;
143
+ export interface FetchHandlerOptions<TContext> {
144
+ getContext(req: Request): TContext | Response | Promise<TContext | Response>;
145
+ getCorsHeaders?: (req: Request) => HeadersInit;
146
+ onError?: (error: unknown, req: Request) => Response | Promise<Response>;
147
+ afterResponse?: (response: Response, req: Request) => Response | Promise<Response>;
148
+ }
149
+ export interface StandaloneServe {
150
+ (handler: (req: Request) => Response | Promise<Response>): void;
151
+ }
152
+ export declare function getStandaloneServe(): StandaloneServe | null;
153
+ export declare function normalizeRoutePath(path: string): string;
154
+ export declare function splitPath(path: string): string[];
155
+ export declare function getRelativePath(req: Request, functionName: string): string;
156
+ export declare function getRelativePathFromParts(pathnameParts: string[], functionName: string, pathPrefix?: string): string;
157
+ export declare function matchRoutePath(routePath: string, requestPath: string): Record<string, string> | null;
158
+ export declare function findRoute<TContext>(config: ServeConfig<TContext>, req: Request): MatchedRoute | Response;
159
+ export declare function queryObject(req: Request): Record<string, string>;
160
+ export declare function parseJsonBody(req: Request): Promise<unknown>;
161
+ export declare function flattenSchemaError(error: unknown): unknown;
162
+ export declare function validateSchema<TSchema extends SchemaLike | undefined>(schema: TSchema, value: unknown, label: "params" | "query" | "body"): SchemaOutput<TSchema> | Response;
163
+ export declare function withCors(req: Request, response: Response, getCorsHeaders?: (req: Request) => HeadersInit): Response;
164
+ export declare function dispatchRoute<TContext>(input: RouteDispatcherContext<TContext>): Promise<Response>;
165
+ export declare function createFetchHandler<TContext>(config: ServeConfig<TContext>, options: FetchHandlerOptions<TContext>): (req: Request) => Promise<Response>;
166
+ export interface OpenApiDocumentOptions {
167
+ title: string;
168
+ version: string;
169
+ servers?: {
170
+ url: string;
171
+ description?: string;
172
+ }[];
173
+ schemaAdapter?: JsonSchemaAdapter;
174
+ }
175
+ export declare function createOpenApiDocument(config: ServeConfig<any>, options: OpenApiDocumentOptions): Record<string, unknown>;
176
+ export {};
package/dist/edge.js ADDED
@@ -0,0 +1,435 @@
1
+ export var HttpStatus;
2
+ (function (HttpStatus) {
3
+ HttpStatus[HttpStatus["OK"] = 200] = "OK";
4
+ HttpStatus[HttpStatus["CREATED"] = 201] = "CREATED";
5
+ HttpStatus[HttpStatus["ACCEPTED"] = 202] = "ACCEPTED";
6
+ HttpStatus[HttpStatus["NO_CONTENT"] = 204] = "NO_CONTENT";
7
+ HttpStatus[HttpStatus["RESET_CONTENT"] = 205] = "RESET_CONTENT";
8
+ HttpStatus[HttpStatus["BAD_REQUEST"] = 400] = "BAD_REQUEST";
9
+ HttpStatus[HttpStatus["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
10
+ HttpStatus[HttpStatus["FORBIDDEN"] = 403] = "FORBIDDEN";
11
+ HttpStatus[HttpStatus["NOT_FOUND"] = 404] = "NOT_FOUND";
12
+ HttpStatus[HttpStatus["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
13
+ HttpStatus[HttpStatus["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
14
+ HttpStatus[HttpStatus["CONFLICT"] = 409] = "CONFLICT";
15
+ HttpStatus[HttpStatus["GONE"] = 410] = "GONE";
16
+ HttpStatus[HttpStatus["PRECONDITION_FAILED"] = 412] = "PRECONDITION_FAILED";
17
+ HttpStatus[HttpStatus["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE";
18
+ HttpStatus[HttpStatus["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
19
+ HttpStatus[HttpStatus["UNPROCESSABLE_CONTENT"] = 422] = "UNPROCESSABLE_CONTENT";
20
+ HttpStatus[HttpStatus["LOCKED"] = 423] = "LOCKED";
21
+ HttpStatus[HttpStatus["PRECONDITION_REQUIRED"] = 428] = "PRECONDITION_REQUIRED";
22
+ HttpStatus[HttpStatus["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
23
+ HttpStatus[HttpStatus["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
24
+ HttpStatus[HttpStatus["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
25
+ HttpStatus[HttpStatus["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
26
+ HttpStatus[HttpStatus["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
27
+ HttpStatus[HttpStatus["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
28
+ })(HttpStatus || (HttpStatus = {}));
29
+ function defineRoute(method, path, operationId, options) {
30
+ return { method, path: normalizeRoutePath(path), operationId, ...options };
31
+ }
32
+ function defineJsonRoute(method, path, operationId, options) {
33
+ return defineRoute(method, path, operationId, options);
34
+ }
35
+ export function createRouteBuilder() {
36
+ return {
37
+ get: (path, operationId, options) => defineRoute("GET", path, operationId, options),
38
+ post: (path, operationId, options) => defineRoute("POST", path, operationId, options),
39
+ put: (path, operationId, options) => defineRoute("PUT", path, operationId, options),
40
+ patch: (path, operationId, options) => defineRoute("PATCH", path, operationId, options),
41
+ delete: (path, operationId, options) => defineRoute("DELETE", path, operationId, options),
42
+ postJson: (path, operationId, options) => defineJsonRoute("POST", path, operationId, options),
43
+ putJson: (path, operationId, options) => defineJsonRoute("PUT", path, operationId, options),
44
+ patchJson: (path, operationId, options) => defineJsonRoute("PATCH", path, operationId, options),
45
+ };
46
+ }
47
+ export const route = createRouteBuilder();
48
+ export function json(body, init = HttpStatus.OK) {
49
+ const responseInit = typeof init === "number"
50
+ ? { status: init }
51
+ : init;
52
+ const headers = new Headers(responseInit.headers);
53
+ if (!headers.has("Content-Type")) {
54
+ headers.set("Content-Type", "application/json");
55
+ }
56
+ return new Response(JSON.stringify(body), { ...responseInit, headers });
57
+ }
58
+ export function jsonError(status, message, details, init = {}) {
59
+ return json({ error: message, ...(details === undefined ? {} : { details }) }, { ...init, status });
60
+ }
61
+ export function empty(status = HttpStatus.NO_CONTENT, init = {}) {
62
+ return new Response(null, { ...init, status });
63
+ }
64
+ export function text(body, init = HttpStatus.OK) {
65
+ const responseInit = typeof init === "number"
66
+ ? { status: init }
67
+ : init;
68
+ const headers = new Headers(responseInit.headers);
69
+ if (!headers.has("Content-Type")) {
70
+ headers.set("Content-Type", "text/plain;charset=UTF-8");
71
+ }
72
+ return new Response(body, { ...responseInit, headers });
73
+ }
74
+ export function created(body, init = {}) {
75
+ return json(body, { ...init, status: HttpStatus.CREATED });
76
+ }
77
+ export function accepted(body, init = {}) {
78
+ return json(body, { ...init, status: HttpStatus.ACCEPTED });
79
+ }
80
+ export function noContent(init = {}) {
81
+ return empty(HttpStatus.NO_CONTENT, init);
82
+ }
83
+ export function badRequest(message = "Bad request", details) {
84
+ return jsonError(HttpStatus.BAD_REQUEST, message, details);
85
+ }
86
+ export function unauthorized(message = "Unauthorized", details) {
87
+ return jsonError(HttpStatus.UNAUTHORIZED, message, details);
88
+ }
89
+ export function forbidden(message = "Forbidden", details) {
90
+ return jsonError(HttpStatus.FORBIDDEN, message, details);
91
+ }
92
+ export function notFound(message = "Not found", details) {
93
+ return jsonError(HttpStatus.NOT_FOUND, message, details);
94
+ }
95
+ export function methodNotAllowed(allowedMethods, message = "Method not allowed") {
96
+ return jsonError(HttpStatus.METHOD_NOT_ALLOWED, message, undefined, {
97
+ headers: { Allow: [...allowedMethods].sort().join(", ") },
98
+ });
99
+ }
100
+ export function conflict(message = "Conflict", details) {
101
+ return jsonError(HttpStatus.CONFLICT, message, details);
102
+ }
103
+ export function gone(message = "Gone", details) {
104
+ return jsonError(HttpStatus.GONE, message, details);
105
+ }
106
+ export function payloadTooLarge(message = "Payload too large", details) {
107
+ return jsonError(HttpStatus.PAYLOAD_TOO_LARGE, message, details);
108
+ }
109
+ export function unsupportedMediaType(message = "Unsupported media type", details) {
110
+ return jsonError(HttpStatus.UNSUPPORTED_MEDIA_TYPE, message, details);
111
+ }
112
+ export function unprocessableContent(message = "Unprocessable content", details) {
113
+ return jsonError(HttpStatus.UNPROCESSABLE_CONTENT, message, details);
114
+ }
115
+ export function tooManyRequests(input = {}) {
116
+ const headers = input.retryAfterSeconds === undefined
117
+ ? undefined
118
+ : { "Retry-After": String(input.retryAfterSeconds) };
119
+ return json({
120
+ error: input.message ?? "Rate limit exceeded",
121
+ ...(input.retryAfterSeconds === undefined
122
+ ? {}
123
+ : { retryAfterSeconds: input.retryAfterSeconds }),
124
+ }, { status: HttpStatus.TOO_MANY_REQUESTS, headers });
125
+ }
126
+ export function internalServerError(message = "Internal server error", details) {
127
+ return jsonError(HttpStatus.INTERNAL_SERVER_ERROR, message, details);
128
+ }
129
+ export function badGateway(message = "Bad gateway", details) {
130
+ return jsonError(HttpStatus.BAD_GATEWAY, message, details);
131
+ }
132
+ export function serviceUnavailable(message = "Service unavailable", details) {
133
+ return jsonError(HttpStatus.SERVICE_UNAVAILABLE, message, details);
134
+ }
135
+ export function gatewayTimeout(message = "Gateway timeout", details) {
136
+ return jsonError(HttpStatus.GATEWAY_TIMEOUT, message, details);
137
+ }
138
+ export function getStandaloneServe() {
139
+ const runtime = globalThis;
140
+ const standaloneServe = runtime.Deno?.serve;
141
+ return typeof standaloneServe === "function"
142
+ ? standaloneServe.bind(runtime.Deno)
143
+ : null;
144
+ }
145
+ export function normalizeRoutePath(path) {
146
+ const trimmed = path.trim();
147
+ if (!trimmed || trimmed === "/")
148
+ return "/";
149
+ return `/${trimmed.replace(/^\/+|\/+$/g, "")}`;
150
+ }
151
+ export function splitPath(path) {
152
+ return path.split("/").filter(Boolean);
153
+ }
154
+ export function getRelativePath(req, functionName) {
155
+ const parts = splitPath(new URL(req.url).pathname);
156
+ return getRelativePathFromParts(parts, functionName);
157
+ }
158
+ export function getRelativePathFromParts(pathnameParts, functionName, pathPrefix) {
159
+ const prefixParts = pathPrefix ? splitPath(pathPrefix) : ["functions", "v1"];
160
+ const hasPrefix = prefixParts.length > 0 &&
161
+ prefixParts.every((part, index) => pathnameParts[index] === part);
162
+ const parts = hasPrefix
163
+ ? pathnameParts.slice(prefixParts.length)
164
+ : pathnameParts;
165
+ if (parts[0] === functionName) {
166
+ return normalizeRoutePath(parts.slice(1).join("/"));
167
+ }
168
+ if (pathnameParts[0] === "functions" && pathnameParts[1] === "v1" &&
169
+ pathnameParts[2] === functionName) {
170
+ return normalizeRoutePath(pathnameParts.slice(3).join("/"));
171
+ }
172
+ return normalizeRoutePath(parts.join("/"));
173
+ }
174
+ export function matchRoutePath(routePath, requestPath) {
175
+ const routeParts = splitPath(routePath);
176
+ const requestParts = splitPath(requestPath);
177
+ if (routeParts.length !== requestParts.length)
178
+ return null;
179
+ const params = {};
180
+ for (let i = 0; i < routeParts.length; i++) {
181
+ const expected = routeParts[i];
182
+ const actual = requestParts[i];
183
+ if (expected.startsWith(":")) {
184
+ params[expected.slice(1)] = decodeURIComponent(actual);
185
+ continue;
186
+ }
187
+ if (expected !== actual)
188
+ return null;
189
+ }
190
+ return params;
191
+ }
192
+ export function findRoute(config, req) {
193
+ const requestPath = getRelativePathFromParts(splitPath(new URL(req.url).pathname), config.name, config.pathPrefix);
194
+ const pathMatches = config.routes
195
+ .map((routeDef) => ({
196
+ route: routeDef,
197
+ params: matchRoutePath(routeDef.path, requestPath),
198
+ }))
199
+ .filter((match) => match.params !== null);
200
+ if (pathMatches.length === 0) {
201
+ return notFound();
202
+ }
203
+ const allowedMethods = [
204
+ ...new Set(pathMatches.map((match) => match.route.method)),
205
+ ].sort();
206
+ if (req.method === "OPTIONS") {
207
+ return new Response(null, {
208
+ status: HttpStatus.NO_CONTENT,
209
+ headers: { Allow: allowedMethods.join(", ") },
210
+ });
211
+ }
212
+ const method = req.method.toUpperCase();
213
+ const match = pathMatches.find((candidate) => candidate.route.method === method);
214
+ if (!match) {
215
+ return methodNotAllowed(allowedMethods);
216
+ }
217
+ return match;
218
+ }
219
+ export function queryObject(req) {
220
+ return Object.fromEntries(new URL(req.url).searchParams.entries());
221
+ }
222
+ export async function parseJsonBody(req) {
223
+ try {
224
+ return await req.json();
225
+ }
226
+ catch {
227
+ throw new Error("Invalid JSON body");
228
+ }
229
+ }
230
+ export function flattenSchemaError(error) {
231
+ if (error &&
232
+ typeof error === "object" &&
233
+ "flatten" in error &&
234
+ typeof error.flatten === "function") {
235
+ return error.flatten();
236
+ }
237
+ return error;
238
+ }
239
+ export function validateSchema(schema, value, label) {
240
+ if (!schema)
241
+ return value;
242
+ const parsed = schema.safeParse(value);
243
+ if (!parsed.success) {
244
+ return jsonError(HttpStatus.BAD_REQUEST, `Invalid ${label}`, flattenSchemaError(parsed.error));
245
+ }
246
+ return parsed.data;
247
+ }
248
+ export function withCors(req, response, getCorsHeaders) {
249
+ if (!getCorsHeaders)
250
+ return response;
251
+ const headers = new Headers(response.headers);
252
+ for (const [key, value] of new Headers(getCorsHeaders(req)).entries()) {
253
+ headers.set(key, value);
254
+ }
255
+ return new Response(response.body, {
256
+ status: response.status,
257
+ statusText: response.statusText,
258
+ headers,
259
+ });
260
+ }
261
+ export async function dispatchRoute(input) {
262
+ const { req, ctx, matched } = input;
263
+ const { route: routeDef, params: rawParams } = matched;
264
+ const params = validateSchema(routeDef.params, rawParams, "params");
265
+ if (params instanceof Response) {
266
+ return withCors(req, params, input.getCorsHeaders);
267
+ }
268
+ const query = validateSchema(routeDef.query, queryObject(req), "query");
269
+ if (query instanceof Response) {
270
+ return withCors(req, query, input.getCorsHeaders);
271
+ }
272
+ const rawBody = routeDef.body ? await parseJsonBody(req) : undefined;
273
+ const body = validateSchema(routeDef.body, rawBody, "body");
274
+ if (body instanceof Response) {
275
+ return withCors(req, body, input.getCorsHeaders);
276
+ }
277
+ return withCors(req, await routeDef.handler({ req, ctx, params, query, body }), input.getCorsHeaders);
278
+ }
279
+ async function defaultOnError(error) {
280
+ const message = error instanceof Error && error.message === "Invalid JSON body"
281
+ ? "Invalid JSON body"
282
+ : "Internal server error";
283
+ return message === "Invalid JSON body"
284
+ ? badRequest(message)
285
+ : internalServerError(message);
286
+ }
287
+ export function createFetchHandler(config, options) {
288
+ return async (req) => {
289
+ try {
290
+ const matched = findRoute(config, req);
291
+ if (matched instanceof Response) {
292
+ const response = withCors(req, matched, options.getCorsHeaders);
293
+ return options.afterResponse
294
+ ? await options.afterResponse(response, req)
295
+ : response;
296
+ }
297
+ const context = await options.getContext(req);
298
+ if (context instanceof Response) {
299
+ const response = withCors(req, context, options.getCorsHeaders);
300
+ return options.afterResponse
301
+ ? await options.afterResponse(response, req)
302
+ : response;
303
+ }
304
+ const response = await dispatchRoute({
305
+ req,
306
+ ctx: context,
307
+ matched,
308
+ getCorsHeaders: options.getCorsHeaders,
309
+ });
310
+ return options.afterResponse
311
+ ? await options.afterResponse(response, req)
312
+ : response;
313
+ }
314
+ catch (error) {
315
+ const response = withCors(req, options.onError
316
+ ? await options.onError(error, req)
317
+ : await defaultOnError(error), options.getCorsHeaders);
318
+ return options.afterResponse
319
+ ? await options.afterResponse(response, req)
320
+ : response;
321
+ }
322
+ };
323
+ }
324
+ function schemaToOpenApi(schema, adapter) {
325
+ if (!schema || !adapter)
326
+ return undefined;
327
+ return adapter.toJsonSchema(schema);
328
+ }
329
+ function pathToOpenApiPath(path) {
330
+ return normalizeRoutePath(path).replace(/:([^/]+)/g, "{$1}");
331
+ }
332
+ function pathParameterNames(path) {
333
+ return splitPath(path)
334
+ .filter((part) => part.startsWith(":"))
335
+ .map((part) => part.slice(1));
336
+ }
337
+ function objectSchemaProperties(schema) {
338
+ if (schema &&
339
+ typeof schema === "object" &&
340
+ "properties" in schema &&
341
+ typeof schema.properties === "object" &&
342
+ schema.properties !== null) {
343
+ return schema.properties;
344
+ }
345
+ return {};
346
+ }
347
+ function objectSchemaRequired(schema) {
348
+ if (schema &&
349
+ typeof schema === "object" &&
350
+ "required" in schema &&
351
+ Array.isArray(schema.required)) {
352
+ return new Set(schema.required);
353
+ }
354
+ return new Set();
355
+ }
356
+ function responseObject(status, schema) {
357
+ if (status === String(HttpStatus.NO_CONTENT) || !schema) {
358
+ return { description: `${status} response` };
359
+ }
360
+ return {
361
+ description: `${status} response`,
362
+ content: {
363
+ "application/json": { schema },
364
+ },
365
+ };
366
+ }
367
+ export function createOpenApiDocument(config, options) {
368
+ const paths = {};
369
+ for (const routeDef of config.routes) {
370
+ const path = pathToOpenApiPath(routeDef.path);
371
+ const pathItem = paths[path] ?? {};
372
+ const paramsSchema = schemaToOpenApi(routeDef.params, options.schemaAdapter);
373
+ const paramsProperties = objectSchemaProperties(paramsSchema);
374
+ const parameters = pathParameterNames(routeDef.path).map((name) => ({
375
+ name,
376
+ in: "path",
377
+ required: true,
378
+ schema: paramsProperties[name] ?? { type: "string" },
379
+ }));
380
+ const querySchema = schemaToOpenApi(routeDef.query, options.schemaAdapter);
381
+ if (querySchema) {
382
+ const queryProperties = objectSchemaProperties(querySchema);
383
+ const queryRequired = objectSchemaRequired(querySchema);
384
+ for (const [name, schema] of Object.entries(queryProperties)) {
385
+ parameters.push({
386
+ name,
387
+ in: "query",
388
+ required: queryRequired.has(name),
389
+ schema,
390
+ });
391
+ }
392
+ if (Object.keys(queryProperties).length === 0) {
393
+ parameters.push({
394
+ name: "query",
395
+ in: "query",
396
+ required: false,
397
+ schema: querySchema,
398
+ });
399
+ }
400
+ }
401
+ pathItem[routeDef.method.toLowerCase()] = {
402
+ operationId: routeDef.operationId,
403
+ ...(routeDef.summary ? { summary: routeDef.summary } : {}),
404
+ ...(routeDef.description ? { description: routeDef.description } : {}),
405
+ ...(parameters.length > 0 ? { parameters } : {}),
406
+ ...(routeDef.body
407
+ ? {
408
+ requestBody: {
409
+ required: true,
410
+ content: {
411
+ "application/json": {
412
+ schema: schemaToOpenApi(routeDef.body, options.schemaAdapter) ??
413
+ {},
414
+ },
415
+ },
416
+ },
417
+ }
418
+ : {}),
419
+ responses: Object.fromEntries(Object.entries(routeDef.responses).map(([status, schema]) => [
420
+ status,
421
+ responseObject(status, schemaToOpenApi(schema, options.schemaAdapter)),
422
+ ])),
423
+ };
424
+ paths[path] = pathItem;
425
+ }
426
+ return {
427
+ openapi: "3.1.0",
428
+ info: {
429
+ title: options.title,
430
+ version: options.version,
431
+ },
432
+ ...(options.servers ? { servers: options.servers } : {}),
433
+ paths,
434
+ };
435
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "1tube",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "private": false,
5
5
  "description": "Self-hosted Supabase Edge Functions gateway for Deno, plus an ESLint plugin for consumers",
6
6
  "license": "MIT",
@@ -14,8 +14,16 @@
14
14
  "url": "https://github.com/lofcz/1tube/issues"
15
15
  },
16
16
  "exports": {
17
- "./edge": "./src/edge.ts",
18
- "./edge-caller": "./src/edge-caller.ts",
17
+ "./edge": {
18
+ "types": "./dist/edge.d.ts",
19
+ "import": "./dist/edge.js",
20
+ "default": "./dist/edge.js"
21
+ },
22
+ "./edge-caller": {
23
+ "types": "./dist/edge-caller.d.ts",
24
+ "import": "./dist/edge-caller.js",
25
+ "default": "./dist/edge-caller.js"
26
+ },
19
27
  "./eslint-plugin": "./eslint-plugin.js",
20
28
  "./package.json": "./package.json"
21
29
  },
@@ -24,6 +32,7 @@
24
32
  },
25
33
  "files": [
26
34
  "eslint-plugin.js",
35
+ "dist",
27
36
  "scripts/npm-cli.js",
28
37
  "src",
29
38
  "deno.json",
@@ -38,10 +47,12 @@
38
47
  "scripts": {
39
48
  "dev": "deno task dev",
40
49
  "serve": "deno task serve",
50
+ "build:edge": "deno run -A npm:typescript@latest/tsc --target ES2022 --module ESNext --moduleResolution Bundler --declaration --outDir dist src/edge.ts src/edge-caller.ts",
51
+ "prepack": "bun run build:edge",
41
52
  "typecheck": "deno check src/server.ts src/cli.ts src/edge.ts src/edge-caller.ts eslint-plugin.js",
42
53
  "check": "deno cache --allow-scripts=npm:protobufjs src/server.ts",
43
54
  "smoke:bin": "node ./scripts/npm-cli.js --version",
44
- "prepublishOnly": "bun run typecheck && bun run check && bun run smoke:bin"
55
+ "prepublishOnly": "bun run build:edge && bun run typecheck && bun run check && bun run smoke:bin"
45
56
  },
46
57
  "dependencies": {
47
58
  "hono": "^4.12.18"