1tube 0.1.2 → 0.1.4

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/.env.example CHANGED
@@ -18,6 +18,7 @@ TAVILY_API_KEY=
18
18
  # Gateway config
19
19
  PORT=3100
20
20
  FUNCTIONS_PATH=../sciobot-next/supabase/functions
21
+ # INTERNAL_KEY=00000000-0000-0000-0000-000000000000 # optional, random GUID is fine
21
22
 
22
23
  # Optional: Sentry error tracking
23
24
  # SENTRY_DSN=
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ ![NPM Version](https://img.shields.io/npm/v/1tube)
2
+
1
3
  # 1tube
2
4
 
3
5
  Self-hosted Supabase Edge Functions gateway. Runs your Deno edge functions locally or behind a .NET host with zero-copy YARP proxying — no cold starts, no version lock, no Supabase compute dependency.
package/eslint-plugin.js CHANGED
@@ -57,6 +57,51 @@ const plugin = {
57
57
  },
58
58
  },
59
59
 
60
+ "require-access-policy": {
61
+ meta: {
62
+ type: "problem",
63
+ docs: {
64
+ description:
65
+ "Require every serve() call to declare an access policy via the `require` option",
66
+ },
67
+ messages: {
68
+ missingRequire:
69
+ 'serve() must declare an access policy. Add { require: "public" }, { require: ["authenticated"] }, or { require: ["isAdminOrHigher"] } etc.',
70
+ },
71
+ schema: [],
72
+ },
73
+ create(context) {
74
+ return {
75
+ CallExpression(node) {
76
+ if (
77
+ node.callee.type !== "Identifier" ||
78
+ node.callee.name !== "serve"
79
+ ) {
80
+ return;
81
+ }
82
+
83
+ const opts = node.arguments[1];
84
+
85
+ if (!opts || opts.type !== "ObjectExpression") {
86
+ context.report({ node, messageId: "missingRequire" });
87
+ return;
88
+ }
89
+
90
+ const hasRequire = opts.properties.some(
91
+ (p) =>
92
+ p.type === "Property" &&
93
+ p.key.type === "Identifier" &&
94
+ p.key.name === "require",
95
+ );
96
+
97
+ if (!hasRequire) {
98
+ context.report({ node, messageId: "missingRequire" });
99
+ }
100
+ },
101
+ };
102
+ },
103
+ },
104
+
60
105
  "no-direct-edge-url": {
61
106
  meta: {
62
107
  type: "problem",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "1tube",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",
package/src/health.ts CHANGED
@@ -6,20 +6,41 @@ import type { Context } from "npm:hono@4";
6
6
  import type { FunctionRegistry } from "./registry.ts";
7
7
  import { getCollectedMetrics, getPrometheusMetrics } from "./gateway/logging.ts";
8
8
 
9
- export function createHealthHandler(registry: FunctionRegistry) {
9
+ function hasValidKey(c: Context, secretKey?: string): boolean {
10
+ return !!secretKey && c.req.query("key") === secretKey;
11
+ }
12
+
13
+ export function createHealthHandler(
14
+ registry: FunctionRegistry,
15
+ secretKey?: string,
16
+ ) {
10
17
  return (c: Context) => {
18
+ if (!hasValidKey(c, secretKey)) {
19
+ return c.json({ status: "ok" });
20
+ }
21
+
11
22
  const metrics = getCollectedMetrics();
23
+ const mem = Deno.memoryUsage();
12
24
  return c.json({
13
25
  status: "ok",
14
26
  function_count: registry.size,
15
27
  functionList: registry.list(),
28
+ memory: {
29
+ rss_mb: Math.round(mem.rss / 1024 / 1024),
30
+ heap_used_mb: Math.round(mem.heapUsed / 1024 / 1024),
31
+ heap_total_mb: Math.round(mem.heapTotal / 1024 / 1024),
32
+ },
16
33
  ...metrics,
17
34
  });
18
35
  };
19
36
  }
20
37
 
21
- export function createMetricsHandler() {
38
+ export function createMetricsHandler(secretKey?: string) {
22
39
  return (c: Context) => {
40
+ if (!hasValidKey(c, secretKey)) {
41
+ return c.json({ error: "Forbidden" }, 403);
42
+ }
43
+
23
44
  return new Response(getPrometheusMetrics(), {
24
45
  headers: { "Content-Type": "text/plain; version=0.0.4; charset=utf-8" },
25
46
  });
package/src/server.ts CHANGED
@@ -17,6 +17,20 @@ import { corsMiddleware } from "./gateway/cors.ts";
17
17
  import { loggingMiddleware } from "./gateway/logging.ts";
18
18
  import { createRateLimiter } from "./gateway/rate-limit.ts";
19
19
  import { createHealthHandler, createMetricsHandler } from "./health.ts";
20
+ import { VERSION } from "./version.ts";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Process-level error handlers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ globalThis.addEventListener("unhandledrejection", (e) => {
27
+ e.preventDefault();
28
+ console.error("[1tube] Unhandled promise rejection:", e.reason);
29
+ });
30
+
31
+ globalThis.addEventListener("error", (e) => {
32
+ console.error("[1tube] Uncaught error:", e.error ?? e.message);
33
+ });
20
34
 
21
35
  // ---------------------------------------------------------------------------
22
36
  // CLI args & env
@@ -71,9 +85,17 @@ function parseArgs(): { port: number; functionsPath: string; defaultTimeoutMs: n
71
85
  // ---------------------------------------------------------------------------
72
86
 
73
87
  const { port, functionsPath, defaultTimeoutMs } = parseArgs();
88
+ const internalKey = Deno.env.get("INTERNAL_KEY")?.trim() || undefined;
74
89
 
75
90
  applyLocalDefaults();
76
91
 
92
+ const denoVersion = `${Deno.version.deno} (V8 ${Deno.version.v8}, TS ${Deno.version.typescript})`;
93
+ const bannerText = ` 1tube v${VERSION} · Deno ${denoVersion} `;
94
+ const bannerLine = "─".repeat(bannerText.length);
95
+ console.log(`\x1b[36m┌${bannerLine}┐\x1b[0m`);
96
+ console.log(`\x1b[36m│\x1b[0m \x1b[1m1tube\x1b[0m v${VERSION} \x1b[2m·\x1b[0m Deno ${denoVersion} \x1b[36m│\x1b[0m`);
97
+ console.log(`\x1b[36m└${bannerLine}┘\x1b[0m`);
98
+
77
99
  const registry = new FunctionRegistry();
78
100
  (globalThis as any).__edgeFunctionRegistry = registry;
79
101
 
@@ -173,7 +195,16 @@ app.all("/functions/v1/:name{.+}", async (c) => {
173
195
  const originalUrl = new URL(c.req.raw.url);
174
196
  const rewrittenPath = originalUrl.pathname.replace(/^\/functions\/v1/, "") || "/";
175
197
  const rewrittenUrl = new URL(rewrittenPath + originalUrl.search, originalUrl.origin);
176
- const rewrittenReq = new Request(rewrittenUrl.toString(), c.req.raw);
198
+
199
+ const abort = new AbortController();
200
+ const rewrittenReq = new Request(rewrittenUrl.toString(), {
201
+ method: c.req.raw.method,
202
+ headers: c.req.raw.headers,
203
+ body: c.req.raw.body,
204
+ signal: abort.signal,
205
+ // @ts-ignore -- Deno supports duplex on Request
206
+ duplex: "half",
207
+ });
177
208
 
178
209
  let handlerPromise: Promise<Response>;
179
210
 
@@ -191,7 +222,10 @@ app.all("/functions/v1/:name{.+}", async (c) => {
191
222
  try {
192
223
  if (timeoutMs > 0) {
193
224
  const timeout = new Promise<never>((_, reject) =>
194
- setTimeout(() => reject(new Error(`Function "${name}" timed out after ${timeoutMs}ms`)), timeoutMs),
225
+ setTimeout(() => {
226
+ abort.abort();
227
+ reject(new Error(`Function "${name}" timed out after ${timeoutMs}ms`));
228
+ }, timeoutMs),
195
229
  );
196
230
  return await Promise.race([handlerPromise, timeout]);
197
231
  }
@@ -207,8 +241,8 @@ app.all("/functions/v1/:name{.+}", async (c) => {
207
241
  });
208
242
 
209
243
  // Health & observability
210
- app.get("/health", createHealthHandler(registry));
211
- app.get("/metrics", createMetricsHandler());
244
+ app.get("/health", createHealthHandler(registry, internalKey));
245
+ app.get("/metrics", createMetricsHandler(internalKey));
212
246
 
213
247
  // Root info
214
248
  app.get("/", (c) =>
package/src/version.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { join, fromFileUrl } from "jsr:@std/path@^1";
2
+
3
+ function readVersion(): string {
4
+ try {
5
+ const dir = fromFileUrl(new URL(".", import.meta.url));
6
+ const raw = Deno.readTextFileSync(join(dir, "..", "package.json"));
7
+ return JSON.parse(raw).version ?? "unknown";
8
+ } catch {
9
+ return "unknown";
10
+ }
11
+ }
12
+
13
+ export const VERSION = readVersion();