@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
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @secondlayer/shared
|
|
2
|
+
|
|
3
|
+
Foundational utilities for Second Layer services: DB layer (Kysely+Postgres), job queue, Zod schemas, HMAC signing, Stacks node clients.
|
|
4
|
+
|
|
5
|
+
## Testing
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Run tests (DB tests skip without DATABASE_URL)
|
|
9
|
+
bun test
|
|
10
|
+
|
|
11
|
+
# Run with database
|
|
12
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/streams_test bun test
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Migrations
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
DATABASE_URL=... bun run migrate
|
|
19
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a random secret for webhook signing
|
|
3
|
+
* Returns 32 bytes as a 64-character hex string
|
|
4
|
+
*/
|
|
5
|
+
declare function generateSecret(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Sign a payload with HMAC-SHA256
|
|
8
|
+
* Returns the signature as a hex string
|
|
9
|
+
*/
|
|
10
|
+
declare function signPayload(payload: string, secret: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Verify an HMAC signature
|
|
13
|
+
* Uses constant-time comparison to prevent timing attacks
|
|
14
|
+
*/
|
|
15
|
+
declare function verifySignature(payload: string, signature: string, secret: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Create a Stripe-style signature header
|
|
18
|
+
* Format: t=timestamp,v1=signature
|
|
19
|
+
*/
|
|
20
|
+
declare function createSignatureHeader(payload: string, secret: string, timestamp?: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Parse and verify a Stripe-style signature header
|
|
23
|
+
* Returns true if valid, false otherwise
|
|
24
|
+
*/
|
|
25
|
+
declare function verifySignatureHeader(payload: string, header: string, secret: string, toleranceSeconds?: number): boolean;
|
|
26
|
+
export { verifySignatureHeader, verifySignature, signPayload, generateSecret, createSignatureHeader };
|
|
@@ -0,0 +1,75 @@
|
|
|
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/crypto/hmac.ts
|
|
14
|
+
var exports_hmac = {};
|
|
15
|
+
__export(exports_hmac, {
|
|
16
|
+
verifySignatureHeader: () => verifySignatureHeader,
|
|
17
|
+
verifySignature: () => verifySignature,
|
|
18
|
+
signPayload: () => signPayload,
|
|
19
|
+
generateSecret: () => generateSecret,
|
|
20
|
+
createSignatureHeader: () => createSignatureHeader
|
|
21
|
+
});
|
|
22
|
+
import { createHmac, randomBytes } from "crypto";
|
|
23
|
+
function generateSecret() {
|
|
24
|
+
return randomBytes(32).toString("hex");
|
|
25
|
+
}
|
|
26
|
+
function signPayload(payload, secret) {
|
|
27
|
+
const hmac = createHmac("sha256", secret);
|
|
28
|
+
hmac.update(payload);
|
|
29
|
+
return hmac.digest("hex");
|
|
30
|
+
}
|
|
31
|
+
function verifySignature(payload, signature, secret) {
|
|
32
|
+
const expectedSignature = signPayload(payload, secret);
|
|
33
|
+
if (signature.length !== expectedSignature.length) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
let result = 0;
|
|
37
|
+
for (let i = 0;i < signature.length; i++) {
|
|
38
|
+
result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);
|
|
39
|
+
}
|
|
40
|
+
return result === 0;
|
|
41
|
+
}
|
|
42
|
+
function createSignatureHeader(payload, secret, timestamp) {
|
|
43
|
+
const ts = timestamp ?? Math.floor(Date.now() / 1000);
|
|
44
|
+
const signedPayload = `${ts}.${payload}`;
|
|
45
|
+
const signature = signPayload(signedPayload, secret);
|
|
46
|
+
return `t=${ts},v1=${signature}`;
|
|
47
|
+
}
|
|
48
|
+
function verifySignatureHeader(payload, header, secret, toleranceSeconds = 300) {
|
|
49
|
+
const parts = header.split(",");
|
|
50
|
+
const timestamp = parts.find((p) => p.startsWith("t="))?.slice(2);
|
|
51
|
+
const signature = parts.find((p) => p.startsWith("v1="))?.slice(3);
|
|
52
|
+
if (!timestamp || !signature) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const ts = parseInt(timestamp, 10);
|
|
56
|
+
if (isNaN(ts)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const now = Math.floor(Date.now() / 1000);
|
|
60
|
+
if (Math.abs(now - ts) > toleranceSeconds) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const signedPayload = `${ts}.${payload}`;
|
|
64
|
+
return verifySignature(signedPayload, signature, secret);
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
verifySignatureHeader,
|
|
68
|
+
verifySignature,
|
|
69
|
+
signPayload,
|
|
70
|
+
generateSecret,
|
|
71
|
+
createSignatureHeader
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//# debugId=C5FB6F078225904664756E2164756E21
|
|
75
|
+
//# sourceMappingURL=hmac.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/crypto/hmac.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { createHmac, randomBytes } from \"crypto\";\n\n/**\n * Generate a random secret for webhook signing\n * Returns 32 bytes as a 64-character hex string\n */\nexport function generateSecret(): string {\n return randomBytes(32).toString(\"hex\");\n}\n\n/**\n * Sign a payload with HMAC-SHA256\n * Returns the signature as a hex string\n */\nexport function signPayload(payload: string, secret: string): string {\n const hmac = createHmac(\"sha256\", secret);\n hmac.update(payload);\n return hmac.digest(\"hex\");\n}\n\n/**\n * Verify an HMAC signature\n * Uses constant-time comparison to prevent timing attacks\n */\nexport function verifySignature(\n payload: string,\n signature: string,\n secret: string\n): boolean {\n const expectedSignature = signPayload(payload, secret);\n\n // Constant-time comparison\n if (signature.length !== expectedSignature.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < signature.length; i++) {\n result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n }\n\n return result === 0;\n}\n\n/**\n * Create a Stripe-style signature header\n * Format: t=timestamp,v1=signature\n */\nexport function createSignatureHeader(\n payload: string,\n secret: string,\n timestamp?: number\n): string {\n const ts = timestamp ?? Math.floor(Date.now() / 1000);\n const signedPayload = `${ts}.${payload}`;\n const signature = signPayload(signedPayload, secret);\n\n return `t=${ts},v1=${signature}`;\n}\n\n/**\n * Parse and verify a Stripe-style signature header\n * Returns true if valid, false otherwise\n */\nexport function verifySignatureHeader(\n payload: string,\n header: string,\n secret: string,\n toleranceSeconds = 300 // 5 minutes\n): boolean {\n // Parse header\n const parts = header.split(\",\");\n const timestamp = parts\n .find((p) => p.startsWith(\"t=\"))\n ?.slice(2);\n const signature = parts\n .find((p) => p.startsWith(\"v1=\"))\n ?.slice(3);\n\n if (!timestamp || !signature) {\n return false;\n }\n\n const ts = parseInt(timestamp, 10);\n if (isNaN(ts)) {\n return false;\n }\n\n // Check timestamp is within tolerance\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - ts) > toleranceSeconds) {\n return false;\n }\n\n // Verify signature\n const signedPayload = `${ts}.${payload}`;\n return verifySignature(signedPayload, signature, secret);\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAMO,SAAS,cAAc,GAAW;AAAA,EACvC,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA;AAOhC,SAAS,WAAW,CAAC,SAAiB,QAAwB;AAAA,EACnE,MAAM,OAAO,WAAW,UAAU,MAAM;AAAA,EACxC,KAAK,OAAO,OAAO;AAAA,EACnB,OAAO,KAAK,OAAO,KAAK;AAAA;AAOnB,SAAS,eAAe,CAC7B,SACA,WACA,QACS;AAAA,EACT,MAAM,oBAAoB,YAAY,SAAS,MAAM;AAAA,EAGrD,IAAI,UAAU,WAAW,kBAAkB,QAAQ;AAAA,IACjD,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAAS;AAAA,EACb,SAAS,IAAI,EAAG,IAAI,UAAU,QAAQ,KAAK;AAAA,IACzC,UAAU,UAAU,WAAW,CAAC,IAAI,kBAAkB,WAAW,CAAC;AAAA,EACpE;AAAA,EAEA,OAAO,WAAW;AAAA;AAOb,SAAS,qBAAqB,CACnC,SACA,QACA,WACQ;AAAA,EACR,MAAM,KAAK,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACpD,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,MAAM,YAAY,YAAY,eAAe,MAAM;AAAA,EAEnD,OAAO,KAAK,SAAS;AAAA;AAOhB,SAAS,qBAAqB,CACnC,SACA,QACA,QACA,mBAAmB,KACV;AAAA,EAET,MAAM,QAAQ,OAAO,MAAM,GAAG;AAAA,EAC9B,MAAM,YAAY,MACf,KAAK,CAAC,MAAM,EAAE,WAAW,IAAI,CAAC,GAC7B,MAAM,CAAC;AAAA,EACX,MAAM,YAAY,MACf,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAC9B,MAAM,CAAC;AAAA,EAEX,IAAI,CAAC,aAAa,CAAC,WAAW;AAAA,IAC5B,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAK,SAAS,WAAW,EAAE;AAAA,EACjC,IAAI,MAAM,EAAE,GAAG;AAAA,IACb,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,IAAI;AAAA,EACxC,IAAI,KAAK,IAAI,MAAM,EAAE,IAAI,kBAAkB;AAAA,IACzC,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,gBAAgB,GAAG,MAAM;AAAA,EAC/B,OAAO,gBAAgB,eAAe,WAAW,MAAM;AAAA;",
|
|
8
|
+
"debugId": "C5FB6F078225904664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely encode a JS value as a JSONB literal for Kysely inserts/updates.
|
|
3
|
+
* Kysely + postgres.js double-encodes JSON when using parameterized queries
|
|
4
|
+
* with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.
|
|
5
|
+
*/
|
|
6
|
+
declare function jsonb(value: unknown);
|
|
7
|
+
/**
|
|
8
|
+
* Safely parse a JSONB value from the database.
|
|
9
|
+
* Handles double-encoded strings where postgres.js returns a JSON string
|
|
10
|
+
* instead of a parsed object.
|
|
11
|
+
*/
|
|
12
|
+
declare function parseJsonb<T = unknown>(value: unknown): T;
|
|
13
|
+
import { Kysely } from "kysely";
|
|
14
|
+
import postgres from "postgres";
|
|
15
|
+
import { Generated, Insertable, Selectable, Updateable } from "kysely";
|
|
16
|
+
interface BlocksTable {
|
|
17
|
+
height: number;
|
|
18
|
+
hash: string;
|
|
19
|
+
parent_hash: string;
|
|
20
|
+
burn_block_height: number;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
canonical: Generated<boolean>;
|
|
23
|
+
created_at: Generated<Date>;
|
|
24
|
+
}
|
|
25
|
+
interface TransactionsTable {
|
|
26
|
+
tx_id: string;
|
|
27
|
+
block_height: number;
|
|
28
|
+
type: string;
|
|
29
|
+
sender: string;
|
|
30
|
+
status: string;
|
|
31
|
+
contract_id: string | null;
|
|
32
|
+
function_name: string | null;
|
|
33
|
+
raw_tx: string;
|
|
34
|
+
created_at: Generated<Date>;
|
|
35
|
+
}
|
|
36
|
+
interface EventsTable {
|
|
37
|
+
id: Generated<string>;
|
|
38
|
+
tx_id: string;
|
|
39
|
+
block_height: number;
|
|
40
|
+
event_index: number;
|
|
41
|
+
type: string;
|
|
42
|
+
data: unknown;
|
|
43
|
+
created_at: Generated<Date>;
|
|
44
|
+
}
|
|
45
|
+
interface StreamsTable {
|
|
46
|
+
id: Generated<string>;
|
|
47
|
+
name: string;
|
|
48
|
+
status: Generated<string>;
|
|
49
|
+
filters: unknown;
|
|
50
|
+
options: Generated<unknown>;
|
|
51
|
+
webhook_url: string;
|
|
52
|
+
webhook_secret: string | null;
|
|
53
|
+
api_key_id: string | null;
|
|
54
|
+
created_at: Generated<Date>;
|
|
55
|
+
updated_at: Generated<Date>;
|
|
56
|
+
}
|
|
57
|
+
interface StreamMetricsTable {
|
|
58
|
+
stream_id: string;
|
|
59
|
+
last_triggered_at: Date | null;
|
|
60
|
+
last_triggered_block: number | null;
|
|
61
|
+
total_deliveries: Generated<number>;
|
|
62
|
+
failed_deliveries: Generated<number>;
|
|
63
|
+
error_message: string | null;
|
|
64
|
+
}
|
|
65
|
+
interface JobsTable {
|
|
66
|
+
id: Generated<string>;
|
|
67
|
+
stream_id: string;
|
|
68
|
+
block_height: number;
|
|
69
|
+
status: Generated<string>;
|
|
70
|
+
attempts: Generated<number>;
|
|
71
|
+
locked_at: Date | null;
|
|
72
|
+
locked_by: string | null;
|
|
73
|
+
error: string | null;
|
|
74
|
+
backfill: Generated<boolean>;
|
|
75
|
+
created_at: Generated<Date>;
|
|
76
|
+
completed_at: Date | null;
|
|
77
|
+
}
|
|
78
|
+
interface IndexProgressTable {
|
|
79
|
+
network: string;
|
|
80
|
+
last_indexed_block: Generated<number>;
|
|
81
|
+
last_contiguous_block: Generated<number>;
|
|
82
|
+
highest_seen_block: Generated<number>;
|
|
83
|
+
updated_at: Generated<Date>;
|
|
84
|
+
}
|
|
85
|
+
interface DeliveriesTable {
|
|
86
|
+
id: Generated<string>;
|
|
87
|
+
stream_id: string;
|
|
88
|
+
job_id: string | null;
|
|
89
|
+
block_height: number;
|
|
90
|
+
status: string;
|
|
91
|
+
status_code: number | null;
|
|
92
|
+
response_time_ms: number | null;
|
|
93
|
+
attempts: Generated<number>;
|
|
94
|
+
error: string | null;
|
|
95
|
+
payload: unknown;
|
|
96
|
+
created_at: Generated<Date>;
|
|
97
|
+
}
|
|
98
|
+
interface ViewsTable {
|
|
99
|
+
id: Generated<string>;
|
|
100
|
+
name: string;
|
|
101
|
+
version: Generated<string>;
|
|
102
|
+
status: Generated<string>;
|
|
103
|
+
definition: unknown;
|
|
104
|
+
schema_hash: string;
|
|
105
|
+
handler_path: string;
|
|
106
|
+
schema_name: string | null;
|
|
107
|
+
last_processed_block: Generated<number>;
|
|
108
|
+
last_error: string | null;
|
|
109
|
+
last_error_at: Date | null;
|
|
110
|
+
total_processed: Generated<number>;
|
|
111
|
+
total_errors: Generated<number>;
|
|
112
|
+
api_key_id: string | null;
|
|
113
|
+
created_at: Generated<Date>;
|
|
114
|
+
updated_at: Generated<Date>;
|
|
115
|
+
}
|
|
116
|
+
interface ApiKeysTable {
|
|
117
|
+
id: Generated<string>;
|
|
118
|
+
key_hash: string;
|
|
119
|
+
key_prefix: string;
|
|
120
|
+
name: string | null;
|
|
121
|
+
status: Generated<string>;
|
|
122
|
+
rate_limit: Generated<number>;
|
|
123
|
+
ip_address: string;
|
|
124
|
+
account_id: string;
|
|
125
|
+
last_used_at: Date | null;
|
|
126
|
+
revoked_at: Date | null;
|
|
127
|
+
created_at: Generated<Date>;
|
|
128
|
+
}
|
|
129
|
+
interface AccountsTable {
|
|
130
|
+
id: Generated<string>;
|
|
131
|
+
email: string;
|
|
132
|
+
plan: Generated<string>;
|
|
133
|
+
created_at: Generated<Date>;
|
|
134
|
+
}
|
|
135
|
+
interface SessionsTable {
|
|
136
|
+
id: Generated<string>;
|
|
137
|
+
token_hash: string;
|
|
138
|
+
token_prefix: string;
|
|
139
|
+
account_id: string;
|
|
140
|
+
ip_address: string;
|
|
141
|
+
expires_at: Generated<Date>;
|
|
142
|
+
revoked_at: Date | null;
|
|
143
|
+
last_used_at: Date | null;
|
|
144
|
+
created_at: Generated<Date>;
|
|
145
|
+
}
|
|
146
|
+
interface MagicLinksTable {
|
|
147
|
+
id: Generated<string>;
|
|
148
|
+
email: string;
|
|
149
|
+
token: string;
|
|
150
|
+
expires_at: Date;
|
|
151
|
+
used_at: Date | null;
|
|
152
|
+
created_at: Generated<Date>;
|
|
153
|
+
}
|
|
154
|
+
interface UsageDailyTable {
|
|
155
|
+
account_id: string;
|
|
156
|
+
date: string;
|
|
157
|
+
api_requests: Generated<number>;
|
|
158
|
+
deliveries: Generated<number>;
|
|
159
|
+
}
|
|
160
|
+
interface UsageSnapshotsTable {
|
|
161
|
+
id: Generated<string>;
|
|
162
|
+
account_id: string;
|
|
163
|
+
measured_at: Generated<Date>;
|
|
164
|
+
storage_bytes: Generated<number>;
|
|
165
|
+
}
|
|
166
|
+
interface Database {
|
|
167
|
+
blocks: BlocksTable;
|
|
168
|
+
transactions: TransactionsTable;
|
|
169
|
+
events: EventsTable;
|
|
170
|
+
streams: StreamsTable;
|
|
171
|
+
stream_metrics: StreamMetricsTable;
|
|
172
|
+
jobs: JobsTable;
|
|
173
|
+
index_progress: IndexProgressTable;
|
|
174
|
+
deliveries: DeliveriesTable;
|
|
175
|
+
views: ViewsTable;
|
|
176
|
+
api_keys: ApiKeysTable;
|
|
177
|
+
accounts: AccountsTable;
|
|
178
|
+
sessions: SessionsTable;
|
|
179
|
+
magic_links: MagicLinksTable;
|
|
180
|
+
usage_daily: UsageDailyTable;
|
|
181
|
+
usage_snapshots: UsageSnapshotsTable;
|
|
182
|
+
}
|
|
183
|
+
type Block = Selectable<BlocksTable>;
|
|
184
|
+
type InsertBlock = Insertable<BlocksTable>;
|
|
185
|
+
type UpdateBlock = Updateable<BlocksTable>;
|
|
186
|
+
type Transaction = Selectable<TransactionsTable>;
|
|
187
|
+
type InsertTransaction = Insertable<TransactionsTable>;
|
|
188
|
+
type UpdateTransaction = Updateable<TransactionsTable>;
|
|
189
|
+
type Event = Selectable<EventsTable>;
|
|
190
|
+
type InsertEvent = Insertable<EventsTable>;
|
|
191
|
+
type UpdateEvent = Updateable<EventsTable>;
|
|
192
|
+
type Stream = Selectable<StreamsTable>;
|
|
193
|
+
type InsertStream = Insertable<StreamsTable>;
|
|
194
|
+
type UpdateStreamRow = Updateable<StreamsTable>;
|
|
195
|
+
type StreamMetrics = Selectable<StreamMetricsTable>;
|
|
196
|
+
type InsertStreamMetrics = Insertable<StreamMetricsTable>;
|
|
197
|
+
type UpdateStreamMetrics = Updateable<StreamMetricsTable>;
|
|
198
|
+
type Job = Selectable<JobsTable>;
|
|
199
|
+
type InsertJob = Insertable<JobsTable>;
|
|
200
|
+
type UpdateJob = Updateable<JobsTable>;
|
|
201
|
+
type IndexProgress = Selectable<IndexProgressTable>;
|
|
202
|
+
type InsertIndexProgress = Insertable<IndexProgressTable>;
|
|
203
|
+
type UpdateIndexProgress = Updateable<IndexProgressTable>;
|
|
204
|
+
type Delivery = Selectable<DeliveriesTable>;
|
|
205
|
+
type InsertDelivery = Insertable<DeliveriesTable>;
|
|
206
|
+
type UpdateDelivery = Updateable<DeliveriesTable>;
|
|
207
|
+
type View = Selectable<ViewsTable>;
|
|
208
|
+
type InsertView = Insertable<ViewsTable>;
|
|
209
|
+
type UpdateView = Updateable<ViewsTable>;
|
|
210
|
+
type ApiKey = Selectable<ApiKeysTable>;
|
|
211
|
+
type InsertApiKey = Insertable<ApiKeysTable>;
|
|
212
|
+
type UpdateApiKey = Updateable<ApiKeysTable>;
|
|
213
|
+
type Account = Selectable<AccountsTable>;
|
|
214
|
+
type InsertAccount = Insertable<AccountsTable>;
|
|
215
|
+
type MagicLink = Selectable<MagicLinksTable>;
|
|
216
|
+
type InsertMagicLink = Insertable<MagicLinksTable>;
|
|
217
|
+
type Session = Selectable<SessionsTable>;
|
|
218
|
+
type InsertSession = Insertable<SessionsTable>;
|
|
219
|
+
type UsageDaily = Selectable<UsageDailyTable>;
|
|
220
|
+
type UsageSnapshot = Selectable<UsageSnapshotsTable>;
|
|
221
|
+
import { sql } from "kysely";
|
|
222
|
+
declare function getDb(connectionString?: string): Kysely<Database>;
|
|
223
|
+
/** Raw postgres.js client for dynamic schema DDL (CREATE SCHEMA, DROP, etc.) */
|
|
224
|
+
declare function getRawClient(): ReturnType<typeof postgres>;
|
|
225
|
+
/** Close the DB connection pool. Call in CLI commands to allow process exit. */
|
|
226
|
+
declare function closeDb(): Promise<void>;
|
|
227
|
+
export { sql, parseJsonb, jsonb, getRawClient, getDb, closeDb, ViewsTable, View, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateView, UpdateTransaction, UpdateStreamRow, UpdateStreamMetrics, UpdateJob, UpdateIndexProgress, UpdateEvent, UpdateDelivery, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, StreamsTable, StreamMetricsTable, StreamMetrics, Stream, SessionsTable, Session, MagicLinksTable, MagicLink, JobsTable, Job, InsertView, InsertTransaction, InsertStreamMetrics, InsertStream, InsertSession, InsertMagicLink, InsertJob, InsertIndexProgress, InsertEvent, InsertDelivery, InsertBlock, InsertApiKey, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Delivery, DeliveriesTable, Database, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, Account };
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
export {
|
|
66
|
+
sql2 as sql,
|
|
67
|
+
parseJsonb,
|
|
68
|
+
jsonb,
|
|
69
|
+
getRawClient,
|
|
70
|
+
getDb,
|
|
71
|
+
closeDb
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//# debugId=F675FEF083D960AD64756E2164756E21
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/db/jsonb.ts", "../src/db/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
|
+
],
|
|
8
|
+
"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;",
|
|
9
|
+
"debugId": "F675FEF083D960AD64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely encode a JS value as a JSONB literal for Kysely inserts/updates.
|
|
3
|
+
* Kysely + postgres.js double-encodes JSON when using parameterized queries
|
|
4
|
+
* with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.
|
|
5
|
+
*/
|
|
6
|
+
declare function jsonb(value: unknown);
|
|
7
|
+
/**
|
|
8
|
+
* Safely parse a JSONB value from the database.
|
|
9
|
+
* Handles double-encoded strings where postgres.js returns a JSON string
|
|
10
|
+
* instead of a parsed object.
|
|
11
|
+
*/
|
|
12
|
+
declare function parseJsonb<T = unknown>(value: unknown): T;
|
|
13
|
+
export { parseJsonb, jsonb };
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
export {
|
|
30
|
+
parseJsonb,
|
|
31
|
+
jsonb
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//# debugId=FD7B2A33BAF805D564756E2164756E21
|
|
35
|
+
//# sourceMappingURL=jsonb.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/db/jsonb.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
|
+
],
|
|
7
|
+
"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;",
|
|
8
|
+
"debugId": "FD7B2A33BAF805D564756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|