@secondlayer/shared 0.6.1 → 0.7.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/src/crypto/hmac.js +1 -2
- package/dist/src/crypto/hmac.js.map +2 -2
- package/dist/src/db/index.d.ts +3 -2
- package/dist/src/db/index.js +3 -2
- package/dist/src/db/index.js.map +3 -3
- package/dist/src/db/jsonb.js +1 -2
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/accounts.d.ts +4 -3
- package/dist/src/db/queries/accounts.js +6 -4
- package/dist/src/db/queries/accounts.js.map +3 -3
- package/dist/src/db/queries/integrity.d.ts +3 -2
- package/dist/src/db/queries/integrity.js +1 -2
- package/dist/src/db/queries/integrity.js.map +2 -2
- package/dist/src/db/queries/metrics.d.ts +3 -2
- package/dist/src/db/queries/metrics.js +1 -2
- package/dist/src/db/queries/metrics.js.map +2 -2
- package/dist/src/db/queries/subgraphs.d.ts +3 -2
- package/dist/src/db/queries/subgraphs.js +2 -3
- package/dist/src/db/queries/subgraphs.js.map +3 -3
- package/dist/src/db/queries/usage.d.ts +11 -3
- package/dist/src/db/queries/usage.js +23 -2
- package/dist/src/db/queries/usage.js.map +3 -3
- package/dist/src/db/schema.d.ts +3 -2
- package/dist/src/env.js +1 -2
- package/dist/src/env.js.map +2 -2
- package/dist/src/errors.d.ts +19 -3
- package/dist/src/errors.js +23 -2
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +22 -5
- package/dist/src/index.js +25 -2
- package/dist/src/index.js.map +4 -4
- package/dist/src/lib/plans.js +1 -2
- package/dist/src/lib/plans.js.map +2 -2
- package/dist/src/logger.js +1 -2
- package/dist/src/logger.js.map +2 -2
- package/dist/src/node/archive-client.d.ts +14 -1
- package/dist/src/node/archive-client.js +104 -21
- package/dist/src/node/archive-client.js.map +3 -3
- package/dist/src/node/client.js +1 -2
- package/dist/src/node/client.js.map +2 -2
- package/dist/src/node/hiro-client.js +1 -2
- package/dist/src/node/hiro-client.js.map +2 -2
- package/dist/src/node/hiro-pg-client.js +1 -2
- package/dist/src/node/hiro-pg-client.js.map +2 -2
- package/dist/src/node/local-client.d.ts +3 -2
- package/dist/src/node/local-client.js +1 -2
- package/dist/src/node/local-client.js.map +2 -2
- package/dist/src/queue/index.js +3 -2
- package/dist/src/queue/index.js.map +3 -3
- package/dist/src/queue/listener.js +1 -2
- package/dist/src/queue/listener.js.map +2 -2
- package/dist/src/queue/recovery.js +3 -2
- package/dist/src/queue/recovery.js.map +3 -3
- package/dist/src/schemas/filters.js +1 -2
- package/dist/src/schemas/filters.js.map +2 -2
- package/dist/src/schemas/index.js +1 -2
- package/dist/src/schemas/index.js.map +2 -2
- package/dist/src/schemas/subgraphs.js +1 -2
- package/dist/src/schemas/subgraphs.js.map +2 -2
- package/dist/src/types.d.ts +1 -1
- package/migrations/0017_security_hardening.ts +20 -0
- package/package.json +1 -1
package/dist/src/crypto/hmac.js
CHANGED
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/crypto/hmac.ts
|
|
19
18
|
var exports_hmac = {};
|
|
@@ -76,5 +75,5 @@ export {
|
|
|
76
75
|
createSignatureHeader
|
|
77
76
|
};
|
|
78
77
|
|
|
79
|
-
//# debugId=
|
|
78
|
+
//# debugId=A51CF8494ADEA11C64756E2164756E21
|
|
80
79
|
//# sourceMappingURL=hmac.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { createHmac, randomBytes } from \"crypto\";\n\n/**\n * Generate a random secret for delivery signing\n * Returns 32 bytes as a 64-character hex string\n */\nexport function generateSecret(): string {\n return randomBytes(32).toString(\"hex\");\n}\n\n/**\n * Sign a payload with HMAC-SHA256\n * Returns the signature as a hex string\n */\nexport function signPayload(payload: string, secret: string): string {\n const hmac = createHmac(\"sha256\", secret);\n hmac.update(payload);\n return hmac.digest(\"hex\");\n}\n\n/**\n * Verify an HMAC signature\n * Uses constant-time comparison to prevent timing attacks\n */\nexport function verifySignature(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = signPayload(payload, secret);\n\n // Constant-time comparison\n if (signature.length !== expectedSignature.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < signature.length; i++) {\n result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n }\n\n return result === 0;\n}\n\n/**\n * Create a Stripe-style signature header\n * Format: t=timestamp,v1=signature\n */\nexport function createSignatureHeader(\n payload: string,\n secret: string,\n timestamp?: number\n): string {\n const ts = timestamp ?? Math.floor(Date.now() / 1000);\n const signedPayload = `${ts}.${payload}`;\n const signature = signPayload(signedPayload, secret);\n\n return `t=${ts},v1=${signature}`;\n}\n\n/**\n * Parse and verify a Stripe-style signature header\n * Returns true if valid, false otherwise\n */\nexport function verifySignatureHeader(\n payload: string,\n header: string,\n secret: string,\n toleranceSeconds = 300 // 5 minutes\n): boolean {\n // Parse header\n const parts = header.split(\",\");\n const timestamp = parts\n .find((p) => p.startsWith(\"t=\"))\n ?.slice(2);\n const signature = parts\n .find((p) => p.startsWith(\"v1=\"))\n ?.slice(3);\n\n if (!timestamp || !signature) {\n return false;\n }\n\n const ts = parseInt(timestamp, 10);\n if (isNaN(ts)) {\n return false;\n }\n\n // Check timestamp is within tolerance\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - ts) > toleranceSeconds) {\n return false;\n }\n\n // Verify signature\n const signedPayload = `${ts}.${payload}`;\n return verifySignature(signedPayload, signature, secret);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAMO,SAAS,cAAc,GAAW;AAAA,EACvC,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA;AAOhC,SAAS,WAAW,CAAC,SAAiB,QAAwB;AAAA,EACnE,MAAM,OAAO,WAAW,UAAU,MAAM;AAAA,EACxC,KAAK,OAAO,OAAO;AAAA,EACnB,OAAO,KAAK,OAAO,KAAK;AAAA;AAOnB,SAAS,eAAe,CAC7B,SACA,WACA,QACS;AAAA,EACT,MAAM,oBAAoB,YAAY,SAAS,MAAM;AAAA,EAGrD,IAAI,UAAU,WAAW,kBAAkB,QAAQ;AAAA,IACjD,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAAS;AAAA,EACb,SAAS,IAAI,EAAG,IAAI,UAAU,QAAQ,KAAK;AAAA,IACzC,UAAU,UAAU,WAAW,CAAC,IAAI,kBAAkB,WAAW,CAAC;AAAA,EACpE;AAAA,EAEA,OAAO,WAAW;AAAA;AAOb,SAAS,qBAAqB,CACnC,SACA,QACA,WACQ;AAAA,EACR,MAAM,KAAK,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACpD,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,MAAM,YAAY,YAAY,eAAe,MAAM;AAAA,EAEnD,OAAO,KAAK,SAAS;AAAA;AAOhB,SAAS,qBAAqB,CACnC,SACA,QACA,QACA,mBAAmB,KACV;AAAA,EAET,MAAM,QAAQ,OAAO,MAAM,GAAG;AAAA,EAC9B,MAAM,YAAY,MACf,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,GAC7B,MAAM,CAAC;AAAA,EACX,MAAM,YAAY,MACf,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAC9B,MAAM,CAAC;AAAA,EAEX,IAAI,CAAC,aAAa,CAAC,WAAW;AAAA,IAC5B,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,SAAS,WAAW,EAAE;AAAA,EACjC,IAAI,MAAM,EAAE,GAAG;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACxC,IAAI,KAAK,IAAI,MAAM,EAAE,IAAI,kBAAkB;AAAA,IACzC,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,OAAO,gBAAgB,eAAe,WAAW,MAAM;AAAA;",
|
|
8
|
+
"debugId": "A51CF8494ADEA11C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/src/db/index.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ interface StreamsTable {
|
|
|
52
52
|
options: Generated<unknown>;
|
|
53
53
|
endpoint_url: string;
|
|
54
54
|
signing_secret: string | null;
|
|
55
|
-
api_key_id: string
|
|
55
|
+
api_key_id: string;
|
|
56
56
|
created_at: Generated<Date>;
|
|
57
57
|
updated_at: Generated<Date>;
|
|
58
58
|
}
|
|
@@ -111,7 +111,7 @@ interface SubgraphsTable {
|
|
|
111
111
|
last_error_at: Date | null;
|
|
112
112
|
total_processed: Generated<number>;
|
|
113
113
|
total_errors: Generated<number>;
|
|
114
|
-
api_key_id: string
|
|
114
|
+
api_key_id: string;
|
|
115
115
|
created_at: Generated<Date>;
|
|
116
116
|
updated_at: Generated<Date>;
|
|
117
117
|
}
|
|
@@ -151,6 +151,7 @@ interface MagicLinksTable {
|
|
|
151
151
|
token: string;
|
|
152
152
|
expires_at: Date;
|
|
153
153
|
used_at: Date | null;
|
|
154
|
+
failed_attempts: Generated<number>;
|
|
154
155
|
created_at: Generated<Date>;
|
|
155
156
|
}
|
|
156
157
|
interface UsageDailyTable {
|
package/dist/src/db/index.js
CHANGED
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/db/jsonb.ts
|
|
19
18
|
import { sql } from "kysely";
|
|
@@ -43,7 +42,9 @@ function getDb(connectionString) {
|
|
|
43
42
|
if (!db) {
|
|
44
43
|
const url = connectionString || process.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/streams_dev";
|
|
45
44
|
const isLocal = url.includes("localhost") || url.includes("127.0.0.1") || url.includes("@postgres:");
|
|
45
|
+
const poolMax = parseInt(process.env.DATABASE_POOL_MAX ?? "20", 10);
|
|
46
46
|
rawClient = postgres(url, {
|
|
47
|
+
max: poolMax,
|
|
47
48
|
ssl: isLocal ? undefined : { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" }
|
|
48
49
|
});
|
|
49
50
|
db = new Kysely({
|
|
@@ -76,5 +77,5 @@ export {
|
|
|
76
77
|
closeDb
|
|
77
78
|
};
|
|
78
79
|
|
|
79
|
-
//# debugId=
|
|
80
|
+
//# debugId=EDB0A373449368CA64756E2164756E21
|
|
80
81
|
//# sourceMappingURL=index.js.map
|
package/dist/src/db/index.js.map
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"sources": ["../src/db/jsonb.ts", "../src/db/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { sql, type RawBuilder } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n const escaped = JSON.stringify(value).replace(/'/g, \"''\");\n return sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n if (typeof value === \"string\") {\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as T;\n }\n }\n return (value ?? {}) as T;\n}\n",
|
|
6
|
-
"import { Kysely } from \"kysely\";\nimport { PostgresJSDialect } from \"kysely-postgres-js\";\nimport postgres from \"postgres\";\nimport type { Database } from \"./types.ts\";\n\nlet db: Kysely<Database> | null = null;\nlet rawClient: ReturnType<typeof postgres> | null = null;\n\nexport function getDb(connectionString?: string): Kysely<Database> {\n if (!db) {\n const url = connectionString || process.env.DATABASE_URL || \"postgres://postgres:postgres@localhost:5432/streams_dev\";\n\n // Always use SSL for remote databases, just disable cert verification if needed\n const isLocal = url.includes(\"localhost\") || url.includes(\"127.0.0.1\") || url.includes(\"@postgres:\");\n rawClient = postgres(url, {\n ssl: isLocal ? undefined : { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== \"0\" },\n });\n db = new Kysely<Database>({\n dialect: new PostgresJSDialect({ postgres: rawClient }),\n });\n }\n return db;\n}\n\n/** Raw postgres.js client for dynamic schema DDL (CREATE SCHEMA, DROP, etc.) */\nexport function getRawClient(): ReturnType<typeof postgres> {\n if (!rawClient) getDb();\n return rawClient!;\n}\n\n/** Close the DB connection pool. Call in CLI commands to allow process exit. */\nexport async function closeDb(): Promise<void> {\n if (db) {\n await db.destroy();\n db = null;\n }\n if (rawClient) {\n await rawClient.end();\n rawClient = null;\n }\n}\n\nimport { sql } from \"kysely\";\nexport { sql };\nexport * from \"./types.ts\";\nexport { jsonb, parseJsonb } from \"./jsonb.ts\";\n"
|
|
6
|
+
"import { Kysely } from \"kysely\";\nimport { PostgresJSDialect } from \"kysely-postgres-js\";\nimport postgres from \"postgres\";\nimport type { Database } from \"./types.ts\";\n\nlet db: Kysely<Database> | null = null;\nlet rawClient: ReturnType<typeof postgres> | null = null;\n\nexport function getDb(connectionString?: string): Kysely<Database> {\n if (!db) {\n const url = connectionString || process.env.DATABASE_URL || \"postgres://postgres:postgres@localhost:5432/streams_dev\";\n\n // Always use SSL for remote databases, just disable cert verification if needed\n const isLocal = url.includes(\"localhost\") || url.includes(\"127.0.0.1\") || url.includes(\"@postgres:\");\n const poolMax = parseInt(process.env.DATABASE_POOL_MAX ?? \"20\", 10);\n rawClient = postgres(url, {\n max: poolMax,\n ssl: isLocal ? undefined : { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== \"0\" },\n });\n db = new Kysely<Database>({\n dialect: new PostgresJSDialect({ postgres: rawClient }),\n });\n }\n return db;\n}\n\n/** Raw postgres.js client for dynamic schema DDL (CREATE SCHEMA, DROP, etc.) */\nexport function getRawClient(): ReturnType<typeof postgres> {\n if (!rawClient) getDb();\n return rawClient!;\n}\n\n/** Close the DB connection pool. Call in CLI commands to allow process exit. */\nexport async function closeDb(): Promise<void> {\n if (db) {\n await db.destroy();\n db = null;\n }\n if (rawClient) {\n await rawClient.end();\n rawClient = null;\n }\n}\n\nimport { sql } from \"kysely\";\nexport { sql };\nexport * from \"./types.ts\";\nexport { jsonb, parseJsonb } from \"./jsonb.ts\";\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EACzD,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE,QAAQ,MAAM,IAAI;AAAA,EACxD,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQrC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EACzD,IAAI,OAAO,UAAU,UAAU;AAAA,IAC7B,IAAI;AAAA,MACF,OAAO,KAAK,MAAM,KAAK;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA;AAAA,EAEX;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;;;ACzBpB;AACA;AACA;AA0CA,gBAAS;AAvCT,IAAI,KAA8B;AAClC,IAAI,YAAgD;AAE7C,SAAS,KAAK,CAAC,kBAA6C;AAAA,EACjE,IAAI,CAAC,IAAI;AAAA,IACP,MAAM,MAAM,oBAAoB,QAAQ,IAAI,gBAAgB;AAAA,IAG5D,MAAM,UAAU,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,WAAW,KAAK,IAAI,SAAS,YAAY;AAAA,IACnG,MAAM,UAAU,SAAS,QAAQ,IAAI,qBAAqB,MAAM,EAAE;AAAA,IAClE,YAAY,SAAS,KAAK;AAAA,MACxB,KAAK;AAAA,MACL,KAAK,UAAU,YAAY,EAAE,oBAAoB,QAAQ,IAAI,iCAAiC,IAAI;AAAA,IACpG,CAAC;AAAA,IACD,KAAK,IAAI,OAAiB;AAAA,MACxB,SAAS,IAAI,kBAAkB,EAAE,UAAU,UAAU,CAAC;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;AAIF,SAAS,YAAY,GAAgC;AAAA,EAC1D,IAAI,CAAC;AAAA,IAAW,MAAM;AAAA,EACtB,OAAO;AAAA;AAIT,eAAsB,OAAO,GAAkB;AAAA,EAC7C,IAAI,IAAI;AAAA,IACN,MAAM,GAAG,QAAQ;AAAA,IACjB,KAAK;AAAA,EACP;AAAA,EACA,IAAI,WAAW;AAAA,IACb,MAAM,UAAU,IAAI;AAAA,IACpB,YAAY;AAAA,EACd;AAAA;",
|
|
9
|
+
"debugId": "EDB0A373449368CA64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/src/db/jsonb.js
CHANGED
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/db/jsonb.ts
|
|
19
18
|
import { sql } from "kysely";
|
|
@@ -36,5 +35,5 @@ export {
|
|
|
36
35
|
jsonb
|
|
37
36
|
};
|
|
38
37
|
|
|
39
|
-
//# debugId=
|
|
38
|
+
//# debugId=A6577D9BF54F45FE64756E2164756E21
|
|
40
39
|
//# sourceMappingURL=jsonb.js.map
|
package/dist/src/db/jsonb.js.map
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { sql, type RawBuilder } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n const escaped = JSON.stringify(value).replace(/'/g, \"''\");\n return sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n if (typeof value === \"string\") {\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as T;\n }\n }\n return (value ?? {}) as T;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EACzD,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE,QAAQ,MAAM,IAAI;AAAA,EACxD,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQrC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EACzD,IAAI,OAAO,UAAU,UAAU;AAAA,IAC7B,IAAI;AAAA,MACF,OAAO,KAAK,MAAM,KAAK;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA;AAAA,EAEX;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;",
|
|
8
|
+
"debugId": "A6577D9BF54F45FE64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -38,7 +38,7 @@ interface StreamsTable {
|
|
|
38
38
|
options: Generated<unknown>;
|
|
39
39
|
endpoint_url: string;
|
|
40
40
|
signing_secret: string | null;
|
|
41
|
-
api_key_id: string
|
|
41
|
+
api_key_id: string;
|
|
42
42
|
created_at: Generated<Date>;
|
|
43
43
|
updated_at: Generated<Date>;
|
|
44
44
|
}
|
|
@@ -97,7 +97,7 @@ interface SubgraphsTable {
|
|
|
97
97
|
last_error_at: Date | null;
|
|
98
98
|
total_processed: Generated<number>;
|
|
99
99
|
total_errors: Generated<number>;
|
|
100
|
-
api_key_id: string
|
|
100
|
+
api_key_id: string;
|
|
101
101
|
created_at: Generated<Date>;
|
|
102
102
|
updated_at: Generated<Date>;
|
|
103
103
|
}
|
|
@@ -137,6 +137,7 @@ interface MagicLinksTable {
|
|
|
137
137
|
token: string;
|
|
138
138
|
expires_at: Date;
|
|
139
139
|
used_at: Date | null;
|
|
140
|
+
failed_attempts: Generated<number>;
|
|
140
141
|
created_at: Generated<Date>;
|
|
141
142
|
}
|
|
142
143
|
interface UsageDailyTable {
|
|
@@ -246,7 +247,7 @@ declare function isEmailAllowed(db: Kysely<Database>, email: string): Promise<bo
|
|
|
246
247
|
declare function createMagicLink(db: Kysely<Database>, email: string, token: string, expiresInMs?: number): Promise<void>;
|
|
247
248
|
/**
|
|
248
249
|
* Verify a magic link token. Returns the email if valid, null otherwise.
|
|
249
|
-
* Marks the token as used atomically.
|
|
250
|
+
* Marks the token as used atomically. Rejects after 5 failed attempts.
|
|
250
251
|
*/
|
|
251
252
|
declare function verifyMagicLink(db: Kysely<Database>, token: string): Promise<string | null>;
|
|
252
253
|
export { verifyMagicLink, upsertAccount, isEmailAllowed, getAccountById, createMagicLink };
|
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/db/queries/accounts.ts
|
|
19
18
|
import { sql } from "kysely";
|
|
@@ -40,8 +39,11 @@ async function createMagicLink(db, email, token, expiresInMs = 15 * 60 * 1000) {
|
|
|
40
39
|
}).execute();
|
|
41
40
|
}
|
|
42
41
|
async function verifyMagicLink(db, token) {
|
|
43
|
-
const result = await db.updateTable("magic_links").set({ used_at: new Date }).where("token", "=", token).where("used_at", "is", null).where("expires_at", ">", new Date).returning("email").executeTakeFirst();
|
|
44
|
-
|
|
42
|
+
const result = await db.updateTable("magic_links").set({ used_at: new Date }).where("token", "=", token).where("used_at", "is", null).where("expires_at", ">", new Date).where("failed_attempts", "<", 5).returning("email").executeTakeFirst();
|
|
43
|
+
if (result?.email)
|
|
44
|
+
return result.email;
|
|
45
|
+
await db.updateTable("magic_links").set({ failed_attempts: sql`failed_attempts + 1` }).where("token", "=", token).where("used_at", "is", null).where("expires_at", ">", new Date).execute();
|
|
46
|
+
return null;
|
|
45
47
|
}
|
|
46
48
|
export {
|
|
47
49
|
verifyMagicLink,
|
|
@@ -51,5 +53,5 @@ export {
|
|
|
51
53
|
createMagicLink
|
|
52
54
|
};
|
|
53
55
|
|
|
54
|
-
//# debugId=
|
|
56
|
+
//# debugId=20A55F621EB14D5064756E2164756E21
|
|
55
57
|
//# sourceMappingURL=accounts.js.map
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/db/queries/accounts.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { sql, type Kysely } from \"kysely\";\nimport type { Database, Account } from \"../types.ts\";\n\nexport async function upsertAccount(\n db: Kysely<Database>,\n email: string,\n): Promise<Account> {\n return await db\n .insertInto(\"accounts\")\n .values({ email })\n .onConflict((oc) =>\n oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n )\n .returningAll()\n .executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n db: Kysely<Database>,\n id: string,\n): Promise<Account | null> {\n return (\n (await db\n .selectFrom(\"accounts\")\n .selectAll()\n .where(\"id\", \"=\", id)\n .executeTakeFirst()) ?? null\n );\n}\n\nexport async function isEmailAllowed(\n db: Kysely<Database>,\n email: string,\n): Promise<boolean> {\n const result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n return result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n db: Kysely<Database>,\n email: string,\n token: string,\n expiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n await db\n .insertInto(\"magic_links\")\n .values({\n email,\n token,\n expires_at: new Date(Date.now() + expiresInMs),\n })\n .execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically.\n */\nexport async function verifyMagicLink(\n db: Kysely<Database>,\n token: string,\n): Promise<string | null> {\n const result = await db\n .updateTable(\"magic_links\")\n .set({ used_at: new Date() })\n .where(\"token\", \"=\", token)\n .where(\"used_at\", \"is\", null)\n .where(\"expires_at\", \">\", new Date())\n .returning(\"email\")\n .executeTakeFirst();\n\n
|
|
5
|
+
"import { sql, type Kysely } from \"kysely\";\nimport type { Database, Account } from \"../types.ts\";\n\nexport async function upsertAccount(\n db: Kysely<Database>,\n email: string,\n): Promise<Account> {\n return await db\n .insertInto(\"accounts\")\n .values({ email })\n .onConflict((oc) =>\n oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n )\n .returningAll()\n .executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n db: Kysely<Database>,\n id: string,\n): Promise<Account | null> {\n return (\n (await db\n .selectFrom(\"accounts\")\n .selectAll()\n .where(\"id\", \"=\", id)\n .executeTakeFirst()) ?? null\n );\n}\n\nexport async function isEmailAllowed(\n db: Kysely<Database>,\n email: string,\n): Promise<boolean> {\n const result = await sql<{ found: number }>`\n SELECT 1 AS found FROM accounts WHERE email = ${email}\n UNION ALL\n SELECT 1 AS found FROM waitlist WHERE email = ${email} AND status = 'approved'\n LIMIT 1\n `.execute(db);\n\n return result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n db: Kysely<Database>,\n email: string,\n token: string,\n expiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n await db\n .insertInto(\"magic_links\")\n .values({\n email,\n token,\n expires_at: new Date(Date.now() + expiresInMs),\n })\n .execute();\n}\n\n/**\n * Verify a magic link token. Returns the email if valid, null otherwise.\n * Marks the token as used atomically. Rejects after 5 failed attempts.\n */\nexport async function verifyMagicLink(\n db: Kysely<Database>,\n token: string,\n): Promise<string | null> {\n const result = await db\n .updateTable(\"magic_links\")\n .set({ used_at: new Date() })\n .where(\"token\", \"=\", token)\n .where(\"used_at\", \"is\", null)\n .where(\"expires_at\", \">\", new Date())\n .where(\"failed_attempts\", \"<\", 5)\n .returning(\"email\")\n .executeTakeFirst();\n\n if (result?.email) return result.email;\n\n // Increment failed attempts if token exists but didn't verify\n await db\n .updateTable(\"magic_links\")\n .set({ failed_attempts: sql`failed_attempts + 1` })\n .where(\"token\", \"=\", token)\n .where(\"used_at\", \"is\", null)\n .where(\"expires_at\", \">\", new Date())\n .execute();\n\n return null;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,aAAa,CACjC,IACA,OACkB;AAAA,EAClB,OAAO,MAAM,GACV,WAAW,UAAU,EACrB,OAAO,EAAE,MAAM,CAAC,EAChB,WAAW,CAAC,OACX,GAAG,OAAO,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAC1C,EACC,aAAa,EACb,wBAAwB;AAAA;AAG7B,eAAsB,cAAc,CAClC,IACA,IACyB;AAAA,EACzB,OACG,MAAM,GACJ,WAAW,UAAU,EACrB,UAAU,EACV,MAAM,MAAM,KAAK,EAAE,EACnB,iBAAiB,KAAM;AAAA;AAI9B,eAAsB,cAAc,CAClC,IACA,OACkB;AAAA,EAClB,MAAM,SAAS,MAAM;AAAA,oDAC6B;AAAA;AAAA,oDAEA;AAAA;AAAA,IAEhD,QAAQ,EAAE;AAAA,EAEZ,OAAO,OAAO,KAAK,SAAS;AAAA;AAG9B,eAAsB,eAAe,CACnC,IACA,OACA,OACA,cAAsB,KAAK,KAAK,MACjB;AAAA,EACf,MAAM,GACH,WAAW,aAAa,EACxB,OAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW;AAAA,EAC/C,CAAC,EACA,QAAQ;AAAA;AAOb,eAAsB,eAAe,CACnC,IACA,OACwB;AAAA,EACxB,MAAM,SAAS,MAAM,GAClB,YAAY,aAAa,EACzB,IAAI,EAAE,SAAS,IAAI,KAAO,CAAC,EAC3B,MAAM,SAAS,KAAK,KAAK,EACzB,MAAM,WAAW,MAAM,IAAI,EAC3B,MAAM,cAAc,KAAK,IAAI,IAAM,EACnC,MAAM,mBAAmB,KAAK,CAAC,EAC/B,UAAU,OAAO,EACjB,iBAAiB;AAAA,EAEpB,IAAI,QAAQ;AAAA,IAAO,OAAO,OAAO;AAAA,EAGjC,MAAM,GACH,YAAY,aAAa,EACzB,IAAI,EAAE,iBAAiB,yBAAyB,CAAC,EACjD,MAAM,SAAS,KAAK,KAAK,EACzB,MAAM,WAAW,MAAM,IAAI,EAC3B,MAAM,cAAc,KAAK,IAAI,IAAM,EACnC,QAAQ;AAAA,EAEX,OAAO;AAAA;",
|
|
8
|
+
"debugId": "20A55F621EB14D5064756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -38,7 +38,7 @@ interface StreamsTable {
|
|
|
38
38
|
options: Generated<unknown>;
|
|
39
39
|
endpoint_url: string;
|
|
40
40
|
signing_secret: string | null;
|
|
41
|
-
api_key_id: string
|
|
41
|
+
api_key_id: string;
|
|
42
42
|
created_at: Generated<Date>;
|
|
43
43
|
updated_at: Generated<Date>;
|
|
44
44
|
}
|
|
@@ -97,7 +97,7 @@ interface SubgraphsTable {
|
|
|
97
97
|
last_error_at: Date | null;
|
|
98
98
|
total_processed: Generated<number>;
|
|
99
99
|
total_errors: Generated<number>;
|
|
100
|
-
api_key_id: string
|
|
100
|
+
api_key_id: string;
|
|
101
101
|
created_at: Generated<Date>;
|
|
102
102
|
updated_at: Generated<Date>;
|
|
103
103
|
}
|
|
@@ -137,6 +137,7 @@ interface MagicLinksTable {
|
|
|
137
137
|
token: string;
|
|
138
138
|
expires_at: Date;
|
|
139
139
|
used_at: Date | null;
|
|
140
|
+
failed_attempts: Generated<number>;
|
|
140
141
|
created_at: Generated<Date>;
|
|
141
142
|
}
|
|
142
143
|
interface UsageDailyTable {
|
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/db/queries/integrity.ts
|
|
19
18
|
import { sql } from "kysely";
|
|
@@ -69,5 +68,5 @@ export {
|
|
|
69
68
|
computeContiguousTip
|
|
70
69
|
};
|
|
71
70
|
|
|
72
|
-
//# debugId=
|
|
71
|
+
//# debugId=C8E32B40B73BEACD64756E2164756E21
|
|
73
72
|
//# sourceMappingURL=integrity.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { sql, type Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport interface Gap {\n gapStart: number;\n gapEnd: number;\n size: number;\n}\n\nexport async function findGaps(db: Kysely<Database>, limit?: number): Promise<Gap[]> {\n const limitClause = limit ? sql`LIMIT ${limit}` : sql``;\n const { rows } = await sql<{ gap_start: string; gap_end: string; size: string }>`\n SELECT gap_start, gap_end, gap_end - gap_start + 1 AS size\n FROM (\n SELECT height + 1 AS gap_start, next_height - 1 AS gap_end\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n ) gaps\n ORDER BY gap_start\n ${limitClause}\n `.execute(db);\n\n return rows.map((r) => ({\n gapStart: Number(r.gap_start),\n gapEnd: Number(r.gap_end),\n size: Number(r.size),\n }));\n}\n\nexport async function countMissingBlocks(db: Kysely<Database>): Promise<number> {\n const { rows } = await sql<{ total: string }>`\n SELECT COALESCE(SUM(next_height - height - 1), 0) AS total\n FROM (\n SELECT height, LEAD(height) OVER (ORDER BY height) AS next_height\n FROM blocks WHERE canonical = true\n ) sub\n WHERE next_height - height > 1\n `.execute(db);\n\n return Number(rows[0]?.total ?? 0);\n}\n\nexport async function computeContiguousTip(db: Kysely<Database>, fromHeight: number): Promise<number> {\n const { rows } = await sql<{ tip: string }>`\n SELECT COALESCE(MAX(height), ${fromHeight}) AS tip\n FROM (\n SELECT height, height - ROW_NUMBER() OVER (ORDER BY height) AS grp\n FROM blocks WHERE canonical = true AND height >= ${fromHeight}\n ) sub\n WHERE grp = (\n SELECT height - ROW_NUMBER() OVER (ORDER BY height)\n FROM blocks WHERE canonical = true AND height = ${fromHeight}\n )\n `.execute(db);\n\n return Number(rows[0]?.tip ?? fromHeight);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AASA,eAAsB,QAAQ,CAAC,IAAsB,OAAgC;AAAA,EACnF,MAAM,cAAc,QAAQ,YAAY,UAAU;AAAA,EAClD,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWnB;AAAA,IACF,QAAQ,EAAE;AAAA,EAEZ,OAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,UAAU,OAAO,EAAE,SAAS;AAAA,IAC5B,QAAQ,OAAO,EAAE,OAAO;AAAA,IACxB,MAAM,OAAO,EAAE,IAAI;AAAA,EACrB,EAAE;AAAA;AAGJ,eAAsB,kBAAkB,CAAC,IAAuC;AAAA,EAC9E,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOrB,QAAQ,EAAE;AAAA,EAEZ,OAAO,OAAO,KAAK,IAAI,SAAS,CAAC;AAAA;AAGnC,eAAsB,oBAAoB,CAAC,IAAsB,YAAqC;AAAA,EACpG,QAAQ,SAAS,MAAM;AAAA,mCACU;AAAA;AAAA;AAAA,yDAGsB;AAAA;AAAA;AAAA;AAAA,wDAID;AAAA;AAAA,IAEpD,QAAQ,EAAE;AAAA,EAEZ,OAAO,OAAO,KAAK,IAAI,OAAO,UAAU;AAAA;",
|
|
8
|
+
"debugId": "C8E32B40B73BEACD64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -38,7 +38,7 @@ interface StreamsTable {
|
|
|
38
38
|
options: Generated<unknown>;
|
|
39
39
|
endpoint_url: string;
|
|
40
40
|
signing_secret: string | null;
|
|
41
|
-
api_key_id: string
|
|
41
|
+
api_key_id: string;
|
|
42
42
|
created_at: Generated<Date>;
|
|
43
43
|
updated_at: Generated<Date>;
|
|
44
44
|
}
|
|
@@ -97,7 +97,7 @@ interface SubgraphsTable {
|
|
|
97
97
|
last_error_at: Date | null;
|
|
98
98
|
total_processed: Generated<number>;
|
|
99
99
|
total_errors: Generated<number>;
|
|
100
|
-
api_key_id: string
|
|
100
|
+
api_key_id: string;
|
|
101
101
|
created_at: Generated<Date>;
|
|
102
102
|
updated_at: Generated<Date>;
|
|
103
103
|
}
|
|
@@ -137,6 +137,7 @@ interface MagicLinksTable {
|
|
|
137
137
|
token: string;
|
|
138
138
|
expires_at: Date;
|
|
139
139
|
used_at: Date | null;
|
|
140
|
+
failed_attempts: Generated<number>;
|
|
140
141
|
created_at: Generated<Date>;
|
|
141
142
|
}
|
|
142
143
|
interface UsageDailyTable {
|
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/db/queries/metrics.ts
|
|
19
18
|
import { sql } from "kysely";
|
|
@@ -52,5 +51,5 @@ export {
|
|
|
52
51
|
getStreamMetrics
|
|
53
52
|
};
|
|
54
53
|
|
|
55
|
-
//# debugId=
|
|
54
|
+
//# debugId=B4106C2E1F1C5A3F64756E2164756E21
|
|
56
55
|
//# sourceMappingURL=metrics.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { sql, type Kysely } from \"kysely\";\nimport type { Database, StreamMetrics } from \"../types.ts\";\n\nexport async function getStreamMetrics(db: Kysely<Database>, streamId: string): Promise<StreamMetrics | null> {\n return (\n await db\n .selectFrom(\"stream_metrics\")\n .selectAll()\n .where(\"stream_id\", \"=\", streamId)\n .executeTakeFirst()\n ) ?? null;\n}\n\nexport async function updateStreamMetrics(\n db: Kysely<Database>,\n streamId: string,\n updates: Partial<{\n lastTriggeredAt: Date;\n lastTriggeredBlock: number;\n totalDeliveries: number;\n failedDeliveries: number;\n errorMessage: string | null;\n }>,\n): Promise<void> {\n await db\n .insertInto(\"stream_metrics\")\n .values({\n stream_id: streamId,\n last_triggered_at: updates.lastTriggeredAt ?? null,\n last_triggered_block: updates.lastTriggeredBlock ?? null,\n total_deliveries: updates.totalDeliveries ?? 0,\n failed_deliveries: updates.failedDeliveries ?? 0,\n error_message: updates.errorMessage ?? null,\n })\n .onConflict((oc) =>\n oc.column(\"stream_id\").doUpdateSet({\n ...(updates.lastTriggeredAt !== undefined ? { last_triggered_at: updates.lastTriggeredAt } : {}),\n ...(updates.lastTriggeredBlock !== undefined ? { last_triggered_block: updates.lastTriggeredBlock } : {}),\n ...(updates.totalDeliveries !== undefined ? { total_deliveries: updates.totalDeliveries } : {}),\n ...(updates.failedDeliveries !== undefined ? { failed_deliveries: updates.failedDeliveries } : {}),\n ...(updates.errorMessage !== undefined ? { error_message: updates.errorMessage } : {}),\n }),\n )\n .execute();\n}\n\nexport async function incrementDeliveryCount(\n db: Kysely<Database>,\n streamId: string,\n failed: boolean,\n): Promise<void> {\n await db\n .insertInto(\"stream_metrics\")\n .values({\n stream_id: streamId,\n total_deliveries: 1,\n failed_deliveries: failed ? 1 : 0,\n })\n .onConflict((oc) =>\n oc.column(\"stream_id\").doUpdateSet({\n total_deliveries: sql`stream_metrics.total_deliveries + 1`,\n ...(failed ? { failed_deliveries: sql`stream_metrics.failed_deliveries + 1` } : {}),\n }),\n )\n .execute();\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,gBAAgB,CAAC,IAAsB,UAAiD;AAAA,EAC5G,OACE,MAAM,GACH,WAAW,gBAAgB,EAC3B,UAAU,EACV,MAAM,aAAa,KAAK,QAAQ,EAChC,iBAAiB,KACjB;AAAA;AAGP,eAAsB,mBAAmB,CACvC,IACA,UACA,SAOe;AAAA,EACf,MAAM,GACH,WAAW,gBAAgB,EAC3B,OAAO;AAAA,IACN,WAAW;AAAA,IACX,mBAAmB,QAAQ,mBAAmB;AAAA,IAC9C,sBAAsB,QAAQ,sBAAsB;AAAA,IACpD,kBAAkB,QAAQ,mBAAmB;AAAA,IAC7C,mBAAmB,QAAQ,oBAAoB;AAAA,IAC/C,eAAe,QAAQ,gBAAgB;AAAA,EACzC,CAAC,EACA,WAAW,CAAC,OACX,GAAG,OAAO,WAAW,EAAE,YAAY;AAAA,OAC7B,QAAQ,oBAAoB,YAAY,EAAE,mBAAmB,QAAQ,gBAAgB,IAAI,CAAC;AAAA,OAC1F,QAAQ,uBAAuB,YAAY,EAAE,sBAAsB,QAAQ,mBAAmB,IAAI,CAAC;AAAA,OACnG,QAAQ,oBAAoB,YAAY,EAAE,kBAAkB,QAAQ,gBAAgB,IAAI,CAAC;AAAA,OACzF,QAAQ,qBAAqB,YAAY,EAAE,mBAAmB,QAAQ,iBAAiB,IAAI,CAAC;AAAA,OAC5F,QAAQ,iBAAiB,YAAY,EAAE,eAAe,QAAQ,aAAa,IAAI,CAAC;AAAA,EACtF,CAAC,CACH,EACC,QAAQ;AAAA;AAGb,eAAsB,sBAAsB,CAC1C,IACA,UACA,QACe;AAAA,EACf,MAAM,GACH,WAAW,gBAAgB,EAC3B,OAAO;AAAA,IACN,WAAW;AAAA,IACX,kBAAkB;AAAA,IAClB,mBAAmB,SAAS,IAAI;AAAA,EAClC,CAAC,EACA,WAAW,CAAC,OACX,GAAG,OAAO,WAAW,EAAE,YAAY;AAAA,IACjC,kBAAkB;AAAA,OACd,SAAS,EAAE,mBAAmB,0CAA0C,IAAI,CAAC;AAAA,EACnF,CAAC,CACH,EACC,QAAQ;AAAA;",
|
|
8
|
+
"debugId": "B4106C2E1F1C5A3F64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -38,7 +38,7 @@ interface StreamsTable {
|
|
|
38
38
|
options: Generated<unknown>;
|
|
39
39
|
endpoint_url: string;
|
|
40
40
|
signing_secret: string | null;
|
|
41
|
-
api_key_id: string
|
|
41
|
+
api_key_id: string;
|
|
42
42
|
created_at: Generated<Date>;
|
|
43
43
|
updated_at: Generated<Date>;
|
|
44
44
|
}
|
|
@@ -97,7 +97,7 @@ interface SubgraphsTable {
|
|
|
97
97
|
last_error_at: Date | null;
|
|
98
98
|
total_processed: Generated<number>;
|
|
99
99
|
total_errors: Generated<number>;
|
|
100
|
-
api_key_id: string
|
|
100
|
+
api_key_id: string;
|
|
101
101
|
created_at: Generated<Date>;
|
|
102
102
|
updated_at: Generated<Date>;
|
|
103
103
|
}
|
|
@@ -137,6 +137,7 @@ interface MagicLinksTable {
|
|
|
137
137
|
token: string;
|
|
138
138
|
expires_at: Date;
|
|
139
139
|
used_at: Date | null;
|
|
140
|
+
failed_attempts: Generated<number>;
|
|
140
141
|
created_at: Generated<Date>;
|
|
141
142
|
}
|
|
142
143
|
interface UsageDailyTable {
|
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/db/jsonb.ts
|
|
19
18
|
import { sql } from "kysely";
|
|
@@ -49,7 +48,7 @@ async function registerSubgraph(db, data) {
|
|
|
49
48
|
definition: jsonb(data.definition),
|
|
50
49
|
schema_hash: data.schemaHash,
|
|
51
50
|
handler_path: data.handlerPath,
|
|
52
|
-
api_key_id: data.apiKeyId
|
|
51
|
+
api_key_id: data.apiKeyId,
|
|
53
52
|
schema_name: data.schemaName ?? null
|
|
54
53
|
}).onConflict((oc) => oc.columns(["name", "api_key_id"]).doUpdateSet({
|
|
55
54
|
version: data.version,
|
|
@@ -112,5 +111,5 @@ export {
|
|
|
112
111
|
deleteSubgraph
|
|
113
112
|
};
|
|
114
113
|
|
|
115
|
-
//# debugId=
|
|
114
|
+
//# debugId=CA1F8ED652ED1B5C64756E2164756E21
|
|
116
115
|
//# sourceMappingURL=subgraphs.js.map
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"sources": ["../src/db/jsonb.ts", "../src/db/queries/subgraphs.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { sql, type RawBuilder } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n const escaped = JSON.stringify(value).replace(/'/g, \"''\");\n return sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n if (typeof value === \"string\") {\n try {\n return JSON.parse(value) as T;\n } catch {\n return value as T;\n }\n }\n return (value ?? {}) as T;\n}\n",
|
|
6
|
-
"import { sql, type Kysely } from \"kysely\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type { Database, Subgraph } from \"../types.ts\";\n\n/**\n * Convert a subgraph name to its PostgreSQL schema name.\n * With keyPrefix: \"subgraph_{prefix}_{name}\" (tenant-isolated)\n * Without keyPrefix: \"subgraph_{name}\" (backward compat)\n */\nexport function pgSchemaName(subgraphName: string, keyPrefix?: string): string {\n const safeName = subgraphName.replace(/-/g, \"_\");\n if (!keyPrefix) {\n return `subgraph_${safeName}`;\n }\n const safePrefix = keyPrefix.replace(/^sk-sl_/, \"\").replace(/-/g, \"_\");\n return `subgraph_${safePrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n db: Kysely<Database>,\n data: {\n name: string;\n version: string;\n definition: Record<string, unknown>;\n schemaHash: string;\n handlerPath: string;\n apiKeyId?: string;\n schemaName?: string;\n },\n): Promise<Subgraph> {\n return await db\n .insertInto(\"subgraphs\")\n .values({\n name: data.name,\n version: data.version,\n definition: jsonb(data.definition) as any,\n schema_hash: data.schemaHash,\n handler_path: data.handlerPath,\n api_key_id: data.apiKeyId
|
|
6
|
+
"import { sql, type Kysely } from \"kysely\";\nimport { jsonb } from \"../jsonb.ts\";\nimport type { Database, Subgraph } from \"../types.ts\";\n\n/**\n * Convert a subgraph name to its PostgreSQL schema name.\n * With keyPrefix: \"subgraph_{prefix}_{name}\" (tenant-isolated)\n * Without keyPrefix: \"subgraph_{name}\" (backward compat)\n */\nexport function pgSchemaName(subgraphName: string, keyPrefix?: string): string {\n const safeName = subgraphName.replace(/-/g, \"_\");\n if (!keyPrefix) {\n return `subgraph_${safeName}`;\n }\n const safePrefix = keyPrefix.replace(/^sk-sl_/, \"\").replace(/-/g, \"_\");\n return `subgraph_${safePrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n db: Kysely<Database>,\n data: {\n name: string;\n version: string;\n definition: Record<string, unknown>;\n schemaHash: string;\n handlerPath: string;\n apiKeyId?: string;\n schemaName?: string;\n },\n): Promise<Subgraph> {\n return await db\n .insertInto(\"subgraphs\")\n .values({\n name: data.name,\n version: data.version,\n definition: jsonb(data.definition) as any,\n schema_hash: data.schemaHash,\n handler_path: data.handlerPath,\n api_key_id: data.apiKeyId!,\n schema_name: data.schemaName ?? null,\n })\n .onConflict((oc) =>\n oc.columns([\"name\", \"api_key_id\"]).doUpdateSet({\n version: data.version,\n definition: jsonb(data.definition) as any,\n schema_hash: data.schemaHash,\n handler_path: data.handlerPath,\n schema_name: data.schemaName ?? null,\n updated_at: new Date(),\n }),\n )\n .returningAll()\n .executeTakeFirstOrThrow();\n}\n\nexport async function getSubgraph(db: Kysely<Database>, name: string, apiKeyId?: string): Promise<Subgraph | null> {\n let query = db\n .selectFrom(\"subgraphs\")\n .selectAll()\n .where(\"name\", \"=\", name);\n\n if (apiKeyId) {\n query = query.where(\"api_key_id\", \"=\", apiKeyId);\n }\n\n return (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function listSubgraphs(db: Kysely<Database>, apiKeyId?: string): Promise<Subgraph[]> {\n let query = db.selectFrom(\"subgraphs\").selectAll();\n if (apiKeyId) {\n query = query.where(\"api_key_id\", \"=\", apiKeyId);\n }\n return query.execute();\n}\n\nexport async function updateSubgraphStatus(\n db: Kysely<Database>,\n name: string,\n status: string,\n lastProcessedBlock?: number,\n): Promise<void> {\n await db\n .updateTable(\"subgraphs\")\n .set({\n status,\n ...(lastProcessedBlock !== undefined ? { last_processed_block: lastProcessedBlock } : {}),\n updated_at: new Date(),\n })\n .where(\"name\", \"=\", name)\n .execute();\n}\n\nexport async function recordSubgraphProcessed(\n db: Kysely<Database>,\n name: string,\n processed: number,\n errors: number,\n lastError?: string,\n): Promise<void> {\n await db\n .updateTable(\"subgraphs\")\n .set({\n total_processed: sql`total_processed + ${processed}`,\n total_errors: sql`total_errors + ${errors}`,\n ...(lastError\n ? { last_error: lastError, last_error_at: new Date() }\n : {}),\n updated_at: new Date(),\n })\n .where(\"name\", \"=\", name)\n .execute();\n}\n\nexport async function updateSubgraphHandlerPath(\n db: Kysely<Database>,\n name: string,\n handlerPath: string,\n): Promise<void> {\n await db\n .updateTable(\"subgraphs\")\n .set({ handler_path: handlerPath, updated_at: new Date() })\n .where(\"name\", \"=\", name)\n .execute();\n}\n\nexport async function deleteSubgraph(db: Kysely<Database>, name: string, apiKeyId?: string): Promise<Subgraph | null> {\n const subgraph = await getSubgraph(db, name, apiKeyId);\n if (!subgraph) return null;\n\n // Use stored schema_name if available, otherwise compute\n const schemaName = subgraph.schema_name ?? pgSchemaName(name);\n\n // Drop the subgraph's schema (CASCADE drops all tables within)\n await sql`DROP SCHEMA IF EXISTS ${sql.raw(`\"${schemaName}\"`)} CASCADE`.execute(db);\n\n // Remove from registry\n await db.deleteFrom(\"subgraphs\").where(\"id\", \"=\", subgraph.id).execute();\n\n return subgraph;\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "CA1F8ED652ED1B5C64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -46,7 +46,7 @@ interface StreamsTable {
|
|
|
46
46
|
options: Generated<unknown>;
|
|
47
47
|
endpoint_url: string;
|
|
48
48
|
signing_secret: string | null;
|
|
49
|
-
api_key_id: string
|
|
49
|
+
api_key_id: string;
|
|
50
50
|
created_at: Generated<Date>;
|
|
51
51
|
updated_at: Generated<Date>;
|
|
52
52
|
}
|
|
@@ -105,7 +105,7 @@ interface SubgraphsTable {
|
|
|
105
105
|
last_error_at: Date | null;
|
|
106
106
|
total_processed: Generated<number>;
|
|
107
107
|
total_errors: Generated<number>;
|
|
108
|
-
api_key_id: string
|
|
108
|
+
api_key_id: string;
|
|
109
109
|
created_at: Generated<Date>;
|
|
110
110
|
updated_at: Generated<Date>;
|
|
111
111
|
}
|
|
@@ -145,6 +145,7 @@ interface MagicLinksTable {
|
|
|
145
145
|
token: string;
|
|
146
146
|
expires_at: Date;
|
|
147
147
|
used_at: Date | null;
|
|
148
|
+
failed_attempts: Generated<number>;
|
|
148
149
|
created_at: Generated<Date>;
|
|
149
150
|
}
|
|
150
151
|
interface UsageDailyTable {
|
|
@@ -258,6 +259,13 @@ interface UsageSummary {
|
|
|
258
259
|
}
|
|
259
260
|
/** Get current usage for an account. */
|
|
260
261
|
declare function getUsage(db: Kysely<Database>, accountId: string): Promise<UsageSummary>;
|
|
262
|
+
interface DailyUsage {
|
|
263
|
+
date: string;
|
|
264
|
+
apiRequests: number;
|
|
265
|
+
deliveries: number;
|
|
266
|
+
}
|
|
267
|
+
/** Get last 7 days of daily usage, filling missing days with 0. */
|
|
268
|
+
declare function getDailyUsage(db: Kysely<Database>, accountId: string): Promise<DailyUsage[]>;
|
|
261
269
|
interface LimitCheck {
|
|
262
270
|
allowed: boolean;
|
|
263
271
|
limits: ReturnType<typeof getPlanLimits>;
|
|
@@ -274,4 +282,4 @@ declare function checkLimits(db: Kysely<Database>, accountId: string, plan: stri
|
|
|
274
282
|
* for each tenant's subgraph schemas.
|
|
275
283
|
*/
|
|
276
284
|
declare function measureStorage(db: Kysely<Database>): Promise<void>;
|
|
277
|
-
export { measureStorage, incrementDeliveries, incrementApiRequests, getUsage, checkLimits, UsageSummary, LimitCheck };
|
|
285
|
+
export { measureStorage, incrementDeliveries, incrementApiRequests, getUsage, getDailyUsage, checkLimits, UsageSummary, LimitCheck, DailyUsage };
|
|
@@ -13,7 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
set: __exportSetter.bind(all, name)
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
17
16
|
|
|
18
17
|
// src/lib/plans.ts
|
|
19
18
|
var FREE_PLAN = {
|
|
@@ -57,6 +56,27 @@ async function getUsage(db, accountId) {
|
|
|
57
56
|
storageBytes: Number(storageRow?.storage_bytes ?? 0)
|
|
58
57
|
};
|
|
59
58
|
}
|
|
59
|
+
async function getDailyUsage(db, accountId) {
|
|
60
|
+
const rows = await db.selectFrom("usage_daily").select(["date", "api_requests", "deliveries"]).where("account_id", "=", accountId).where("date", ">=", sql`NOW()::date - 6`).orderBy("date", "asc").execute();
|
|
61
|
+
const byDate = new Map(rows.map((r) => {
|
|
62
|
+
const d = r.date;
|
|
63
|
+
const key = d instanceof Date ? d.toISOString().slice(0, 10) : String(d).slice(0, 10);
|
|
64
|
+
return [key, r];
|
|
65
|
+
}));
|
|
66
|
+
const result = [];
|
|
67
|
+
for (let i = 6;i >= 0; i--) {
|
|
68
|
+
const d = new Date;
|
|
69
|
+
d.setDate(d.getDate() - i);
|
|
70
|
+
const dateStr = d.toISOString().slice(0, 10);
|
|
71
|
+
const row = byDate.get(dateStr);
|
|
72
|
+
result.push({
|
|
73
|
+
date: dateStr,
|
|
74
|
+
apiRequests: row?.api_requests ?? 0,
|
|
75
|
+
deliveries: row?.deliveries ?? 0
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
60
80
|
async function checkLimits(db, accountId, plan) {
|
|
61
81
|
const limits = getPlanLimits(plan);
|
|
62
82
|
const usage = await getUsage(db, accountId);
|
|
@@ -115,8 +135,9 @@ export {
|
|
|
115
135
|
incrementDeliveries,
|
|
116
136
|
incrementApiRequests,
|
|
117
137
|
getUsage,
|
|
138
|
+
getDailyUsage,
|
|
118
139
|
checkLimits
|
|
119
140
|
};
|
|
120
141
|
|
|
121
|
-
//# debugId=
|
|
142
|
+
//# debugId=DDD1942F27B4B83564756E2164756E21
|
|
122
143
|
//# sourceMappingURL=usage.js.map
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"sources": ["../src/lib/plans.ts", "../src/db/queries/usage.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"export interface PlanLimits {\n streams: number;\n subgraphs: number;\n apiRequestsPerDay: number;\n deliveriesPerMonth: number;\n storageBytes: number;\n}\n\nexport const FREE_PLAN: PlanLimits = {\n streams: 3,\n subgraphs: 2,\n apiRequestsPerDay: 1_000,\n deliveriesPerMonth: 5_000,\n storageBytes: 100 * 1024 * 1024, // 100MB\n};\n\nexport function getPlanLimits(plan: string): PlanLimits {\n switch (plan) {\n case \"free\":\n default:\n return FREE_PLAN;\n }\n}\n",
|
|
6
|
-
"import { sql, type Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\nimport { getPlanLimits } from \"../../lib/plans.ts\";\n\n/** Increment API request counter for today. Fire-and-forget safe. */\nexport async function incrementApiRequests(\n db: Kysely<Database>,\n accountId: string,\n): Promise<void> {\n const today = new Date().toISOString().slice(0, 10);\n await db\n .insertInto(\"usage_daily\")\n .values({ account_id: accountId, date: today, api_requests: 1, deliveries: 0 })\n .onConflict((oc) =>\n oc.columns([\"account_id\", \"date\"]).doUpdateSet({\n api_requests: sql`usage_daily.api_requests + 1`,\n }),\n )\n .execute();\n}\n\n/** Increment delivery counter for today. */\nexport async function incrementDeliveries(\n db: Kysely<Database>,\n accountId: string,\n count = 1,\n): Promise<void> {\n const today = new Date().toISOString().slice(0, 10);\n await db\n .insertInto(\"usage_daily\")\n .values({ account_id: accountId, date: today, api_requests: 0, deliveries: count })\n .onConflict((oc) =>\n oc.columns([\"account_id\", \"date\"]).doUpdateSet({\n deliveries: sql`usage_daily.deliveries + ${count}`,\n }),\n )\n .execute();\n}\n\nexport interface UsageSummary {\n apiRequestsToday: number;\n deliveriesThisMonth: number;\n storageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n db: Kysely<Database>,\n accountId: string,\n): Promise<UsageSummary> {\n const today = new Date().toISOString().slice(0, 10);\n const monthStart = today.slice(0, 7) + \"-01\"; // YYYY-MM-01\n\n // Today's API requests\n const dailyRow = await db\n .selectFrom(\"usage_daily\")\n .select(\"api_requests\")\n .where(\"account_id\", \"=\", accountId)\n .where(\"date\", \"=\", today)\n .executeTakeFirst();\n\n // This month's deliveries\n const monthlyRow = await db\n .selectFrom(\"usage_daily\")\n .select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n .where(\"account_id\", \"=\", accountId)\n .where(\"date\", \">=\", monthStart)\n .executeTakeFirst();\n\n // Latest storage snapshot\n const storageRow = await db\n .selectFrom(\"usage_snapshots\")\n .select(\"storage_bytes\")\n .where(\"account_id\", \"=\", accountId)\n .orderBy(\"measured_at\", \"desc\")\n .limit(1)\n .executeTakeFirst();\n\n return {\n apiRequestsToday: dailyRow?.api_requests ?? 0,\n deliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n storageBytes: Number(storageRow?.storage_bytes ?? 0),\n };\n}\n\nexport interface LimitCheck {\n allowed: boolean;\n limits: ReturnType<typeof getPlanLimits>;\n current: UsageSummary & { streams: number; subgraphs: number };\n exceeded?: string;\n}\n\n/** Check if an account is within plan limits. */\nexport async function checkLimits(\n db: Kysely<Database>,\n accountId: string,\n plan: string,\n): Promise<LimitCheck> {\n const limits = getPlanLimits(plan);\n const usage = await getUsage(db, accountId);\n\n // Count streams owned by this account's keys\n const streamCount = await db\n .selectFrom(\"streams\")\n .innerJoin(\"api_keys\", \"streams.api_key_id\", \"api_keys.id\")\n .select(sql<number>`count(*)`.as(\"count\"))\n .where(\"api_keys.account_id\", \"=\", accountId)\n .executeTakeFirst();\n\n const subgraphCount = await db\n .selectFrom(\"subgraphs\")\n .innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n .select(sql<number>`count(*)`.as(\"count\"))\n .where(\"api_keys.account_id\", \"=\", accountId)\n .executeTakeFirst();\n\n const current = {\n ...usage,\n streams: Number(streamCount?.count ?? 0),\n subgraphs: Number(subgraphCount?.count ?? 0),\n };\n\n // Check each limit\n if (current.streams >= limits.streams) {\n return { allowed: false, limits, current, exceeded: \"streams\" };\n }\n if (current.subgraphs >= limits.subgraphs) {\n return { allowed: false, limits, current, exceeded: \"subgraphs\" };\n }\n if (current.apiRequestsToday >= limits.apiRequestsPerDay) {\n return { allowed: false, limits, current, exceeded: \"api_requests\" };\n }\n if (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {\n return { allowed: false, limits, current, exceeded: \"deliveries\" };\n }\n if (current.storageBytes >= limits.storageBytes) {\n return { allowed: false, limits, current, exceeded: \"storage\" };\n }\n\n return { allowed: true, limits, current };\n}\n\n/**\n * Measure storage for all accounts by querying pg_total_relation_size\n * for each tenant's subgraph schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n // Get all accounts with subgraphs\n const accountSubgraphs = await db\n .selectFrom(\"subgraphs\")\n .innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n .select([\"api_keys.account_id\", \"subgraphs.schema_name\"])\n .where(\"subgraphs.schema_name\", \"is not\", null)\n .execute();\n\n // Group schemas by account\n const byAccount = new Map<string, string[]>();\n for (const row of accountSubgraphs) {\n const schemas = byAccount.get(row.account_id) ?? [];\n if (row.schema_name) schemas.push(row.schema_name);\n byAccount.set(row.account_id, schemas);\n }\n\n for (const [accountId, schemas] of byAccount) {\n let totalBytes = 0;\n for (const schema of schemas) {\n try {\n const result = await sql<{ size: string }>`\n SELECT COALESCE(SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))), 0)::text as size\n FROM pg_tables WHERE schemaname = ${schema}\n `.execute(db);\n totalBytes += Number((result.rows[0] as any)?.size ?? 0);\n } catch {\n // Schema may not exist\n }\n }\n\n await db\n .insertInto(\"usage_snapshots\")\n .values({\n account_id: accountId,\n storage_bytes: totalBytes,\n })\n .execute();\n }\n}\n"
|
|
6
|
+
"import { sql, type Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\nimport { getPlanLimits } from \"../../lib/plans.ts\";\n\n/** Increment API request counter for today. Fire-and-forget safe. */\nexport async function incrementApiRequests(\n db: Kysely<Database>,\n accountId: string,\n): Promise<void> {\n const today = new Date().toISOString().slice(0, 10);\n await db\n .insertInto(\"usage_daily\")\n .values({ account_id: accountId, date: today, api_requests: 1, deliveries: 0 })\n .onConflict((oc) =>\n oc.columns([\"account_id\", \"date\"]).doUpdateSet({\n api_requests: sql`usage_daily.api_requests + 1`,\n }),\n )\n .execute();\n}\n\n/** Increment delivery counter for today. */\nexport async function incrementDeliveries(\n db: Kysely<Database>,\n accountId: string,\n count = 1,\n): Promise<void> {\n const today = new Date().toISOString().slice(0, 10);\n await db\n .insertInto(\"usage_daily\")\n .values({ account_id: accountId, date: today, api_requests: 0, deliveries: count })\n .onConflict((oc) =>\n oc.columns([\"account_id\", \"date\"]).doUpdateSet({\n deliveries: sql`usage_daily.deliveries + ${count}`,\n }),\n )\n .execute();\n}\n\nexport interface UsageSummary {\n apiRequestsToday: number;\n deliveriesThisMonth: number;\n storageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n db: Kysely<Database>,\n accountId: string,\n): Promise<UsageSummary> {\n const today = new Date().toISOString().slice(0, 10);\n const monthStart = today.slice(0, 7) + \"-01\"; // YYYY-MM-01\n\n // Today's API requests\n const dailyRow = await db\n .selectFrom(\"usage_daily\")\n .select(\"api_requests\")\n .where(\"account_id\", \"=\", accountId)\n .where(\"date\", \"=\", today)\n .executeTakeFirst();\n\n // This month's deliveries\n const monthlyRow = await db\n .selectFrom(\"usage_daily\")\n .select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n .where(\"account_id\", \"=\", accountId)\n .where(\"date\", \">=\", monthStart)\n .executeTakeFirst();\n\n // Latest storage snapshot\n const storageRow = await db\n .selectFrom(\"usage_snapshots\")\n .select(\"storage_bytes\")\n .where(\"account_id\", \"=\", accountId)\n .orderBy(\"measured_at\", \"desc\")\n .limit(1)\n .executeTakeFirst();\n\n return {\n apiRequestsToday: dailyRow?.api_requests ?? 0,\n deliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n storageBytes: Number(storageRow?.storage_bytes ?? 0),\n };\n}\n\nexport interface DailyUsage {\n date: string;\n apiRequests: number;\n deliveries: number;\n}\n\n/** Get last 7 days of daily usage, filling missing days with 0. */\nexport async function getDailyUsage(\n db: Kysely<Database>,\n accountId: string,\n): Promise<DailyUsage[]> {\n const rows = await db\n .selectFrom(\"usage_daily\")\n .select([\"date\", \"api_requests\", \"deliveries\"])\n .where(\"account_id\", \"=\", accountId)\n .where(\"date\", \">=\", sql<string>`NOW()::date - 6`)\n .orderBy(\"date\", \"asc\")\n .execute();\n\n // Fill missing days with 0 (normalize date to YYYY-MM-DD string)\n const byDate = new Map(rows.map((r) => {\n const d = r.date as unknown;\n const key = d instanceof Date ? d.toISOString().slice(0, 10) : String(d).slice(0, 10);\n return [key, r];\n }));\n const result: DailyUsage[] = [];\n for (let i = 6; i >= 0; i--) {\n const d = new Date();\n d.setDate(d.getDate() - i);\n const dateStr = d.toISOString().slice(0, 10);\n const row = byDate.get(dateStr);\n result.push({\n date: dateStr,\n apiRequests: row?.api_requests ?? 0,\n deliveries: row?.deliveries ?? 0,\n });\n }\n return result;\n}\n\nexport interface LimitCheck {\n allowed: boolean;\n limits: ReturnType<typeof getPlanLimits>;\n current: UsageSummary & { streams: number; subgraphs: number };\n exceeded?: string;\n}\n\n/** Check if an account is within plan limits. */\nexport async function checkLimits(\n db: Kysely<Database>,\n accountId: string,\n plan: string,\n): Promise<LimitCheck> {\n const limits = getPlanLimits(plan);\n const usage = await getUsage(db, accountId);\n\n // Count streams owned by this account's keys\n const streamCount = await db\n .selectFrom(\"streams\")\n .innerJoin(\"api_keys\", \"streams.api_key_id\", \"api_keys.id\")\n .select(sql<number>`count(*)`.as(\"count\"))\n .where(\"api_keys.account_id\", \"=\", accountId)\n .executeTakeFirst();\n\n const subgraphCount = await db\n .selectFrom(\"subgraphs\")\n .innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n .select(sql<number>`count(*)`.as(\"count\"))\n .where(\"api_keys.account_id\", \"=\", accountId)\n .executeTakeFirst();\n\n const current = {\n ...usage,\n streams: Number(streamCount?.count ?? 0),\n subgraphs: Number(subgraphCount?.count ?? 0),\n };\n\n // Check each limit\n if (current.streams >= limits.streams) {\n return { allowed: false, limits, current, exceeded: \"streams\" };\n }\n if (current.subgraphs >= limits.subgraphs) {\n return { allowed: false, limits, current, exceeded: \"subgraphs\" };\n }\n if (current.apiRequestsToday >= limits.apiRequestsPerDay) {\n return { allowed: false, limits, current, exceeded: \"api_requests\" };\n }\n if (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {\n return { allowed: false, limits, current, exceeded: \"deliveries\" };\n }\n if (current.storageBytes >= limits.storageBytes) {\n return { allowed: false, limits, current, exceeded: \"storage\" };\n }\n\n return { allowed: true, limits, current };\n}\n\n/**\n * Measure storage for all accounts by querying pg_total_relation_size\n * for each tenant's subgraph schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n // Get all accounts with subgraphs\n const accountSubgraphs = await db\n .selectFrom(\"subgraphs\")\n .innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n .select([\"api_keys.account_id\", \"subgraphs.schema_name\"])\n .where(\"subgraphs.schema_name\", \"is not\", null)\n .execute();\n\n // Group schemas by account\n const byAccount = new Map<string, string[]>();\n for (const row of accountSubgraphs) {\n const schemas = byAccount.get(row.account_id) ?? [];\n if (row.schema_name) schemas.push(row.schema_name);\n byAccount.set(row.account_id, schemas);\n }\n\n for (const [accountId, schemas] of byAccount) {\n let totalBytes = 0;\n for (const schema of schemas) {\n try {\n const result = await sql<{ size: string }>`\n SELECT COALESCE(SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))), 0)::text as size\n FROM pg_tables WHERE schemaname = ${schema}\n `.execute(db);\n totalBytes += Number((result.rows[0] as any)?.size ?? 0);\n } catch {\n // Schema may not exist\n }\n }\n\n await db\n .insertInto(\"usage_snapshots\")\n .values({\n account_id: accountId,\n storage_bytes: totalBytes,\n })\n .execute();\n }\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "DDD1942F27B4B83564756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|