1tube 0.1.3 → 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/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.3",
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
@@ -19,6 +19,19 @@ import { createRateLimiter } from "./gateway/rate-limit.ts";
19
19
  import { createHealthHandler, createMetricsHandler } from "./health.ts";
20
20
  import { VERSION } from "./version.ts";
21
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
+ });
34
+
22
35
  // ---------------------------------------------------------------------------
23
36
  // CLI args & env
24
37
  // ---------------------------------------------------------------------------
@@ -72,6 +85,7 @@ function parseArgs(): { port: number; functionsPath: string; defaultTimeoutMs: n
72
85
  // ---------------------------------------------------------------------------
73
86
 
74
87
  const { port, functionsPath, defaultTimeoutMs } = parseArgs();
88
+ const internalKey = Deno.env.get("INTERNAL_KEY")?.trim() || undefined;
75
89
 
76
90
  applyLocalDefaults();
77
91
 
@@ -181,7 +195,16 @@ app.all("/functions/v1/:name{.+}", async (c) => {
181
195
  const originalUrl = new URL(c.req.raw.url);
182
196
  const rewrittenPath = originalUrl.pathname.replace(/^\/functions\/v1/, "") || "/";
183
197
  const rewrittenUrl = new URL(rewrittenPath + originalUrl.search, originalUrl.origin);
184
- 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
+ });
185
208
 
186
209
  let handlerPromise: Promise<Response>;
187
210
 
@@ -199,7 +222,10 @@ app.all("/functions/v1/:name{.+}", async (c) => {
199
222
  try {
200
223
  if (timeoutMs > 0) {
201
224
  const timeout = new Promise<never>((_, reject) =>
202
- 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),
203
229
  );
204
230
  return await Promise.race([handlerPromise, timeout]);
205
231
  }
@@ -215,8 +241,8 @@ app.all("/functions/v1/:name{.+}", async (c) => {
215
241
  });
216
242
 
217
243
  // Health & observability
218
- app.get("/health", createHealthHandler(registry));
219
- app.get("/metrics", createMetricsHandler());
244
+ app.get("/health", createHealthHandler(registry, internalKey));
245
+ app.get("/metrics", createMetricsHandler(internalKey));
220
246
 
221
247
  // Root info
222
248
  app.get("/", (c) =>