@secondlayer/shared 0.8.0 → 0.9.0
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/db/index.d.ts +1 -0
- package/dist/src/db/queries/accounts.d.ts +10 -3
- package/dist/src/db/queries/accounts.js +12 -3
- package/dist/src/db/queries/accounts.js.map +3 -3
- package/dist/src/db/queries/integrity.d.ts +1 -0
- package/dist/src/db/queries/metrics.d.ts +1 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +1 -0
- package/dist/src/db/queries/subgraphs.d.ts +2 -0
- package/dist/src/db/queries/subgraphs.js +4 -2
- package/dist/src/db/queries/subgraphs.js.map +3 -3
- package/dist/src/db/queries/usage.d.ts +1 -0
- package/dist/src/db/schema.d.ts +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/node/local-client.d.ts +1 -0
- package/migrations/0019_magic_link_code.ts +11 -0
- package/package.json +1 -1
package/dist/src/db/index.d.ts
CHANGED
|
@@ -146,6 +146,7 @@ interface MagicLinksTable {
|
|
|
146
146
|
id: Generated<string>;
|
|
147
147
|
email: string;
|
|
148
148
|
token: string;
|
|
149
|
+
code: string | null;
|
|
149
150
|
expires_at: Date;
|
|
150
151
|
used_at: Date | null;
|
|
151
152
|
failed_attempts: Generated<number>;
|
|
@@ -256,10 +257,16 @@ type Account = Selectable<AccountsTable>;
|
|
|
256
257
|
declare function upsertAccount(db: Kysely<Database>, email: string): Promise<Account>;
|
|
257
258
|
declare function getAccountById(db: Kysely<Database>, id: string): Promise<Account | null>;
|
|
258
259
|
declare function isEmailAllowed(db: Kysely<Database>, email: string): Promise<boolean>;
|
|
259
|
-
declare function createMagicLink(db: Kysely<Database>, email: string, token: string, expiresInMs?: number): Promise<void>;
|
|
260
|
+
declare function createMagicLink(db: Kysely<Database>, email: string, token: string, code: string, expiresInMs?: number): Promise<void>;
|
|
260
261
|
/**
|
|
261
262
|
* Verify a magic link token. Returns the email if valid, null otherwise.
|
|
262
|
-
* Marks the token as used atomically. Rejects after
|
|
263
|
+
* Marks the token as used atomically. Rejects after 3 failed attempts.
|
|
263
264
|
*/
|
|
264
265
|
declare function verifyMagicLink(db: Kysely<Database>, token: string): Promise<string | null>;
|
|
265
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Verify by 6-digit code + email. Same atomic pattern as verifyMagicLink.
|
|
268
|
+
* Rejects after 3 failed attempts. Increments failed_attempts on all
|
|
269
|
+
* active codes for this email on failure (prevents parallel brute-force).
|
|
270
|
+
*/
|
|
271
|
+
declare function verifyMagicLinkByCode(db: Kysely<Database>, email: string, code: string): Promise<string | null>;
|
|
272
|
+
export { verifyMagicLinkByCode, verifyMagicLink, upsertAccount, isEmailAllowed, getAccountById, createMagicLink };
|
|
@@ -31,21 +31,30 @@ async function isEmailAllowed(db, email) {
|
|
|
31
31
|
`.execute(db);
|
|
32
32
|
return result.rows.length > 0;
|
|
33
33
|
}
|
|
34
|
-
async function createMagicLink(db, email, token, expiresInMs = 15 * 60 * 1000) {
|
|
34
|
+
async function createMagicLink(db, email, token, code, expiresInMs = 15 * 60 * 1000) {
|
|
35
35
|
await db.insertInto("magic_links").values({
|
|
36
36
|
email,
|
|
37
37
|
token,
|
|
38
|
+
code,
|
|
38
39
|
expires_at: new Date(Date.now() + expiresInMs)
|
|
39
40
|
}).execute();
|
|
40
41
|
}
|
|
41
42
|
async function verifyMagicLink(db, token) {
|
|
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", "<",
|
|
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).where("failed_attempts", "<", 3).returning("email").executeTakeFirst();
|
|
43
44
|
if (result?.email)
|
|
44
45
|
return result.email;
|
|
45
46
|
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
47
|
return null;
|
|
47
48
|
}
|
|
49
|
+
async function verifyMagicLinkByCode(db, email, code) {
|
|
50
|
+
const result = await db.updateTable("magic_links").set({ used_at: new Date }).where("email", "=", email).where("code", "=", code).where("used_at", "is", null).where("expires_at", ">", new Date).where("failed_attempts", "<", 3).returning("email").executeTakeFirst();
|
|
51
|
+
if (result?.email)
|
|
52
|
+
return result.email;
|
|
53
|
+
await db.updateTable("magic_links").set({ failed_attempts: sql`failed_attempts + 1` }).where("email", "=", email).where("used_at", "is", null).where("expires_at", ">", new Date).execute();
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
48
56
|
export {
|
|
57
|
+
verifyMagicLinkByCode,
|
|
49
58
|
verifyMagicLink,
|
|
50
59
|
upsertAccount,
|
|
51
60
|
isEmailAllowed,
|
|
@@ -53,5 +62,5 @@ export {
|
|
|
53
62
|
createMagicLink
|
|
54
63
|
};
|
|
55
64
|
|
|
56
|
-
//# debugId=
|
|
65
|
+
//# debugId=8F0ACA05832385B164756E2164756E21
|
|
57
66
|
//# 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 { type Kysely, sql } from \"kysely\";\nimport type { Account, Database } from \"../types.ts\";\n\nexport async function upsertAccount(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<Account> {\n\treturn await db\n\t\t.insertInto(\"accounts\")\n\t\t.values({ email })\n\t\t.onConflict(\n\t\t\t(oc) => oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<Account | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"accounts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function isEmailAllowed(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<boolean> {\n\tconst 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\treturn result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n\tdb: Kysely<Database>,\n\temail: string,\n\ttoken: string,\n\texpiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"magic_links\")\n\t\t.values({\n\t\t\temail,\n\t\t\ttoken,\n\t\t\texpires_at: new Date(Date.now() + expiresInMs),\n\t\t})\n\t\t.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
|
+
"import { type Kysely, sql } from \"kysely\";\nimport type { Account, Database } from \"../types.ts\";\n\nexport async function upsertAccount(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<Account> {\n\treturn await db\n\t\t.insertInto(\"accounts\")\n\t\t.values({ email })\n\t\t.onConflict(\n\t\t\t(oc) => oc.column(\"email\").doUpdateSet({ email }), // no-op update to return existing\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getAccountById(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<Account | null> {\n\treturn (\n\t\t(await db\n\t\t\t.selectFrom(\"accounts\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nexport async function isEmailAllowed(\n\tdb: Kysely<Database>,\n\temail: string,\n): Promise<boolean> {\n\tconst 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\treturn result.rows.length > 0;\n}\n\nexport async function createMagicLink(\n\tdb: Kysely<Database>,\n\temail: string,\n\ttoken: string,\n\tcode: string,\n\texpiresInMs: number = 15 * 60 * 1000,\n): Promise<void> {\n\tawait db\n\t\t.insertInto(\"magic_links\")\n\t\t.values({\n\t\t\temail,\n\t\t\ttoken,\n\t\t\tcode,\n\t\t\texpires_at: new Date(Date.now() + expiresInMs),\n\t\t})\n\t\t.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 3 failed attempts.\n */\nexport async function verifyMagicLink(\n\tdb: Kysely<Database>,\n\ttoken: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts if token exists but didn't verify\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"token\", \"=\", token)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n\n/**\n * Verify by 6-digit code + email. Same atomic pattern as verifyMagicLink.\n * Rejects after 3 failed attempts. Increments failed_attempts on all\n * active codes for this email on failure (prevents parallel brute-force).\n */\nexport async function verifyMagicLinkByCode(\n\tdb: Kysely<Database>,\n\temail: string,\n\tcode: string,\n): Promise<string | null> {\n\tconst result = await db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ used_at: new Date() })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"code\", \"=\", code)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.where(\"failed_attempts\", \"<\", 3)\n\t\t.returning(\"email\")\n\t\t.executeTakeFirst();\n\n\tif (result?.email) return result.email;\n\n\t// Increment failed attempts on all active codes for this email\n\tawait db\n\t\t.updateTable(\"magic_links\")\n\t\t.set({ failed_attempts: sql`failed_attempts + 1` })\n\t\t.where(\"email\", \"=\", email)\n\t\t.where(\"used_at\", \"is\", null)\n\t\t.where(\"expires_at\", \">\", new Date())\n\t\t.execute();\n\n\treturn null;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,aAAa,CAClC,IACA,OACmB;AAAA,EACnB,OAAO,MAAM,GACX,WAAW,UAAU,EACrB,OAAO,EAAE,MAAM,CAAC,EAChB,WACA,CAAC,OAAO,GAAG,OAAO,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CACjD,EACC,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,cAAc,CACnC,IACA,IAC0B;AAAA,EAC1B,OACE,MAAM,GACL,WAAW,UAAU,EACrB,UAAU,EACV,MAAM,MAAM,KAAK,EAAE,EACnB,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,cAAc,CACnC,IACA,OACmB;AAAA,EACnB,MAAM,SAAS,MAAM;AAAA,oDAC8B;AAAA;AAAA,oDAEA;AAAA;AAAA,IAEhD,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,SAAS;AAAA;AAG7B,eAAsB,eAAe,CACpC,IACA,OACA,OACA,cAAsB,KAAK,KAAK,MAChB;AAAA,EAChB,MAAM,GACJ,WAAW,aAAa,EACxB,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW;AAAA,EAC9C,CAAC,EACA,QAAQ;AAAA;AAOX,eAAsB,eAAe,CACpC,IACA,OACyB;AAAA,EACzB,MAAM,SAAS,MAAM,GACnB,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,EAEnB,IAAI,QAAQ;AAAA,IAAO,OAAO,OAAO;AAAA,EAGjC,MAAM,GACJ,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,EAEV,OAAO;AAAA;",
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAGA,eAAsB,aAAa,CAClC,IACA,OACmB;AAAA,EACnB,OAAO,MAAM,GACX,WAAW,UAAU,EACrB,OAAO,EAAE,MAAM,CAAC,EAChB,WACA,CAAC,OAAO,GAAG,OAAO,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CACjD,EACC,aAAa,EACb,wBAAwB;AAAA;AAG3B,eAAsB,cAAc,CACnC,IACA,IAC0B;AAAA,EAC1B,OACE,MAAM,GACL,WAAW,UAAU,EACrB,UAAU,EACV,MAAM,MAAM,KAAK,EAAE,EACnB,iBAAiB,KAAM;AAAA;AAI3B,eAAsB,cAAc,CACnC,IACA,OACmB;AAAA,EACnB,MAAM,SAAS,MAAM;AAAA,oDAC8B;AAAA;AAAA,oDAEA;AAAA;AAAA,IAEhD,QAAQ,EAAE;AAAA,EAEb,OAAO,OAAO,KAAK,SAAS;AAAA;AAG7B,eAAsB,eAAe,CACpC,IACA,OACA,OACA,MACA,cAAsB,KAAK,KAAK,MAChB;AAAA,EAChB,MAAM,GACJ,WAAW,aAAa,EACxB,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW;AAAA,EAC9C,CAAC,EACA,QAAQ;AAAA;AAOX,eAAsB,eAAe,CACpC,IACA,OACyB;AAAA,EACzB,MAAM,SAAS,MAAM,GACnB,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,EAEnB,IAAI,QAAQ;AAAA,IAAO,OAAO,OAAO;AAAA,EAGjC,MAAM,GACJ,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,EAEV,OAAO;AAAA;AAQR,eAAsB,qBAAqB,CAC1C,IACA,OACA,MACyB;AAAA,EACzB,MAAM,SAAS,MAAM,GACnB,YAAY,aAAa,EACzB,IAAI,EAAE,SAAS,IAAI,KAAO,CAAC,EAC3B,MAAM,SAAS,KAAK,KAAK,EACzB,MAAM,QAAQ,KAAK,IAAI,EACvB,MAAM,WAAW,MAAM,IAAI,EAC3B,MAAM,cAAc,KAAK,IAAI,IAAM,EACnC,MAAM,mBAAmB,KAAK,CAAC,EAC/B,UAAU,OAAO,EACjB,iBAAiB;AAAA,EAEnB,IAAI,QAAQ;AAAA,IAAO,OAAO,OAAO;AAAA,EAGjC,MAAM,GACJ,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,EAEV,OAAO;AAAA;",
|
|
8
|
+
"debugId": "8F0ACA05832385B164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -146,6 +146,7 @@ interface MagicLinksTable {
|
|
|
146
146
|
id: Generated<string>;
|
|
147
147
|
email: string;
|
|
148
148
|
token: string;
|
|
149
|
+
code: string | null;
|
|
149
150
|
expires_at: Date;
|
|
150
151
|
used_at: Date | null;
|
|
151
152
|
failed_attempts: Generated<number>;
|
|
@@ -267,6 +268,7 @@ declare function registerSubgraph(db: Kysely<Database>, data: {
|
|
|
267
268
|
handlerPath: string
|
|
268
269
|
apiKeyId?: string
|
|
269
270
|
schemaName?: string
|
|
271
|
+
startBlock?: number
|
|
270
272
|
}): Promise<Subgraph>;
|
|
271
273
|
declare function getSubgraph(db: Kysely<Database>, name: string, apiKeyId?: string): Promise<Subgraph | null>;
|
|
272
274
|
declare function listSubgraphs(db: Kysely<Database>, apiKeyId?: string): Promise<Subgraph[]>;
|
|
@@ -49,13 +49,15 @@ async function registerSubgraph(db, data) {
|
|
|
49
49
|
schema_hash: data.schemaHash,
|
|
50
50
|
handler_path: data.handlerPath,
|
|
51
51
|
api_key_id: data.apiKeyId,
|
|
52
|
-
schema_name: data.schemaName ?? null
|
|
52
|
+
schema_name: data.schemaName ?? null,
|
|
53
|
+
start_block: data.startBlock ?? 0
|
|
53
54
|
}).onConflict((oc) => oc.columns(["name", "api_key_id"]).doUpdateSet({
|
|
54
55
|
version: data.version,
|
|
55
56
|
definition: jsonb(data.definition),
|
|
56
57
|
schema_hash: data.schemaHash,
|
|
57
58
|
handler_path: data.handlerPath,
|
|
58
59
|
schema_name: data.schemaName ?? null,
|
|
60
|
+
start_block: data.startBlock ?? 0,
|
|
59
61
|
updated_at: new Date
|
|
60
62
|
})).returningAll().executeTakeFirstOrThrow();
|
|
61
63
|
}
|
|
@@ -111,5 +113,5 @@ export {
|
|
|
111
113
|
deleteSubgraph
|
|
112
114
|
};
|
|
113
115
|
|
|
114
|
-
//# debugId=
|
|
116
|
+
//# debugId=3B8125C51D68206164756E2164756E21
|
|
115
117
|
//# 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 { type RawBuilder, sql } 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\tconst escaped = JSON.stringify(value).replace(/'/g, \"''\");\n\treturn 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\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n",
|
|
6
|
-
"import { type Kysely, sql } 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\tconst safeName = subgraphName.replace(/-/g, \"_\");\n\tif (!keyPrefix) {\n\t\treturn `subgraph_${safeName}`;\n\t}\n\tconst safePrefix = keyPrefix.replace(/^sk-sl_/, \"\").replace(/-/g, \"_\");\n\treturn `subgraph_${safePrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tname: string;\n\t\tversion: string;\n\t\tdefinition: Record<string, unknown>;\n\t\tschemaHash: string;\n\t\thandlerPath: string;\n\t\tapiKeyId?: string;\n\t\tschemaName?: string;\n\t},\n): Promise<Subgraph> {\n\treturn await db\n\t\t.insertInto(\"subgraphs\")\n\t\t.values({\n\t\t\tname: data.name,\n\t\t\tversion: data.version,\n\t\t\tdefinition: jsonb(data.definition) as any,\n\t\t\tschema_hash: data.schemaHash,\n\t\t\thandler_path: data.handlerPath,\n\t\t\tapi_key_id: data.apiKeyId!,\n\t\t\tschema_name: data.schemaName ?? null,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"name\", \"api_key_id\"]).doUpdateSet({\n\t\t\t\tversion: data.version,\n\t\t\t\tdefinition: jsonb(data.definition) as any,\n\t\t\t\tschema_hash: data.schemaHash,\n\t\t\t\thandler_path: data.handlerPath,\n\t\t\t\tschema_name: data.schemaName ?? null,\n\t\t\t\tupdated_at: new Date(),\n\t\t\t}),\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId?: string,\n): Promise<Subgraph | null> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll().where(\"name\", \"=\", name);\n\n\tif (apiKeyId) {\n\t\tquery = query.where(\"api_key_id\", \"=\", apiKeyId);\n\t}\n\n\treturn (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function listSubgraphs(\n\tdb: Kysely<Database>,\n\tapiKeyId?: string,\n): Promise<Subgraph[]> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll();\n\tif (apiKeyId) {\n\t\tquery = query.where(\"api_key_id\", \"=\", apiKeyId);\n\t}\n\treturn query.execute();\n}\n\nexport async function updateSubgraphStatus(\n\tdb: Kysely<Database>,\n\tname: string,\n\tstatus: string,\n\tlastProcessedBlock?: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tstatus,\n\t\t\t...(lastProcessedBlock !== undefined\n\t\t\t\t? { last_processed_block: lastProcessedBlock }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function recordSubgraphProcessed(\n\tdb: Kysely<Database>,\n\tname: string,\n\tprocessed: number,\n\terrors: number,\n\tlastError?: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\ttotal_processed: sql`total_processed + ${processed}`,\n\t\t\ttotal_errors: sql`total_errors + ${errors}`,\n\t\t\t...(lastError\n\t\t\t\t? { last_error: lastError, last_error_at: new Date() }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function updateSubgraphHandlerPath(\n\tdb: Kysely<Database>,\n\tname: string,\n\thandlerPath: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ handler_path: handlerPath, updated_at: new Date() })\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function deleteSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId?: string,\n): Promise<Subgraph | null> {\n\tconst subgraph = await getSubgraph(db, name, apiKeyId);\n\tif (!subgraph) return null;\n\n\t// Use stored schema_name if available, otherwise compute\n\tconst schemaName = subgraph.schema_name ?? pgSchemaName(name);\n\n\t// Drop the subgraph's schema (CASCADE drops all tables within)\n\tawait sql`DROP SCHEMA IF EXISTS ${sql.raw(`\"${schemaName}\"`)} CASCADE`.execute(\n\t\tdb,\n\t);\n\n\t// Remove from registry\n\tawait db.deleteFrom(\"subgraphs\").where(\"id\", \"=\", subgraph.id).execute();\n\n\treturn subgraph;\n}\n"
|
|
6
|
+
"import { type Kysely, sql } 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\tconst safeName = subgraphName.replace(/-/g, \"_\");\n\tif (!keyPrefix) {\n\t\treturn `subgraph_${safeName}`;\n\t}\n\tconst safePrefix = keyPrefix.replace(/^sk-sl_/, \"\").replace(/-/g, \"_\");\n\treturn `subgraph_${safePrefix}_${safeName}`;\n}\n\nexport async function registerSubgraph(\n\tdb: Kysely<Database>,\n\tdata: {\n\t\tname: string;\n\t\tversion: string;\n\t\tdefinition: Record<string, unknown>;\n\t\tschemaHash: string;\n\t\thandlerPath: string;\n\t\tapiKeyId?: string;\n\t\tschemaName?: string;\n\t\tstartBlock?: number;\n\t},\n): Promise<Subgraph> {\n\treturn await db\n\t\t.insertInto(\"subgraphs\")\n\t\t.values({\n\t\t\tname: data.name,\n\t\t\tversion: data.version,\n\t\t\tdefinition: jsonb(data.definition) as any,\n\t\t\tschema_hash: data.schemaHash,\n\t\t\thandler_path: data.handlerPath,\n\t\t\tapi_key_id: data.apiKeyId!,\n\t\t\tschema_name: data.schemaName ?? null,\n\t\t\tstart_block: data.startBlock ?? 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"name\", \"api_key_id\"]).doUpdateSet({\n\t\t\t\tversion: data.version,\n\t\t\t\tdefinition: jsonb(data.definition) as any,\n\t\t\t\tschema_hash: data.schemaHash,\n\t\t\t\thandler_path: data.handlerPath,\n\t\t\t\tschema_name: data.schemaName ?? null,\n\t\t\t\tstart_block: data.startBlock ?? 0,\n\t\t\t\tupdated_at: new Date(),\n\t\t\t}),\n\t\t)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId?: string,\n): Promise<Subgraph | null> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll().where(\"name\", \"=\", name);\n\n\tif (apiKeyId) {\n\t\tquery = query.where(\"api_key_id\", \"=\", apiKeyId);\n\t}\n\n\treturn (await query.executeTakeFirst()) ?? null;\n}\n\nexport async function listSubgraphs(\n\tdb: Kysely<Database>,\n\tapiKeyId?: string,\n): Promise<Subgraph[]> {\n\tlet query = db.selectFrom(\"subgraphs\").selectAll();\n\tif (apiKeyId) {\n\t\tquery = query.where(\"api_key_id\", \"=\", apiKeyId);\n\t}\n\treturn query.execute();\n}\n\nexport async function updateSubgraphStatus(\n\tdb: Kysely<Database>,\n\tname: string,\n\tstatus: string,\n\tlastProcessedBlock?: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\tstatus,\n\t\t\t...(lastProcessedBlock !== undefined\n\t\t\t\t? { last_processed_block: lastProcessedBlock }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function recordSubgraphProcessed(\n\tdb: Kysely<Database>,\n\tname: string,\n\tprocessed: number,\n\terrors: number,\n\tlastError?: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({\n\t\t\ttotal_processed: sql`total_processed + ${processed}`,\n\t\t\ttotal_errors: sql`total_errors + ${errors}`,\n\t\t\t...(lastError\n\t\t\t\t? { last_error: lastError, last_error_at: new Date() }\n\t\t\t\t: {}),\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function updateSubgraphHandlerPath(\n\tdb: Kysely<Database>,\n\tname: string,\n\thandlerPath: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"subgraphs\")\n\t\t.set({ handler_path: handlerPath, updated_at: new Date() })\n\t\t.where(\"name\", \"=\", name)\n\t\t.execute();\n}\n\nexport async function deleteSubgraph(\n\tdb: Kysely<Database>,\n\tname: string,\n\tapiKeyId?: string,\n): Promise<Subgraph | null> {\n\tconst subgraph = await getSubgraph(db, name, apiKeyId);\n\tif (!subgraph) return null;\n\n\t// Use stored schema_name if available, otherwise compute\n\tconst schemaName = subgraph.schema_name ?? pgSchemaName(name);\n\n\t// Drop the subgraph's schema (CASCADE drops all tables within)\n\tawait sql`DROP SCHEMA IF EXISTS ${sql.raw(`\"${schemaName}\"`)} CASCADE`.execute(\n\t\tdb,\n\t);\n\n\t// Remove from registry\n\tawait db.deleteFrom(\"subgraphs\").where(\"id\", \"=\", subgraph.id).execute();\n\n\treturn subgraph;\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EAC1D,MAAM,UAAU,KAAK,UAAU,KAAK,EAAE,QAAQ,MAAM,IAAI;AAAA,EACxD,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQpC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EAC1D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,KAAK,MAAM,KAAK;AAAA,MACtB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;;;ACzBnB,gBAAsB;AASf,SAAS,YAAY,CAAC,cAAsB,WAA4B;AAAA,EAC9E,MAAM,WAAW,aAAa,QAAQ,MAAM,GAAG;AAAA,EAC/C,IAAI,CAAC,WAAW;AAAA,IACf,OAAO,YAAY;AAAA,EACpB;AAAA,EACA,MAAM,aAAa,UAAU,QAAQ,WAAW,EAAE,EAAE,QAAQ,MAAM,GAAG;AAAA,EACrE,OAAO,YAAY,cAAc;AAAA;AAGlC,eAAsB,gBAAgB,CACrC,IACA,
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "3B8125C51D68206164756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/src/db/schema.d.ts
CHANGED
package/dist/src/index.d.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
4
|
+
await sql`ALTER TABLE magic_links ADD COLUMN IF NOT EXISTS code text`.execute(
|
|
5
|
+
db,
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
10
|
+
await sql`ALTER TABLE magic_links DROP COLUMN IF EXISTS code`.execute(db);
|
|
11
|
+
}
|