@secondlayer/shared 0.2.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/README.md +19 -0
- package/dist/src/crypto/hmac.d.ts +26 -0
- package/dist/src/crypto/hmac.js +75 -0
- package/dist/src/crypto/hmac.js.map +10 -0
- package/dist/src/db/index.d.ts +227 -0
- package/dist/src/db/index.js +75 -0
- package/dist/src/db/index.js.map +11 -0
- package/dist/src/db/jsonb.d.ts +13 -0
- package/dist/src/db/jsonb.js +35 -0
- package/dist/src/db/jsonb.js.map +10 -0
- package/dist/src/db/queries/accounts.d.ts +179 -0
- package/dist/src/db/queries/accounts.js +39 -0
- package/dist/src/db/queries/accounts.js.map +10 -0
- package/dist/src/db/queries/integrity.d.ts +178 -0
- package/dist/src/db/queries/integrity.js +68 -0
- package/dist/src/db/queries/integrity.js.map +10 -0
- package/dist/src/db/queries/metrics.d.ts +179 -0
- package/dist/src/db/queries/metrics.js +51 -0
- package/dist/src/db/queries/metrics.js.map +10 -0
- package/dist/src/db/queries/usage.d.ts +205 -0
- package/dist/src/db/queries/usage.js +117 -0
- package/dist/src/db/queries/usage.js.map +11 -0
- package/dist/src/db/queries/views.d.ts +191 -0
- package/dist/src/db/queries/views.js +111 -0
- package/dist/src/db/queries/views.js.map +11 -0
- package/dist/src/db/schema.d.ts +207 -0
- package/dist/src/db/schema.js +3 -0
- package/dist/src/db/schema.js.map +9 -0
- package/dist/src/env.d.ts +7 -0
- package/dist/src/env.js +60 -0
- package/dist/src/env.js.map +10 -0
- package/dist/src/errors.d.ts +51 -0
- package/dist/src/errors.js +103 -0
- package/dist/src/errors.js.map +10 -0
- package/dist/src/index.d.ts +464 -0
- package/dist/src/index.js +642 -0
- package/dist/src/index.js.map +19 -0
- package/dist/src/lib/plans.d.ts +10 -0
- package/dist/src/lib/plans.js +34 -0
- package/dist/src/lib/plans.js.map +10 -0
- package/dist/src/logger.d.ts +2 -0
- package/dist/src/logger.js +130 -0
- package/dist/src/logger.js.map +11 -0
- package/dist/src/node/client.d.ts +35 -0
- package/dist/src/node/client.js +56 -0
- package/dist/src/node/client.js.map +10 -0
- package/dist/src/node/hiro-client.d.ts +186 -0
- package/dist/src/node/hiro-client.js +410 -0
- package/dist/src/node/hiro-client.js.map +12 -0
- package/dist/src/queue/index.d.ts +50 -0
- package/dist/src/queue/index.js +176 -0
- package/dist/src/queue/index.js.map +12 -0
- package/dist/src/queue/listener.d.ts +20 -0
- package/dist/src/queue/listener.js +63 -0
- package/dist/src/queue/listener.js.map +10 -0
- package/dist/src/queue/recovery.d.ts +14 -0
- package/dist/src/queue/recovery.js +100 -0
- package/dist/src/queue/recovery.js.map +12 -0
- package/dist/src/schemas/filters.d.ts +30 -0
- package/dist/src/schemas/filters.js +133 -0
- package/dist/src/schemas/filters.js.map +10 -0
- package/dist/src/schemas/index.d.ts +109 -0
- package/dist/src/schemas/index.js +228 -0
- package/dist/src/schemas/index.js.map +12 -0
- package/dist/src/schemas/views.d.ts +51 -0
- package/dist/src/schemas/views.js +29 -0
- package/dist/src/schemas/views.js.map +10 -0
- package/dist/src/types.d.ts +102 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +9 -0
- package/migrations/0001_initial.ts +182 -0
- package/migrations/0002_api_keys.ts +38 -0
- package/migrations/0003_tenant_isolation.ts +114 -0
- package/migrations/0004_accounts_and_usage.ts +90 -0
- package/migrations/0005_sessions.ts +42 -0
- package/package.json +128 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/db/jsonb.ts
|
|
14
|
+
import { sql } from "kysely";
|
|
15
|
+
function jsonb(value) {
|
|
16
|
+
const escaped = JSON.stringify(value).replace(/'/g, "''");
|
|
17
|
+
return sql`${sql.raw(`'${escaped}'::jsonb`)}`;
|
|
18
|
+
}
|
|
19
|
+
function parseJsonb(value) {
|
|
20
|
+
if (typeof value === "string") {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value);
|
|
23
|
+
} catch {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return value ?? {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/db/index.ts
|
|
31
|
+
import { Kysely } from "kysely";
|
|
32
|
+
import { PostgresJSDialect } from "kysely-postgres-js";
|
|
33
|
+
import postgres from "postgres";
|
|
34
|
+
import { sql as sql2 } from "kysely";
|
|
35
|
+
var db = null;
|
|
36
|
+
var rawClient = null;
|
|
37
|
+
function getDb(connectionString) {
|
|
38
|
+
if (!db) {
|
|
39
|
+
const url = connectionString || process.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/streams_dev";
|
|
40
|
+
const isLocal = url.includes("localhost") || url.includes("127.0.0.1") || url.includes("@postgres:");
|
|
41
|
+
rawClient = postgres(url, {
|
|
42
|
+
ssl: isLocal ? undefined : { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" }
|
|
43
|
+
});
|
|
44
|
+
db = new Kysely({
|
|
45
|
+
dialect: new PostgresJSDialect({ postgres: rawClient })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return db;
|
|
49
|
+
}
|
|
50
|
+
function getRawClient() {
|
|
51
|
+
if (!rawClient)
|
|
52
|
+
getDb();
|
|
53
|
+
return rawClient;
|
|
54
|
+
}
|
|
55
|
+
async function closeDb() {
|
|
56
|
+
if (db) {
|
|
57
|
+
await db.destroy();
|
|
58
|
+
db = null;
|
|
59
|
+
}
|
|
60
|
+
if (rawClient) {
|
|
61
|
+
await rawClient.end();
|
|
62
|
+
rawClient = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// src/queue/index.ts
|
|
66
|
+
var exports_queue = {};
|
|
67
|
+
__export(exports_queue, {
|
|
68
|
+
stats: () => stats,
|
|
69
|
+
getWorkerId: () => getWorkerId,
|
|
70
|
+
fail: () => fail,
|
|
71
|
+
enqueue: () => enqueue,
|
|
72
|
+
complete: () => complete,
|
|
73
|
+
claim: () => claim,
|
|
74
|
+
WORKER_ID: () => WORKER_ID
|
|
75
|
+
});
|
|
76
|
+
import { sql as sql3 } from "kysely";
|
|
77
|
+
import { randomUUID } from "crypto";
|
|
78
|
+
var WORKER_ID = `worker-${randomUUID().slice(0, 8)}`;
|
|
79
|
+
async function enqueue(streamId, blockHeight, backfill = false) {
|
|
80
|
+
const db2 = getDb();
|
|
81
|
+
const row = await db2.insertInto("jobs").values({
|
|
82
|
+
stream_id: streamId,
|
|
83
|
+
block_height: blockHeight,
|
|
84
|
+
backfill,
|
|
85
|
+
status: "pending",
|
|
86
|
+
attempts: 0
|
|
87
|
+
}).returning(["id"]).executeTakeFirstOrThrow();
|
|
88
|
+
return row.id;
|
|
89
|
+
}
|
|
90
|
+
async function claim() {
|
|
91
|
+
const db2 = getDb();
|
|
92
|
+
const { rows } = await sql3`
|
|
93
|
+
UPDATE jobs
|
|
94
|
+
SET
|
|
95
|
+
status = 'processing',
|
|
96
|
+
locked_at = NOW(),
|
|
97
|
+
locked_by = ${WORKER_ID},
|
|
98
|
+
attempts = attempts + 1
|
|
99
|
+
WHERE id = (
|
|
100
|
+
SELECT id FROM jobs
|
|
101
|
+
WHERE status = 'pending'
|
|
102
|
+
ORDER BY
|
|
103
|
+
backfill ASC,
|
|
104
|
+
block_height ASC,
|
|
105
|
+
created_at ASC
|
|
106
|
+
FOR UPDATE SKIP LOCKED
|
|
107
|
+
LIMIT 1
|
|
108
|
+
)
|
|
109
|
+
RETURNING *
|
|
110
|
+
`.execute(db2);
|
|
111
|
+
return rows[0] ?? null;
|
|
112
|
+
}
|
|
113
|
+
async function complete(jobId) {
|
|
114
|
+
const db2 = getDb();
|
|
115
|
+
await db2.updateTable("jobs").set({
|
|
116
|
+
status: "completed",
|
|
117
|
+
completed_at: new Date,
|
|
118
|
+
locked_at: null,
|
|
119
|
+
locked_by: null
|
|
120
|
+
}).where("id", "=", jobId).execute();
|
|
121
|
+
}
|
|
122
|
+
async function fail(jobId, error, maxAttempts = 3) {
|
|
123
|
+
const db2 = getDb();
|
|
124
|
+
const job = await db2.selectFrom("jobs").select("attempts").where("id", "=", jobId).executeTakeFirst();
|
|
125
|
+
if (!job)
|
|
126
|
+
return;
|
|
127
|
+
if (job.attempts < maxAttempts) {
|
|
128
|
+
await db2.updateTable("jobs").set({
|
|
129
|
+
status: "pending",
|
|
130
|
+
error,
|
|
131
|
+
locked_at: null,
|
|
132
|
+
locked_by: null
|
|
133
|
+
}).where("id", "=", jobId).execute();
|
|
134
|
+
} else {
|
|
135
|
+
await db2.updateTable("jobs").set({
|
|
136
|
+
status: "failed",
|
|
137
|
+
error,
|
|
138
|
+
completed_at: new Date,
|
|
139
|
+
locked_at: null,
|
|
140
|
+
locked_by: null
|
|
141
|
+
}).where("id", "=", jobId).execute();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function stats() {
|
|
145
|
+
const { rows } = await sql3`
|
|
146
|
+
SELECT status, COUNT(*) as count
|
|
147
|
+
FROM jobs
|
|
148
|
+
GROUP BY status
|
|
149
|
+
`.execute(getDb());
|
|
150
|
+
const counts = {};
|
|
151
|
+
for (const row of rows) {
|
|
152
|
+
counts[row.status] = parseInt(row.count, 10);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
pending: counts["pending"] || 0,
|
|
156
|
+
processing: counts["processing"] || 0,
|
|
157
|
+
completed: counts["completed"] || 0,
|
|
158
|
+
failed: counts["failed"] || 0,
|
|
159
|
+
total: Object.values(counts).reduce((a, b) => a + b, 0)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function getWorkerId() {
|
|
163
|
+
return WORKER_ID;
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
stats,
|
|
167
|
+
getWorkerId,
|
|
168
|
+
fail,
|
|
169
|
+
enqueue,
|
|
170
|
+
complete,
|
|
171
|
+
claim,
|
|
172
|
+
WORKER_ID
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
//# debugId=886805471F1F907464756E2164756E21
|
|
176
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/db/jsonb.ts", "../src/db/index.ts", "../src/queue/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { 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) {\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",
|
|
7
|
+
"import { sql } from \"kysely\";\nimport { getDb } from \"../db/index.ts\";\nimport type { Job } from \"../db/types.ts\";\nimport { randomUUID } from \"crypto\";\n\nexport interface QueueStats {\n pending: number;\n processing: number;\n completed: number;\n failed: number;\n total: number;\n}\n\n// Worker identifier for this process\nconst WORKER_ID = `worker-${randomUUID().slice(0, 8)}`;\n\n/**\n * Enqueue a new job for stream evaluation\n */\nexport async function enqueue(\n streamId: string,\n blockHeight: number,\n backfill = false\n): Promise<string> {\n const db = getDb();\n\n const row = await db\n .insertInto(\"jobs\")\n .values({\n stream_id: streamId,\n block_height: blockHeight,\n backfill,\n status: \"pending\",\n attempts: 0,\n })\n .returning([\"id\"])\n .executeTakeFirstOrThrow();\n\n return row.id;\n}\n\n/**\n * Claim a pending job using SKIP LOCKED to prevent concurrent access\n * Returns null if no jobs available\n */\nexport async function claim(): Promise<Job | null> {\n const db = getDb();\n\n const { rows } = await sql<Job>`\n UPDATE jobs\n SET\n status = 'processing',\n locked_at = NOW(),\n locked_by = ${WORKER_ID},\n attempts = attempts + 1\n WHERE id = (\n SELECT id FROM jobs\n WHERE status = 'pending'\n ORDER BY\n backfill ASC,\n block_height ASC,\n created_at ASC\n FOR UPDATE SKIP LOCKED\n LIMIT 1\n )\n RETURNING *\n `.execute(db);\n\n return rows[0] ?? null;\n}\n\n/**\n * Mark a job as completed\n */\nexport async function complete(jobId: string): Promise<void> {\n const db = getDb();\n\n await db\n .updateTable(\"jobs\")\n .set({\n status: \"completed\",\n completed_at: new Date(),\n locked_at: null,\n locked_by: null,\n })\n .where(\"id\", \"=\", jobId)\n .execute();\n}\n\n/**\n * Mark a job as failed\n * Re-queues if under max attempts, otherwise marks as permanently failed\n */\nexport async function fail(\n jobId: string,\n error: string,\n maxAttempts = 3\n): Promise<void> {\n const db = getDb();\n\n const job = await db\n .selectFrom(\"jobs\")\n .select(\"attempts\")\n .where(\"id\", \"=\", jobId)\n .executeTakeFirst();\n\n if (!job) return;\n\n if (job.attempts < maxAttempts) {\n await db\n .updateTable(\"jobs\")\n .set({\n status: \"pending\",\n error,\n locked_at: null,\n locked_by: null,\n })\n .where(\"id\", \"=\", jobId)\n .execute();\n } else {\n await db\n .updateTable(\"jobs\")\n .set({\n status: \"failed\",\n error,\n completed_at: new Date(),\n locked_at: null,\n locked_by: null,\n })\n .where(\"id\", \"=\", jobId)\n .execute();\n }\n}\n\n/**\n * Get queue statistics\n */\nexport async function stats(): Promise<QueueStats> {\n const { rows } = await sql<{ status: string; count: string }>`\n SELECT status, COUNT(*) as count\n FROM jobs\n GROUP BY status\n `.execute(getDb());\n\n const counts: Record<string, number> = {};\n for (const row of rows) {\n counts[row.status] = parseInt(row.count, 10);\n }\n\n return {\n pending: counts[\"pending\"] || 0,\n processing: counts[\"processing\"] || 0,\n completed: counts[\"completed\"] || 0,\n failed: counts[\"failed\"] || 0,\n total: Object.values(counts).reduce((a, b) => a + b, 0),\n };\n}\n\n/**\n * Get worker ID for this process\n */\nexport function getWorkerId(): string {\n return WORKER_ID;\n}\n\nexport { WORKER_ID };\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "886805471F1F907464756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const CHANNEL_NEW_JOB = "streams:new_job";
|
|
2
|
+
/**
|
|
3
|
+
* Listen for notifications on a channel
|
|
4
|
+
* Uses a dedicated connection for LISTEN
|
|
5
|
+
*/
|
|
6
|
+
declare function listen(channel: string, callback: (payload?: string) => void): Promise<() => Promise<void>>;
|
|
7
|
+
/**
|
|
8
|
+
* Send a notification on a channel
|
|
9
|
+
*/
|
|
10
|
+
declare function notify(channel: string, payload?: string): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Listen for new job notifications
|
|
13
|
+
* Convenience wrapper for the common use case
|
|
14
|
+
*/
|
|
15
|
+
declare function listenForJobs(callback: (payload?: string) => void): Promise<() => Promise<void>>;
|
|
16
|
+
/**
|
|
17
|
+
* Notify workers about new jobs
|
|
18
|
+
*/
|
|
19
|
+
declare function notifyNewJob(streamId?: string): Promise<void>;
|
|
20
|
+
export { notifyNewJob, notify, listenForJobs, listen, CHANNEL_NEW_JOB };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/queue/listener.ts
|
|
14
|
+
import postgres from "postgres";
|
|
15
|
+
var CHANNEL_NEW_JOB = "streams:new_job";
|
|
16
|
+
async function listen(channel, callback) {
|
|
17
|
+
const connectionString = process.env.DATABASE_URL;
|
|
18
|
+
if (!connectionString) {
|
|
19
|
+
throw new Error("DATABASE_URL is required");
|
|
20
|
+
}
|
|
21
|
+
const client = postgres(connectionString, {
|
|
22
|
+
max: 1,
|
|
23
|
+
onnotice: () => {}
|
|
24
|
+
});
|
|
25
|
+
await client.listen(channel, (payload) => {
|
|
26
|
+
callback(payload);
|
|
27
|
+
});
|
|
28
|
+
return async () => {
|
|
29
|
+
await client.end();
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function notify(channel, payload) {
|
|
33
|
+
const connectionString = process.env.DATABASE_URL;
|
|
34
|
+
if (!connectionString) {
|
|
35
|
+
throw new Error("DATABASE_URL is required");
|
|
36
|
+
}
|
|
37
|
+
const client = postgres(connectionString, { max: 1 });
|
|
38
|
+
try {
|
|
39
|
+
if (payload) {
|
|
40
|
+
await client`SELECT pg_notify(${channel}, ${payload})`;
|
|
41
|
+
} else {
|
|
42
|
+
await client`SELECT pg_notify(${channel}, '')`;
|
|
43
|
+
}
|
|
44
|
+
} finally {
|
|
45
|
+
await client.end();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function listenForJobs(callback) {
|
|
49
|
+
return listen(CHANNEL_NEW_JOB, callback);
|
|
50
|
+
}
|
|
51
|
+
async function notifyNewJob(streamId) {
|
|
52
|
+
return notify(CHANNEL_NEW_JOB, streamId);
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
notifyNewJob,
|
|
56
|
+
notify,
|
|
57
|
+
listenForJobs,
|
|
58
|
+
listen,
|
|
59
|
+
CHANNEL_NEW_JOB
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//# debugId=60629118C41A17E064756E2164756E21
|
|
63
|
+
//# sourceMappingURL=listener.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/queue/listener.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import postgres from \"postgres\";\n\nconst CHANNEL_NEW_JOB = \"streams:new_job\";\n\n/**\n * Listen for notifications on a channel\n * Uses a dedicated connection for LISTEN\n */\nexport async function listen(\n channel: string,\n callback: (payload?: string) => void\n): Promise<() => Promise<void>> {\n const connectionString = process.env.DATABASE_URL;\n if (!connectionString) {\n throw new Error(\"DATABASE_URL is required\");\n }\n\n // Each listener gets its own connection\n const client = postgres(connectionString, {\n max: 1,\n onnotice: () => {}, // Ignore notices\n });\n\n await client.listen(channel, (payload) => {\n callback(payload);\n });\n\n // Return cleanup function\n return async () => {\n await client.end();\n };\n}\n\n/**\n * Send a notification on a channel\n */\nexport async function notify(\n channel: string,\n payload?: string\n): Promise<void> {\n const connectionString = process.env.DATABASE_URL;\n if (!connectionString) {\n throw new Error(\"DATABASE_URL is required\");\n }\n\n const client = postgres(connectionString, { max: 1 });\n\n try {\n if (payload) {\n await client`SELECT pg_notify(${channel}, ${payload})`;\n } else {\n await client`SELECT pg_notify(${channel}, '')`;\n }\n } finally {\n await client.end();\n }\n}\n\n/**\n * Listen for new job notifications\n * Convenience wrapper for the common use case\n */\nexport async function listenForJobs(\n callback: (payload?: string) => void\n): Promise<() => Promise<void>> {\n return listen(CHANNEL_NEW_JOB, callback);\n}\n\n/**\n * Notify workers about new jobs\n */\nexport async function notifyNewJob(streamId?: string): Promise<void> {\n return notify(CHANNEL_NEW_JOB, streamId);\n}\n\nexport { CHANNEL_NEW_JOB };\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;AAAA;AAEA,IAAM,kBAAkB;AAMxB,eAAsB,MAAM,CAC1B,SACA,UAC8B;AAAA,EAC9B,MAAM,mBAAmB,QAAQ,IAAI;AAAA,EACrC,IAAI,CAAC,kBAAkB;AAAA,IACrB,MAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAAA,EAGA,MAAM,SAAS,SAAS,kBAAkB;AAAA,IACxC,KAAK;AAAA,IACL,UAAU,MAAM;AAAA,EAClB,CAAC;AAAA,EAED,MAAM,OAAO,OAAO,SAAS,CAAC,YAAY;AAAA,IACxC,SAAS,OAAO;AAAA,GACjB;AAAA,EAGD,OAAO,YAAY;AAAA,IACjB,MAAM,OAAO,IAAI;AAAA;AAAA;AAOrB,eAAsB,MAAM,CAC1B,SACA,SACe;AAAA,EACf,MAAM,mBAAmB,QAAQ,IAAI;AAAA,EACrC,IAAI,CAAC,kBAAkB;AAAA,IACrB,MAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAAA,EAEA,MAAM,SAAS,SAAS,kBAAkB,EAAE,KAAK,EAAE,CAAC;AAAA,EAEpD,IAAI;AAAA,IACF,IAAI,SAAS;AAAA,MACX,MAAM,0BAA0B,YAAY;AAAA,IAC9C,EAAO;AAAA,MACL,MAAM,0BAA0B;AAAA;AAAA,YAElC;AAAA,IACA,MAAM,OAAO,IAAI;AAAA;AAAA;AAQrB,eAAsB,aAAa,CACjC,UAC8B;AAAA,EAC9B,OAAO,OAAO,iBAAiB,QAAQ;AAAA;AAMzC,eAAsB,YAAY,CAAC,UAAkC;AAAA,EACnE,OAAO,OAAO,iBAAiB,QAAQ;AAAA;",
|
|
8
|
+
"debugId": "60629118C41A17E064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recover jobs that have been locked for longer than the threshold
|
|
3
|
+
* These are likely from crashed workers
|
|
4
|
+
*
|
|
5
|
+
* @param staleThresholdMinutes - Minutes after which a locked job is considered stale
|
|
6
|
+
* @returns Number of recovered jobs
|
|
7
|
+
*/
|
|
8
|
+
declare function recoverStaleJobs(staleThresholdMinutes?: number): Promise<number>;
|
|
9
|
+
/**
|
|
10
|
+
* Run periodic stale job recovery
|
|
11
|
+
* Returns a cleanup function to stop the interval
|
|
12
|
+
*/
|
|
13
|
+
declare function startRecoveryLoop(intervalMs?: number, staleThresholdMinutes?: number): () => void;
|
|
14
|
+
export { startRecoveryLoop, recoverStaleJobs };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/db/jsonb.ts
|
|
14
|
+
import { sql } from "kysely";
|
|
15
|
+
function jsonb(value) {
|
|
16
|
+
const escaped = JSON.stringify(value).replace(/'/g, "''");
|
|
17
|
+
return sql`${sql.raw(`'${escaped}'::jsonb`)}`;
|
|
18
|
+
}
|
|
19
|
+
function parseJsonb(value) {
|
|
20
|
+
if (typeof value === "string") {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value);
|
|
23
|
+
} catch {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return value ?? {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/db/index.ts
|
|
31
|
+
import { Kysely } from "kysely";
|
|
32
|
+
import { PostgresJSDialect } from "kysely-postgres-js";
|
|
33
|
+
import postgres from "postgres";
|
|
34
|
+
import { sql as sql2 } from "kysely";
|
|
35
|
+
var db = null;
|
|
36
|
+
var rawClient = null;
|
|
37
|
+
function getDb(connectionString) {
|
|
38
|
+
if (!db) {
|
|
39
|
+
const url = connectionString || process.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/streams_dev";
|
|
40
|
+
const isLocal = url.includes("localhost") || url.includes("127.0.0.1") || url.includes("@postgres:");
|
|
41
|
+
rawClient = postgres(url, {
|
|
42
|
+
ssl: isLocal ? undefined : { rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" }
|
|
43
|
+
});
|
|
44
|
+
db = new Kysely({
|
|
45
|
+
dialect: new PostgresJSDialect({ postgres: rawClient })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return db;
|
|
49
|
+
}
|
|
50
|
+
function getRawClient() {
|
|
51
|
+
if (!rawClient)
|
|
52
|
+
getDb();
|
|
53
|
+
return rawClient;
|
|
54
|
+
}
|
|
55
|
+
async function closeDb() {
|
|
56
|
+
if (db) {
|
|
57
|
+
await db.destroy();
|
|
58
|
+
db = null;
|
|
59
|
+
}
|
|
60
|
+
if (rawClient) {
|
|
61
|
+
await rawClient.end();
|
|
62
|
+
rawClient = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// src/queue/recovery.ts
|
|
66
|
+
import { sql as sql3 } from "kysely";
|
|
67
|
+
async function recoverStaleJobs(staleThresholdMinutes = 5) {
|
|
68
|
+
const { rows } = await sql3`
|
|
69
|
+
UPDATE jobs
|
|
70
|
+
SET
|
|
71
|
+
status = 'pending',
|
|
72
|
+
locked_at = NULL,
|
|
73
|
+
locked_by = NULL
|
|
74
|
+
WHERE
|
|
75
|
+
status = 'processing'
|
|
76
|
+
AND locked_at < NOW() - INTERVAL '${sql3.raw(staleThresholdMinutes.toString())} minutes'
|
|
77
|
+
RETURNING id
|
|
78
|
+
`.execute(getDb());
|
|
79
|
+
return rows.length;
|
|
80
|
+
}
|
|
81
|
+
function startRecoveryLoop(intervalMs = 60000, staleThresholdMinutes = 5) {
|
|
82
|
+
const intervalId = setInterval(async () => {
|
|
83
|
+
try {
|
|
84
|
+
const recovered = await recoverStaleJobs(staleThresholdMinutes);
|
|
85
|
+
if (recovered > 0) {
|
|
86
|
+
console.log(`Recovered ${recovered} stale jobs`);
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("Error recovering stale jobs:", error);
|
|
90
|
+
}
|
|
91
|
+
}, intervalMs);
|
|
92
|
+
return () => clearInterval(intervalId);
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
startRecoveryLoop,
|
|
96
|
+
recoverStaleJobs
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
//# debugId=87547F4988908A4464756E2164756E21
|
|
100
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/db/jsonb.ts", "../src/db/index.ts", "../src/queue/recovery.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { 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) {\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",
|
|
7
|
+
"import { sql } from \"kysely\";\nimport { getDb } from \"../db/index.ts\";\n\n/**\n * Recover jobs that have been locked for longer than the threshold\n * These are likely from crashed workers\n *\n * @param staleThresholdMinutes - Minutes after which a locked job is considered stale\n * @returns Number of recovered jobs\n */\nexport async function recoverStaleJobs(\n staleThresholdMinutes = 5\n): Promise<number> {\n const { rows } = await sql`\n UPDATE jobs\n SET\n status = 'pending',\n locked_at = NULL,\n locked_by = NULL\n WHERE\n status = 'processing'\n AND locked_at < NOW() - INTERVAL '${sql.raw(staleThresholdMinutes.toString())} minutes'\n RETURNING id\n `.execute(getDb());\n\n return rows.length;\n}\n\n/**\n * Run periodic stale job recovery\n * Returns a cleanup function to stop the interval\n */\nexport function startRecoveryLoop(\n intervalMs = 60000, // 1 minute\n staleThresholdMinutes = 5\n): () => void {\n const intervalId = setInterval(async () => {\n try {\n const recovered = await recoverStaleJobs(staleThresholdMinutes);\n if (recovered > 0) {\n console.log(`Recovered ${recovered} stale jobs`);\n }\n } catch (error) {\n console.error(\"Error recovering stale jobs:\", error);\n }\n }, intervalMs);\n\n return () => clearInterval(intervalId);\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": ";;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAgB;AAAA,EACpC,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;AAwCA,gBAAS;AArCT,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,YAAY,SAAS,KAAK;AAAA,MACxB,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;;ACvCF,gBAAS;AAUT,eAAsB,gBAAgB,CACpC,wBAAwB,GACP;AAAA,EACjB,QAAQ,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0CAQiB,KAAI,IAAI,sBAAsB,SAAS,CAAC;AAAA;AAAA,IAE9E,QAAQ,MAAM,CAAC;AAAA,EAEjB,OAAO,KAAK;AAAA;AAOP,SAAS,iBAAiB,CAC/B,aAAa,OACb,wBAAwB,GACZ;AAAA,EACZ,MAAM,aAAa,YAAY,YAAY;AAAA,IACzC,IAAI;AAAA,MACF,MAAM,YAAY,MAAM,iBAAiB,qBAAqB;AAAA,MAC9D,IAAI,YAAY,GAAG;AAAA,QACjB,QAAQ,IAAI,aAAa,sBAAsB;AAAA,MACjD;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,MAAM,gCAAgC,KAAK;AAAA;AAAA,KAEpD,UAAU;AAAA,EAEb,OAAO,MAAM,cAAc,UAAU;AAAA;",
|
|
10
|
+
"debugId": "87547F4988908A4464756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const StxTransferFilterSchema: unknown;
|
|
3
|
+
declare const StxMintFilterSchema: unknown;
|
|
4
|
+
declare const StxBurnFilterSchema: unknown;
|
|
5
|
+
declare const StxLockFilterSchema: unknown;
|
|
6
|
+
declare const FtTransferFilterSchema: unknown;
|
|
7
|
+
declare const FtMintFilterSchema: unknown;
|
|
8
|
+
declare const FtBurnFilterSchema: unknown;
|
|
9
|
+
declare const NftTransferFilterSchema: unknown;
|
|
10
|
+
declare const NftMintFilterSchema: unknown;
|
|
11
|
+
declare const NftBurnFilterSchema: unknown;
|
|
12
|
+
declare const ContractCallFilterSchema: unknown;
|
|
13
|
+
declare const ContractDeployFilterSchema: unknown;
|
|
14
|
+
declare const PrintEventFilterSchema: unknown;
|
|
15
|
+
declare const StreamFilterSchema: unknown;
|
|
16
|
+
type StxTransferFilter = z.infer<typeof StxTransferFilterSchema>;
|
|
17
|
+
type StxMintFilter = z.infer<typeof StxMintFilterSchema>;
|
|
18
|
+
type StxBurnFilter = z.infer<typeof StxBurnFilterSchema>;
|
|
19
|
+
type StxLockFilter = z.infer<typeof StxLockFilterSchema>;
|
|
20
|
+
type FtTransferFilter = z.infer<typeof FtTransferFilterSchema>;
|
|
21
|
+
type FtMintFilter = z.infer<typeof FtMintFilterSchema>;
|
|
22
|
+
type FtBurnFilter = z.infer<typeof FtBurnFilterSchema>;
|
|
23
|
+
type NftTransferFilter = z.infer<typeof NftTransferFilterSchema>;
|
|
24
|
+
type NftMintFilter = z.infer<typeof NftMintFilterSchema>;
|
|
25
|
+
type NftBurnFilter = z.infer<typeof NftBurnFilterSchema>;
|
|
26
|
+
type ContractCallFilter = z.infer<typeof ContractCallFilterSchema>;
|
|
27
|
+
type ContractDeployFilter = z.infer<typeof ContractDeployFilterSchema>;
|
|
28
|
+
type PrintEventFilter = z.infer<typeof PrintEventFilterSchema>;
|
|
29
|
+
type StreamFilter = z.infer<typeof StreamFilterSchema>;
|
|
30
|
+
export { StxTransferFilterSchema, StxTransferFilter, StxMintFilterSchema, StxMintFilter, StxLockFilterSchema, StxLockFilter, StxBurnFilterSchema, StxBurnFilter, StreamFilterSchema, StreamFilter, PrintEventFilterSchema, PrintEventFilter, NftTransferFilterSchema, NftTransferFilter, NftMintFilterSchema, NftMintFilter, NftBurnFilterSchema, NftBurnFilter, FtTransferFilterSchema, FtTransferFilter, FtMintFilterSchema, FtMintFilter, FtBurnFilterSchema, FtBurnFilter, ContractDeployFilterSchema, ContractDeployFilter, ContractCallFilterSchema, ContractCallFilter };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/schemas/filters.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { validateStacksAddress } from "@stacks/transactions";
|
|
16
|
+
var stacksPrincipal = z.string().refine((val) => {
|
|
17
|
+
const parts = val.split(".");
|
|
18
|
+
if (parts.length > 2)
|
|
19
|
+
return false;
|
|
20
|
+
return validateStacksAddress(parts[0]);
|
|
21
|
+
}, "Invalid Stacks principal address");
|
|
22
|
+
var baseFilter = {
|
|
23
|
+
sender: stacksPrincipal.optional(),
|
|
24
|
+
recipient: stacksPrincipal.optional()
|
|
25
|
+
};
|
|
26
|
+
var StxTransferFilterSchema = z.object({
|
|
27
|
+
type: z.literal("stx_transfer"),
|
|
28
|
+
...baseFilter,
|
|
29
|
+
minAmount: z.coerce.number().int().positive().optional(),
|
|
30
|
+
maxAmount: z.coerce.number().int().positive().optional()
|
|
31
|
+
});
|
|
32
|
+
var StxMintFilterSchema = z.object({
|
|
33
|
+
type: z.literal("stx_mint"),
|
|
34
|
+
recipient: stacksPrincipal.optional(),
|
|
35
|
+
minAmount: z.coerce.number().int().positive().optional()
|
|
36
|
+
});
|
|
37
|
+
var StxBurnFilterSchema = z.object({
|
|
38
|
+
type: z.literal("stx_burn"),
|
|
39
|
+
sender: stacksPrincipal.optional(),
|
|
40
|
+
minAmount: z.coerce.number().int().positive().optional()
|
|
41
|
+
});
|
|
42
|
+
var StxLockFilterSchema = z.object({
|
|
43
|
+
type: z.literal("stx_lock"),
|
|
44
|
+
lockedAddress: stacksPrincipal.optional(),
|
|
45
|
+
minAmount: z.coerce.number().int().positive().optional()
|
|
46
|
+
});
|
|
47
|
+
var FtTransferFilterSchema = z.object({
|
|
48
|
+
type: z.literal("ft_transfer"),
|
|
49
|
+
...baseFilter,
|
|
50
|
+
assetIdentifier: z.string().optional(),
|
|
51
|
+
minAmount: z.coerce.number().int().positive().optional()
|
|
52
|
+
});
|
|
53
|
+
var FtMintFilterSchema = z.object({
|
|
54
|
+
type: z.literal("ft_mint"),
|
|
55
|
+
recipient: stacksPrincipal.optional(),
|
|
56
|
+
assetIdentifier: z.string().optional(),
|
|
57
|
+
minAmount: z.coerce.number().int().positive().optional()
|
|
58
|
+
});
|
|
59
|
+
var FtBurnFilterSchema = z.object({
|
|
60
|
+
type: z.literal("ft_burn"),
|
|
61
|
+
sender: stacksPrincipal.optional(),
|
|
62
|
+
assetIdentifier: z.string().optional(),
|
|
63
|
+
minAmount: z.coerce.number().int().positive().optional()
|
|
64
|
+
});
|
|
65
|
+
var NftTransferFilterSchema = z.object({
|
|
66
|
+
type: z.literal("nft_transfer"),
|
|
67
|
+
...baseFilter,
|
|
68
|
+
assetIdentifier: z.string().optional(),
|
|
69
|
+
tokenId: z.string().optional()
|
|
70
|
+
});
|
|
71
|
+
var NftMintFilterSchema = z.object({
|
|
72
|
+
type: z.literal("nft_mint"),
|
|
73
|
+
recipient: stacksPrincipal.optional(),
|
|
74
|
+
assetIdentifier: z.string().optional(),
|
|
75
|
+
tokenId: z.string().optional()
|
|
76
|
+
});
|
|
77
|
+
var NftBurnFilterSchema = z.object({
|
|
78
|
+
type: z.literal("nft_burn"),
|
|
79
|
+
sender: stacksPrincipal.optional(),
|
|
80
|
+
assetIdentifier: z.string().optional(),
|
|
81
|
+
tokenId: z.string().optional()
|
|
82
|
+
});
|
|
83
|
+
var ContractCallFilterSchema = z.object({
|
|
84
|
+
type: z.literal("contract_call"),
|
|
85
|
+
contractId: stacksPrincipal.optional(),
|
|
86
|
+
functionName: z.string().optional(),
|
|
87
|
+
caller: stacksPrincipal.optional()
|
|
88
|
+
});
|
|
89
|
+
var ContractDeployFilterSchema = z.object({
|
|
90
|
+
type: z.literal("contract_deploy"),
|
|
91
|
+
deployer: stacksPrincipal.optional(),
|
|
92
|
+
contractName: z.string().optional()
|
|
93
|
+
});
|
|
94
|
+
var PrintEventFilterSchema = z.object({
|
|
95
|
+
type: z.literal("print_event"),
|
|
96
|
+
contractId: stacksPrincipal.optional(),
|
|
97
|
+
topic: z.string().optional(),
|
|
98
|
+
contains: z.string().optional()
|
|
99
|
+
});
|
|
100
|
+
var StreamFilterSchema = z.discriminatedUnion("type", [
|
|
101
|
+
StxTransferFilterSchema,
|
|
102
|
+
StxMintFilterSchema,
|
|
103
|
+
StxBurnFilterSchema,
|
|
104
|
+
StxLockFilterSchema,
|
|
105
|
+
FtTransferFilterSchema,
|
|
106
|
+
FtMintFilterSchema,
|
|
107
|
+
FtBurnFilterSchema,
|
|
108
|
+
NftTransferFilterSchema,
|
|
109
|
+
NftMintFilterSchema,
|
|
110
|
+
NftBurnFilterSchema,
|
|
111
|
+
ContractCallFilterSchema,
|
|
112
|
+
ContractDeployFilterSchema,
|
|
113
|
+
PrintEventFilterSchema
|
|
114
|
+
]);
|
|
115
|
+
export {
|
|
116
|
+
StxTransferFilterSchema,
|
|
117
|
+
StxMintFilterSchema,
|
|
118
|
+
StxLockFilterSchema,
|
|
119
|
+
StxBurnFilterSchema,
|
|
120
|
+
StreamFilterSchema,
|
|
121
|
+
PrintEventFilterSchema,
|
|
122
|
+
NftTransferFilterSchema,
|
|
123
|
+
NftMintFilterSchema,
|
|
124
|
+
NftBurnFilterSchema,
|
|
125
|
+
FtTransferFilterSchema,
|
|
126
|
+
FtMintFilterSchema,
|
|
127
|
+
FtBurnFilterSchema,
|
|
128
|
+
ContractDeployFilterSchema,
|
|
129
|
+
ContractCallFilterSchema
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
//# debugId=9E9E2A0DED55251864756E2164756E21
|
|
133
|
+
//# sourceMappingURL=filters.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/schemas/filters.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { z } from \"zod\";\nimport { validateStacksAddress } from \"@stacks/transactions\";\n\n/** Validate a Stacks principal (standard or contract, e.g. SP2J...ABC or SP2J...ABC.contract-name) */\nconst stacksPrincipal = z.string().refine((val) => {\n const parts = val.split(\".\");\n if (parts.length > 2) return false;\n return validateStacksAddress(parts[0]!);\n}, \"Invalid Stacks principal address\");\n\n// Base filter with common fields\nconst baseFilter = {\n // Optional: filter by sender\n sender: stacksPrincipal.optional(),\n // Optional: filter by recipient\n recipient: stacksPrincipal.optional(),\n};\n\n// STX Transfer Filter\nexport const StxTransferFilterSchema = z.object({\n type: z.literal(\"stx_transfer\"),\n ...baseFilter,\n // Optional: minimum amount in microSTX\n minAmount: z.coerce.number().int().positive().optional(),\n // Optional: maximum amount in microSTX\n maxAmount: z.coerce.number().int().positive().optional(),\n});\n\n// STX Mint Filter\nexport const StxMintFilterSchema = z.object({\n type: z.literal(\"stx_mint\"),\n recipient: stacksPrincipal.optional(),\n minAmount: z.coerce.number().int().positive().optional(),\n});\n\n// STX Burn Filter\nexport const StxBurnFilterSchema = z.object({\n type: z.literal(\"stx_burn\"),\n sender: stacksPrincipal.optional(),\n minAmount: z.coerce.number().int().positive().optional(),\n});\n\n// STX Lock Filter\nexport const StxLockFilterSchema = z.object({\n type: z.literal(\"stx_lock\"),\n lockedAddress: stacksPrincipal.optional(),\n minAmount: z.coerce.number().int().positive().optional(),\n});\n\n// FT Transfer Filter\nexport const FtTransferFilterSchema = z.object({\n type: z.literal(\"ft_transfer\"),\n ...baseFilter,\n // Contract that defines the token (e.g., SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-wstx)\n assetIdentifier: z.string().optional(),\n minAmount: z.coerce.number().int().positive().optional(),\n});\n\n// FT Mint Filter\nexport const FtMintFilterSchema = z.object({\n type: z.literal(\"ft_mint\"),\n recipient: stacksPrincipal.optional(),\n assetIdentifier: z.string().optional(),\n minAmount: z.coerce.number().int().positive().optional(),\n});\n\n// FT Burn Filter\nexport const FtBurnFilterSchema = z.object({\n type: z.literal(\"ft_burn\"),\n sender: stacksPrincipal.optional(),\n assetIdentifier: z.string().optional(),\n minAmount: z.coerce.number().int().positive().optional(),\n});\n\n// NFT Transfer Filter\nexport const NftTransferFilterSchema = z.object({\n type: z.literal(\"nft_transfer\"),\n ...baseFilter,\n assetIdentifier: z.string().optional(),\n // Optional: filter by specific token ID (Clarity value as hex)\n tokenId: z.string().optional(),\n});\n\n// NFT Mint Filter\nexport const NftMintFilterSchema = z.object({\n type: z.literal(\"nft_mint\"),\n recipient: stacksPrincipal.optional(),\n assetIdentifier: z.string().optional(),\n tokenId: z.string().optional(),\n});\n\n// NFT Burn Filter\nexport const NftBurnFilterSchema = z.object({\n type: z.literal(\"nft_burn\"),\n sender: stacksPrincipal.optional(),\n assetIdentifier: z.string().optional(),\n tokenId: z.string().optional(),\n});\n\n// Contract Call Filter\nexport const ContractCallFilterSchema = z.object({\n type: z.literal(\"contract_call\"),\n // Contract being called\n contractId: stacksPrincipal.optional(),\n // Function name (supports wildcards with *)\n functionName: z.string().optional(),\n // Caller address\n caller: stacksPrincipal.optional(),\n});\n\n// Contract Deploy Filter\nexport const ContractDeployFilterSchema = z.object({\n type: z.literal(\"contract_deploy\"),\n // Deployer address\n deployer: stacksPrincipal.optional(),\n // Contract name pattern (supports wildcards)\n contractName: z.string().optional(),\n});\n\n// Print Event Filter (smart contract events)\nexport const PrintEventFilterSchema = z.object({\n type: z.literal(\"print_event\"),\n // Contract emitting the event\n contractId: stacksPrincipal.optional(),\n // Topic/name of the event\n topic: z.string().optional(),\n // Search for substring in event data\n contains: z.string().optional(),\n});\n\n// Union of all filter types\nexport const StreamFilterSchema = z.discriminatedUnion(\"type\", [\n StxTransferFilterSchema,\n StxMintFilterSchema,\n StxBurnFilterSchema,\n StxLockFilterSchema,\n FtTransferFilterSchema,\n FtMintFilterSchema,\n FtBurnFilterSchema,\n NftTransferFilterSchema,\n NftMintFilterSchema,\n NftBurnFilterSchema,\n ContractCallFilterSchema,\n ContractDeployFilterSchema,\n PrintEventFilterSchema,\n]);\n\n// Type exports\nexport type StxTransferFilter = z.infer<typeof StxTransferFilterSchema>;\nexport type StxMintFilter = z.infer<typeof StxMintFilterSchema>;\nexport type StxBurnFilter = z.infer<typeof StxBurnFilterSchema>;\nexport type StxLockFilter = z.infer<typeof StxLockFilterSchema>;\nexport type FtTransferFilter = z.infer<typeof FtTransferFilterSchema>;\nexport type FtMintFilter = z.infer<typeof FtMintFilterSchema>;\nexport type FtBurnFilter = z.infer<typeof FtBurnFilterSchema>;\nexport type NftTransferFilter = z.infer<typeof NftTransferFilterSchema>;\nexport type NftMintFilter = z.infer<typeof NftMintFilterSchema>;\nexport type NftBurnFilter = z.infer<typeof NftBurnFilterSchema>;\nexport type ContractCallFilter = z.infer<typeof ContractCallFilterSchema>;\nexport type ContractDeployFilter = z.infer<typeof ContractDeployFilterSchema>;\nexport type PrintEventFilter = z.infer<typeof PrintEventFilterSchema>;\nexport type StreamFilter = z.infer<typeof StreamFilterSchema>;\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9E9E2A0DED55251864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|