@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,51 @@
|
|
|
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/queries/metrics.ts
|
|
14
|
+
import { sql } from "kysely";
|
|
15
|
+
async function getStreamMetrics(db, streamId) {
|
|
16
|
+
return await db.selectFrom("stream_metrics").selectAll().where("stream_id", "=", streamId).executeTakeFirst() ?? null;
|
|
17
|
+
}
|
|
18
|
+
async function updateStreamMetrics(db, streamId, updates) {
|
|
19
|
+
await db.insertInto("stream_metrics").values({
|
|
20
|
+
stream_id: streamId,
|
|
21
|
+
last_triggered_at: updates.lastTriggeredAt ?? null,
|
|
22
|
+
last_triggered_block: updates.lastTriggeredBlock ?? null,
|
|
23
|
+
total_deliveries: updates.totalDeliveries ?? 0,
|
|
24
|
+
failed_deliveries: updates.failedDeliveries ?? 0,
|
|
25
|
+
error_message: updates.errorMessage ?? null
|
|
26
|
+
}).onConflict((oc) => oc.column("stream_id").doUpdateSet({
|
|
27
|
+
...updates.lastTriggeredAt !== undefined ? { last_triggered_at: updates.lastTriggeredAt } : {},
|
|
28
|
+
...updates.lastTriggeredBlock !== undefined ? { last_triggered_block: updates.lastTriggeredBlock } : {},
|
|
29
|
+
...updates.totalDeliveries !== undefined ? { total_deliveries: updates.totalDeliveries } : {},
|
|
30
|
+
...updates.failedDeliveries !== undefined ? { failed_deliveries: updates.failedDeliveries } : {},
|
|
31
|
+
...updates.errorMessage !== undefined ? { error_message: updates.errorMessage } : {}
|
|
32
|
+
})).execute();
|
|
33
|
+
}
|
|
34
|
+
async function incrementDeliveryCount(db, streamId, failed) {
|
|
35
|
+
await db.insertInto("stream_metrics").values({
|
|
36
|
+
stream_id: streamId,
|
|
37
|
+
total_deliveries: 1,
|
|
38
|
+
failed_deliveries: failed ? 1 : 0
|
|
39
|
+
}).onConflict((oc) => oc.column("stream_id").doUpdateSet({
|
|
40
|
+
total_deliveries: sql`stream_metrics.total_deliveries + 1`,
|
|
41
|
+
...failed ? { failed_deliveries: sql`stream_metrics.failed_deliveries + 1` } : {}
|
|
42
|
+
})).execute();
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
updateStreamMetrics,
|
|
46
|
+
incrementDeliveryCount,
|
|
47
|
+
getStreamMetrics
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//# debugId=A4E370524682609864756E2164756E21
|
|
51
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/db/queries/metrics.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { sql, type Kysely } from \"kysely\";\nimport type { Database } from \"../types.ts\";\n\nexport async function getStreamMetrics(db: Kysely<Database>, streamId: string) {\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) {\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) {\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
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;AAAA;AAGA,eAAsB,gBAAgB,CAAC,IAAsB,UAAkB;AAAA,EAC7E,OACE,MAAM,GACH,WAAW,gBAAgB,EAC3B,UAAU,EACV,MAAM,aAAa,KAAK,QAAQ,EAChC,iBAAiB,KACjB;AAAA;AAGP,eAAsB,mBAAmB,CACvC,IACA,UACA,SAOA;AAAA,EACA,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,QACA;AAAA,EACA,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": "A4E370524682609864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
interface PlanLimits {
|
|
2
|
+
streams: number;
|
|
3
|
+
views: number;
|
|
4
|
+
apiRequestsPerDay: number;
|
|
5
|
+
deliveriesPerMonth: number;
|
|
6
|
+
storageBytes: number;
|
|
7
|
+
}
|
|
8
|
+
declare function getPlanLimits(plan: string): PlanLimits;
|
|
9
|
+
import { Kysely } from "kysely";
|
|
10
|
+
import { Generated } from "kysely";
|
|
11
|
+
interface BlocksTable {
|
|
12
|
+
height: number;
|
|
13
|
+
hash: string;
|
|
14
|
+
parent_hash: string;
|
|
15
|
+
burn_block_height: number;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
canonical: Generated<boolean>;
|
|
18
|
+
created_at: Generated<Date>;
|
|
19
|
+
}
|
|
20
|
+
interface TransactionsTable {
|
|
21
|
+
tx_id: string;
|
|
22
|
+
block_height: number;
|
|
23
|
+
type: string;
|
|
24
|
+
sender: string;
|
|
25
|
+
status: string;
|
|
26
|
+
contract_id: string | null;
|
|
27
|
+
function_name: string | null;
|
|
28
|
+
raw_tx: string;
|
|
29
|
+
created_at: Generated<Date>;
|
|
30
|
+
}
|
|
31
|
+
interface EventsTable {
|
|
32
|
+
id: Generated<string>;
|
|
33
|
+
tx_id: string;
|
|
34
|
+
block_height: number;
|
|
35
|
+
event_index: number;
|
|
36
|
+
type: string;
|
|
37
|
+
data: unknown;
|
|
38
|
+
created_at: Generated<Date>;
|
|
39
|
+
}
|
|
40
|
+
interface StreamsTable {
|
|
41
|
+
id: Generated<string>;
|
|
42
|
+
name: string;
|
|
43
|
+
status: Generated<string>;
|
|
44
|
+
filters: unknown;
|
|
45
|
+
options: Generated<unknown>;
|
|
46
|
+
webhook_url: string;
|
|
47
|
+
webhook_secret: string | null;
|
|
48
|
+
api_key_id: string | null;
|
|
49
|
+
created_at: Generated<Date>;
|
|
50
|
+
updated_at: Generated<Date>;
|
|
51
|
+
}
|
|
52
|
+
interface StreamMetricsTable {
|
|
53
|
+
stream_id: string;
|
|
54
|
+
last_triggered_at: Date | null;
|
|
55
|
+
last_triggered_block: number | null;
|
|
56
|
+
total_deliveries: Generated<number>;
|
|
57
|
+
failed_deliveries: Generated<number>;
|
|
58
|
+
error_message: string | null;
|
|
59
|
+
}
|
|
60
|
+
interface JobsTable {
|
|
61
|
+
id: Generated<string>;
|
|
62
|
+
stream_id: string;
|
|
63
|
+
block_height: number;
|
|
64
|
+
status: Generated<string>;
|
|
65
|
+
attempts: Generated<number>;
|
|
66
|
+
locked_at: Date | null;
|
|
67
|
+
locked_by: string | null;
|
|
68
|
+
error: string | null;
|
|
69
|
+
backfill: Generated<boolean>;
|
|
70
|
+
created_at: Generated<Date>;
|
|
71
|
+
completed_at: Date | null;
|
|
72
|
+
}
|
|
73
|
+
interface IndexProgressTable {
|
|
74
|
+
network: string;
|
|
75
|
+
last_indexed_block: Generated<number>;
|
|
76
|
+
last_contiguous_block: Generated<number>;
|
|
77
|
+
highest_seen_block: Generated<number>;
|
|
78
|
+
updated_at: Generated<Date>;
|
|
79
|
+
}
|
|
80
|
+
interface DeliveriesTable {
|
|
81
|
+
id: Generated<string>;
|
|
82
|
+
stream_id: string;
|
|
83
|
+
job_id: string | null;
|
|
84
|
+
block_height: number;
|
|
85
|
+
status: string;
|
|
86
|
+
status_code: number | null;
|
|
87
|
+
response_time_ms: number | null;
|
|
88
|
+
attempts: Generated<number>;
|
|
89
|
+
error: string | null;
|
|
90
|
+
payload: unknown;
|
|
91
|
+
created_at: Generated<Date>;
|
|
92
|
+
}
|
|
93
|
+
interface ViewsTable {
|
|
94
|
+
id: Generated<string>;
|
|
95
|
+
name: string;
|
|
96
|
+
version: Generated<string>;
|
|
97
|
+
status: Generated<string>;
|
|
98
|
+
definition: unknown;
|
|
99
|
+
schema_hash: string;
|
|
100
|
+
handler_path: string;
|
|
101
|
+
schema_name: string | null;
|
|
102
|
+
last_processed_block: Generated<number>;
|
|
103
|
+
last_error: string | null;
|
|
104
|
+
last_error_at: Date | null;
|
|
105
|
+
total_processed: Generated<number>;
|
|
106
|
+
total_errors: Generated<number>;
|
|
107
|
+
api_key_id: string | null;
|
|
108
|
+
created_at: Generated<Date>;
|
|
109
|
+
updated_at: Generated<Date>;
|
|
110
|
+
}
|
|
111
|
+
interface ApiKeysTable {
|
|
112
|
+
id: Generated<string>;
|
|
113
|
+
key_hash: string;
|
|
114
|
+
key_prefix: string;
|
|
115
|
+
name: string | null;
|
|
116
|
+
status: Generated<string>;
|
|
117
|
+
rate_limit: Generated<number>;
|
|
118
|
+
ip_address: string;
|
|
119
|
+
account_id: string;
|
|
120
|
+
last_used_at: Date | null;
|
|
121
|
+
revoked_at: Date | null;
|
|
122
|
+
created_at: Generated<Date>;
|
|
123
|
+
}
|
|
124
|
+
interface AccountsTable {
|
|
125
|
+
id: Generated<string>;
|
|
126
|
+
email: string;
|
|
127
|
+
plan: Generated<string>;
|
|
128
|
+
created_at: Generated<Date>;
|
|
129
|
+
}
|
|
130
|
+
interface SessionsTable {
|
|
131
|
+
id: Generated<string>;
|
|
132
|
+
token_hash: string;
|
|
133
|
+
token_prefix: string;
|
|
134
|
+
account_id: string;
|
|
135
|
+
ip_address: string;
|
|
136
|
+
expires_at: Generated<Date>;
|
|
137
|
+
revoked_at: Date | null;
|
|
138
|
+
last_used_at: Date | null;
|
|
139
|
+
created_at: Generated<Date>;
|
|
140
|
+
}
|
|
141
|
+
interface MagicLinksTable {
|
|
142
|
+
id: Generated<string>;
|
|
143
|
+
email: string;
|
|
144
|
+
token: string;
|
|
145
|
+
expires_at: Date;
|
|
146
|
+
used_at: Date | null;
|
|
147
|
+
created_at: Generated<Date>;
|
|
148
|
+
}
|
|
149
|
+
interface UsageDailyTable {
|
|
150
|
+
account_id: string;
|
|
151
|
+
date: string;
|
|
152
|
+
api_requests: Generated<number>;
|
|
153
|
+
deliveries: Generated<number>;
|
|
154
|
+
}
|
|
155
|
+
interface UsageSnapshotsTable {
|
|
156
|
+
id: Generated<string>;
|
|
157
|
+
account_id: string;
|
|
158
|
+
measured_at: Generated<Date>;
|
|
159
|
+
storage_bytes: Generated<number>;
|
|
160
|
+
}
|
|
161
|
+
interface Database {
|
|
162
|
+
blocks: BlocksTable;
|
|
163
|
+
transactions: TransactionsTable;
|
|
164
|
+
events: EventsTable;
|
|
165
|
+
streams: StreamsTable;
|
|
166
|
+
stream_metrics: StreamMetricsTable;
|
|
167
|
+
jobs: JobsTable;
|
|
168
|
+
index_progress: IndexProgressTable;
|
|
169
|
+
deliveries: DeliveriesTable;
|
|
170
|
+
views: ViewsTable;
|
|
171
|
+
api_keys: ApiKeysTable;
|
|
172
|
+
accounts: AccountsTable;
|
|
173
|
+
sessions: SessionsTable;
|
|
174
|
+
magic_links: MagicLinksTable;
|
|
175
|
+
usage_daily: UsageDailyTable;
|
|
176
|
+
usage_snapshots: UsageSnapshotsTable;
|
|
177
|
+
}
|
|
178
|
+
/** Increment API request counter for today. Fire-and-forget safe. */
|
|
179
|
+
declare function incrementApiRequests(db: Kysely<Database>, accountId: string): Promise<void>;
|
|
180
|
+
/** Increment delivery counter for today. */
|
|
181
|
+
declare function incrementDeliveries(db: Kysely<Database>, accountId: string, count?: number): Promise<void>;
|
|
182
|
+
interface UsageSummary {
|
|
183
|
+
apiRequestsToday: number;
|
|
184
|
+
deliveriesThisMonth: number;
|
|
185
|
+
storageBytes: number;
|
|
186
|
+
}
|
|
187
|
+
/** Get current usage for an account. */
|
|
188
|
+
declare function getUsage(db: Kysely<Database>, accountId: string): Promise<UsageSummary>;
|
|
189
|
+
interface LimitCheck {
|
|
190
|
+
allowed: boolean;
|
|
191
|
+
limits: ReturnType<typeof getPlanLimits>;
|
|
192
|
+
current: UsageSummary & {
|
|
193
|
+
streams: number
|
|
194
|
+
views: number
|
|
195
|
+
};
|
|
196
|
+
exceeded?: string;
|
|
197
|
+
}
|
|
198
|
+
/** Check if an account is within plan limits. */
|
|
199
|
+
declare function checkLimits(db: Kysely<Database>, accountId: string, plan: string): Promise<LimitCheck>;
|
|
200
|
+
/**
|
|
201
|
+
* Measure storage for all accounts by querying pg_total_relation_size
|
|
202
|
+
* for each tenant's view schemas.
|
|
203
|
+
*/
|
|
204
|
+
declare function measureStorage(db: Kysely<Database>): Promise<void>;
|
|
205
|
+
export { measureStorage, incrementDeliveries, incrementApiRequests, getUsage, checkLimits, UsageSummary, LimitCheck };
|
|
@@ -0,0 +1,117 @@
|
|
|
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/lib/plans.ts
|
|
14
|
+
var FREE_PLAN = {
|
|
15
|
+
streams: 3,
|
|
16
|
+
views: 2,
|
|
17
|
+
apiRequestsPerDay: 1000,
|
|
18
|
+
deliveriesPerMonth: 5000,
|
|
19
|
+
storageBytes: 100 * 1024 * 1024
|
|
20
|
+
};
|
|
21
|
+
function getPlanLimits(plan) {
|
|
22
|
+
switch (plan) {
|
|
23
|
+
case "free":
|
|
24
|
+
default:
|
|
25
|
+
return FREE_PLAN;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/db/queries/usage.ts
|
|
30
|
+
import { sql } from "kysely";
|
|
31
|
+
async function incrementApiRequests(db, accountId) {
|
|
32
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
33
|
+
await db.insertInto("usage_daily").values({ account_id: accountId, date: today, api_requests: 1, deliveries: 0 }).onConflict((oc) => oc.columns(["account_id", "date"]).doUpdateSet({
|
|
34
|
+
api_requests: sql`usage_daily.api_requests + 1`
|
|
35
|
+
})).execute();
|
|
36
|
+
}
|
|
37
|
+
async function incrementDeliveries(db, accountId, count = 1) {
|
|
38
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
39
|
+
await db.insertInto("usage_daily").values({ account_id: accountId, date: today, api_requests: 0, deliveries: count }).onConflict((oc) => oc.columns(["account_id", "date"]).doUpdateSet({
|
|
40
|
+
deliveries: sql`usage_daily.deliveries + ${count}`
|
|
41
|
+
})).execute();
|
|
42
|
+
}
|
|
43
|
+
async function getUsage(db, accountId) {
|
|
44
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
45
|
+
const monthStart = today.slice(0, 7) + "-01";
|
|
46
|
+
const dailyRow = await db.selectFrom("usage_daily").select("api_requests").where("account_id", "=", accountId).where("date", "=", today).executeTakeFirst();
|
|
47
|
+
const monthlyRow = await db.selectFrom("usage_daily").select(sql`COALESCE(SUM(deliveries), 0)`.as("total")).where("account_id", "=", accountId).where("date", ">=", monthStart).executeTakeFirst();
|
|
48
|
+
const storageRow = await db.selectFrom("usage_snapshots").select("storage_bytes").where("account_id", "=", accountId).orderBy("measured_at", "desc").limit(1).executeTakeFirst();
|
|
49
|
+
return {
|
|
50
|
+
apiRequestsToday: dailyRow?.api_requests ?? 0,
|
|
51
|
+
deliveriesThisMonth: Number(monthlyRow?.total ?? 0),
|
|
52
|
+
storageBytes: Number(storageRow?.storage_bytes ?? 0)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function checkLimits(db, accountId, plan) {
|
|
56
|
+
const limits = getPlanLimits(plan);
|
|
57
|
+
const usage = await getUsage(db, accountId);
|
|
58
|
+
const streamCount = await db.selectFrom("streams").innerJoin("api_keys", "streams.api_key_id", "api_keys.id").select(sql`count(*)`.as("count")).where("api_keys.account_id", "=", accountId).executeTakeFirst();
|
|
59
|
+
const viewCount = await db.selectFrom("views").innerJoin("api_keys", "views.api_key_id", "api_keys.id").select(sql`count(*)`.as("count")).where("api_keys.account_id", "=", accountId).executeTakeFirst();
|
|
60
|
+
const current = {
|
|
61
|
+
...usage,
|
|
62
|
+
streams: Number(streamCount?.count ?? 0),
|
|
63
|
+
views: Number(viewCount?.count ?? 0)
|
|
64
|
+
};
|
|
65
|
+
if (current.streams >= limits.streams) {
|
|
66
|
+
return { allowed: false, limits, current, exceeded: "streams" };
|
|
67
|
+
}
|
|
68
|
+
if (current.views >= limits.views) {
|
|
69
|
+
return { allowed: false, limits, current, exceeded: "views" };
|
|
70
|
+
}
|
|
71
|
+
if (current.apiRequestsToday >= limits.apiRequestsPerDay) {
|
|
72
|
+
return { allowed: false, limits, current, exceeded: "api_requests" };
|
|
73
|
+
}
|
|
74
|
+
if (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {
|
|
75
|
+
return { allowed: false, limits, current, exceeded: "deliveries" };
|
|
76
|
+
}
|
|
77
|
+
if (current.storageBytes >= limits.storageBytes) {
|
|
78
|
+
return { allowed: false, limits, current, exceeded: "storage" };
|
|
79
|
+
}
|
|
80
|
+
return { allowed: true, limits, current };
|
|
81
|
+
}
|
|
82
|
+
async function measureStorage(db) {
|
|
83
|
+
const accountViews = await db.selectFrom("views").innerJoin("api_keys", "views.api_key_id", "api_keys.id").select(["api_keys.account_id", "views.schema_name"]).where("views.schema_name", "is not", null).execute();
|
|
84
|
+
const byAccount = new Map;
|
|
85
|
+
for (const row of accountViews) {
|
|
86
|
+
const schemas = byAccount.get(row.account_id) ?? [];
|
|
87
|
+
if (row.schema_name)
|
|
88
|
+
schemas.push(row.schema_name);
|
|
89
|
+
byAccount.set(row.account_id, schemas);
|
|
90
|
+
}
|
|
91
|
+
for (const [accountId, schemas] of byAccount) {
|
|
92
|
+
let totalBytes = 0;
|
|
93
|
+
for (const schema of schemas) {
|
|
94
|
+
try {
|
|
95
|
+
const result = await sql`
|
|
96
|
+
SELECT COALESCE(SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))), 0)::text as size
|
|
97
|
+
FROM pg_tables WHERE schemaname = ${schema}
|
|
98
|
+
`.execute(db);
|
|
99
|
+
totalBytes += Number(result.rows[0]?.size ?? 0);
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
await db.insertInto("usage_snapshots").values({
|
|
103
|
+
account_id: accountId,
|
|
104
|
+
storage_bytes: totalBytes
|
|
105
|
+
}).execute();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
measureStorage,
|
|
110
|
+
incrementDeliveries,
|
|
111
|
+
incrementApiRequests,
|
|
112
|
+
getUsage,
|
|
113
|
+
checkLimits
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//# debugId=51CB827BBF658C6C64756E2164756E21
|
|
117
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/plans.ts", "../src/db/queries/usage.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export interface PlanLimits {\n streams: number;\n views: number;\n apiRequestsPerDay: number;\n deliveriesPerMonth: number;\n storageBytes: number;\n}\n\nexport const FREE_PLAN: PlanLimits = {\n streams: 3,\n views: 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; views: 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 viewCount = await db\n .selectFrom(\"views\")\n .innerJoin(\"api_keys\", \"views.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 views: Number(viewCount?.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.views >= limits.views) {\n return { allowed: false, limits, current, exceeded: \"views\" };\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 view schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n // Get all accounts with views\n const accountViews = await db\n .selectFrom(\"views\")\n .innerJoin(\"api_keys\", \"views.api_key_id\", \"api_keys.id\")\n .select([\"api_keys.account_id\", \"views.schema_name\"])\n .where(\"views.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 accountViews) {\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
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "51CB827BBF658C6C64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import { Generated } from "kysely";
|
|
3
|
+
interface BlocksTable {
|
|
4
|
+
height: number;
|
|
5
|
+
hash: string;
|
|
6
|
+
parent_hash: string;
|
|
7
|
+
burn_block_height: number;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
canonical: Generated<boolean>;
|
|
10
|
+
created_at: Generated<Date>;
|
|
11
|
+
}
|
|
12
|
+
interface TransactionsTable {
|
|
13
|
+
tx_id: string;
|
|
14
|
+
block_height: number;
|
|
15
|
+
type: string;
|
|
16
|
+
sender: string;
|
|
17
|
+
status: string;
|
|
18
|
+
contract_id: string | null;
|
|
19
|
+
function_name: string | null;
|
|
20
|
+
raw_tx: string;
|
|
21
|
+
created_at: Generated<Date>;
|
|
22
|
+
}
|
|
23
|
+
interface EventsTable {
|
|
24
|
+
id: Generated<string>;
|
|
25
|
+
tx_id: string;
|
|
26
|
+
block_height: number;
|
|
27
|
+
event_index: number;
|
|
28
|
+
type: string;
|
|
29
|
+
data: unknown;
|
|
30
|
+
created_at: Generated<Date>;
|
|
31
|
+
}
|
|
32
|
+
interface StreamsTable {
|
|
33
|
+
id: Generated<string>;
|
|
34
|
+
name: string;
|
|
35
|
+
status: Generated<string>;
|
|
36
|
+
filters: unknown;
|
|
37
|
+
options: Generated<unknown>;
|
|
38
|
+
webhook_url: string;
|
|
39
|
+
webhook_secret: string | null;
|
|
40
|
+
api_key_id: string | null;
|
|
41
|
+
created_at: Generated<Date>;
|
|
42
|
+
updated_at: Generated<Date>;
|
|
43
|
+
}
|
|
44
|
+
interface StreamMetricsTable {
|
|
45
|
+
stream_id: string;
|
|
46
|
+
last_triggered_at: Date | null;
|
|
47
|
+
last_triggered_block: number | null;
|
|
48
|
+
total_deliveries: Generated<number>;
|
|
49
|
+
failed_deliveries: Generated<number>;
|
|
50
|
+
error_message: string | null;
|
|
51
|
+
}
|
|
52
|
+
interface JobsTable {
|
|
53
|
+
id: Generated<string>;
|
|
54
|
+
stream_id: string;
|
|
55
|
+
block_height: number;
|
|
56
|
+
status: Generated<string>;
|
|
57
|
+
attempts: Generated<number>;
|
|
58
|
+
locked_at: Date | null;
|
|
59
|
+
locked_by: string | null;
|
|
60
|
+
error: string | null;
|
|
61
|
+
backfill: Generated<boolean>;
|
|
62
|
+
created_at: Generated<Date>;
|
|
63
|
+
completed_at: Date | null;
|
|
64
|
+
}
|
|
65
|
+
interface IndexProgressTable {
|
|
66
|
+
network: string;
|
|
67
|
+
last_indexed_block: Generated<number>;
|
|
68
|
+
last_contiguous_block: Generated<number>;
|
|
69
|
+
highest_seen_block: Generated<number>;
|
|
70
|
+
updated_at: Generated<Date>;
|
|
71
|
+
}
|
|
72
|
+
interface DeliveriesTable {
|
|
73
|
+
id: Generated<string>;
|
|
74
|
+
stream_id: string;
|
|
75
|
+
job_id: string | null;
|
|
76
|
+
block_height: number;
|
|
77
|
+
status: string;
|
|
78
|
+
status_code: number | null;
|
|
79
|
+
response_time_ms: number | null;
|
|
80
|
+
attempts: Generated<number>;
|
|
81
|
+
error: string | null;
|
|
82
|
+
payload: unknown;
|
|
83
|
+
created_at: Generated<Date>;
|
|
84
|
+
}
|
|
85
|
+
interface ViewsTable {
|
|
86
|
+
id: Generated<string>;
|
|
87
|
+
name: string;
|
|
88
|
+
version: Generated<string>;
|
|
89
|
+
status: Generated<string>;
|
|
90
|
+
definition: unknown;
|
|
91
|
+
schema_hash: string;
|
|
92
|
+
handler_path: string;
|
|
93
|
+
schema_name: string | null;
|
|
94
|
+
last_processed_block: Generated<number>;
|
|
95
|
+
last_error: string | null;
|
|
96
|
+
last_error_at: Date | null;
|
|
97
|
+
total_processed: Generated<number>;
|
|
98
|
+
total_errors: Generated<number>;
|
|
99
|
+
api_key_id: string | null;
|
|
100
|
+
created_at: Generated<Date>;
|
|
101
|
+
updated_at: Generated<Date>;
|
|
102
|
+
}
|
|
103
|
+
interface ApiKeysTable {
|
|
104
|
+
id: Generated<string>;
|
|
105
|
+
key_hash: string;
|
|
106
|
+
key_prefix: string;
|
|
107
|
+
name: string | null;
|
|
108
|
+
status: Generated<string>;
|
|
109
|
+
rate_limit: Generated<number>;
|
|
110
|
+
ip_address: string;
|
|
111
|
+
account_id: string;
|
|
112
|
+
last_used_at: Date | null;
|
|
113
|
+
revoked_at: Date | null;
|
|
114
|
+
created_at: Generated<Date>;
|
|
115
|
+
}
|
|
116
|
+
interface AccountsTable {
|
|
117
|
+
id: Generated<string>;
|
|
118
|
+
email: string;
|
|
119
|
+
plan: Generated<string>;
|
|
120
|
+
created_at: Generated<Date>;
|
|
121
|
+
}
|
|
122
|
+
interface SessionsTable {
|
|
123
|
+
id: Generated<string>;
|
|
124
|
+
token_hash: string;
|
|
125
|
+
token_prefix: string;
|
|
126
|
+
account_id: string;
|
|
127
|
+
ip_address: string;
|
|
128
|
+
expires_at: Generated<Date>;
|
|
129
|
+
revoked_at: Date | null;
|
|
130
|
+
last_used_at: Date | null;
|
|
131
|
+
created_at: Generated<Date>;
|
|
132
|
+
}
|
|
133
|
+
interface MagicLinksTable {
|
|
134
|
+
id: Generated<string>;
|
|
135
|
+
email: string;
|
|
136
|
+
token: string;
|
|
137
|
+
expires_at: Date;
|
|
138
|
+
used_at: Date | null;
|
|
139
|
+
created_at: Generated<Date>;
|
|
140
|
+
}
|
|
141
|
+
interface UsageDailyTable {
|
|
142
|
+
account_id: string;
|
|
143
|
+
date: string;
|
|
144
|
+
api_requests: Generated<number>;
|
|
145
|
+
deliveries: Generated<number>;
|
|
146
|
+
}
|
|
147
|
+
interface UsageSnapshotsTable {
|
|
148
|
+
id: Generated<string>;
|
|
149
|
+
account_id: string;
|
|
150
|
+
measured_at: Generated<Date>;
|
|
151
|
+
storage_bytes: Generated<number>;
|
|
152
|
+
}
|
|
153
|
+
interface Database {
|
|
154
|
+
blocks: BlocksTable;
|
|
155
|
+
transactions: TransactionsTable;
|
|
156
|
+
events: EventsTable;
|
|
157
|
+
streams: StreamsTable;
|
|
158
|
+
stream_metrics: StreamMetricsTable;
|
|
159
|
+
jobs: JobsTable;
|
|
160
|
+
index_progress: IndexProgressTable;
|
|
161
|
+
deliveries: DeliveriesTable;
|
|
162
|
+
views: ViewsTable;
|
|
163
|
+
api_keys: ApiKeysTable;
|
|
164
|
+
accounts: AccountsTable;
|
|
165
|
+
sessions: SessionsTable;
|
|
166
|
+
magic_links: MagicLinksTable;
|
|
167
|
+
usage_daily: UsageDailyTable;
|
|
168
|
+
usage_snapshots: UsageSnapshotsTable;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Convert a view name to its PostgreSQL schema name.
|
|
172
|
+
* With keyPrefix: "view_{prefix}_{name}" (tenant-isolated)
|
|
173
|
+
* Without keyPrefix: "view_{name}" (backward compat)
|
|
174
|
+
*/
|
|
175
|
+
declare function pgSchemaName(viewName: string, keyPrefix?: string): string;
|
|
176
|
+
declare function registerView(db: Kysely<Database>, data: {
|
|
177
|
+
name: string
|
|
178
|
+
version: string
|
|
179
|
+
definition: Record<string, unknown>
|
|
180
|
+
schemaHash: string
|
|
181
|
+
handlerPath: string
|
|
182
|
+
apiKeyId?: string
|
|
183
|
+
schemaName?: string
|
|
184
|
+
});
|
|
185
|
+
declare function getView(db: Kysely<Database>, name: string, apiKeyId?: string);
|
|
186
|
+
declare function listViews(db: Kysely<Database>, apiKeyId?: string);
|
|
187
|
+
declare function updateViewStatus(db: Kysely<Database>, name: string, status: string, lastProcessedBlock?: number);
|
|
188
|
+
declare function recordViewProcessed(db: Kysely<Database>, name: string, processed: number, errors: number, lastError?: string);
|
|
189
|
+
declare function updateViewHandlerPath(db: Kysely<Database>, name: string, handlerPath: string);
|
|
190
|
+
declare function deleteView(db: Kysely<Database>, name: string, apiKeyId?: string);
|
|
191
|
+
export { updateViewStatus, updateViewHandlerPath, registerView, recordViewProcessed, pgSchemaName, listViews, getView, deleteView };
|