@secondlayer/shared 2.1.0 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +2 -2
  2. package/dist/src/crypto/secrets.js +47 -3
  3. package/dist/src/crypto/secrets.js.map +5 -4
  4. package/dist/src/db/index.d.ts +112 -137
  5. package/dist/src/db/index.js.map +2 -2
  6. package/dist/src/db/jsonb.d.ts +5 -1
  7. package/dist/src/db/jsonb.js.map +2 -2
  8. package/dist/src/db/queries/account-spend-caps.d.ts +444 -0
  9. package/dist/src/db/queries/account-spend-caps.js +60 -0
  10. package/dist/src/db/queries/account-spend-caps.js.map +10 -0
  11. package/dist/src/db/queries/account-usage.d.ts +468 -0
  12. package/dist/src/db/queries/account-usage.js +222 -0
  13. package/dist/src/db/queries/account-usage.js.map +11 -0
  14. package/dist/src/db/queries/accounts.d.ts +100 -109
  15. package/dist/src/db/queries/accounts.js +15 -1
  16. package/dist/src/db/queries/accounts.js.map +3 -3
  17. package/dist/src/db/queries/integrity.d.ts +85 -107
  18. package/dist/src/db/queries/projects.d.ts +87 -109
  19. package/dist/src/db/queries/provisioning-audit.d.ts +85 -107
  20. package/dist/src/db/queries/subgraph-gaps.d.ts +85 -107
  21. package/dist/src/db/queries/subgraphs.d.ts +86 -109
  22. package/dist/src/db/queries/subgraphs.js +2 -3
  23. package/dist/src/db/queries/subgraphs.js.map +4 -4
  24. package/dist/src/db/queries/{workflows.d.ts → tenant-compute-addons.d.ts} +108 -142
  25. package/dist/src/db/queries/tenant-compute-addons.js +47 -0
  26. package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
  27. package/dist/src/db/queries/tenants.d.ts +98 -110
  28. package/dist/src/db/queries/tenants.js +55 -8
  29. package/dist/src/db/queries/tenants.js.map +6 -5
  30. package/dist/src/db/queries/usage.d.ts +86 -132
  31. package/dist/src/db/queries/usage.js +5 -64
  32. package/dist/src/db/queries/usage.js.map +4 -5
  33. package/dist/src/db/schema.d.ts +107 -136
  34. package/dist/src/errors.d.ts +8 -7
  35. package/dist/src/errors.js +11 -12
  36. package/dist/src/errors.js.map +3 -3
  37. package/dist/src/index.d.ts +119 -143
  38. package/dist/src/index.js +11 -12
  39. package/dist/src/index.js.map +4 -4
  40. package/dist/src/node/local-client.d.ts +85 -107
  41. package/dist/src/pricing.d.ts +20 -1
  42. package/dist/src/pricing.js +58 -1
  43. package/dist/src/pricing.js.map +3 -3
  44. package/migrations/0045_drop_marketplace_columns.ts +47 -0
  45. package/migrations/0046_tenant_activity_signal.ts +47 -0
  46. package/migrations/0047_usage_daily_tenant_id.ts +73 -0
  47. package/migrations/0048_tenant_compute_addons.ts +49 -0
  48. package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
  49. package/migrations/0050_account_spend_caps.ts +45 -0
  50. package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
  51. package/migrations/0052_sentries.ts +61 -0
  52. package/migrations/0053_workflow_runtime.ts +88 -0
  53. package/migrations/0054_accounts_plan_hobby.ts +32 -0
  54. package/migrations/0055_ai_usage_account_scope.ts +108 -0
  55. package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
  56. package/migrations/0057_subscriptions.ts +137 -0
  57. package/package.json +26 -14
  58. package/dist/src/db/queries/workflows.js +0 -260
  59. package/dist/src/db/queries/workflows.js.map +0 -12
  60. package/dist/src/lib/plans.d.ts +0 -9
  61. package/dist/src/lib/plans.js +0 -37
  62. package/dist/src/lib/plans.js.map +0 -10
  63. package/dist/src/schemas/workflows.d.ts +0 -70
  64. package/dist/src/schemas/workflows.js +0 -43
  65. package/dist/src/schemas/workflows.js.map +0 -10
package/README.md CHANGED
@@ -24,10 +24,11 @@ DATABASE_URL=... bun run migrate
24
24
  |------|-------------|
25
25
  | `@secondlayer/shared` | Core utilities |
26
26
  | `@secondlayer/shared/db` | Kysely database layer |
27
- | `@secondlayer/shared/db/queries/*` | Query helpers (integrity, accounts, usage, subgraphs, marketplace, projects, subgraph-gaps, workflows) |
27
+ | `@secondlayer/shared/db/queries/*` | Query helpers (integrity, accounts, usage, subgraphs, projects, subgraph-gaps, tenants, provisioning-audit) |
28
28
  | `@secondlayer/shared/db/schema` | Database schema |
29
29
  | `@secondlayer/shared/db/jsonb` | JSONB helpers |
30
30
  | `@secondlayer/shared/schemas` | Zod schemas |
31
+ | `@secondlayer/shared/schemas/accounts` | Account profile schemas |
31
32
  | `@secondlayer/shared/schemas/filters` | Event filter schemas |
32
33
  | `@secondlayer/shared/schemas/subgraphs` | Subgraph schemas |
33
34
  | `@secondlayer/shared/types` | Shared TypeScript types |
@@ -38,4 +39,3 @@ DATABASE_URL=... bun run migrate
38
39
  | `@secondlayer/shared/crypto` | HMAC signing |
39
40
  | `@secondlayer/shared/node` | Stacks node client |
40
41
  | `@secondlayer/shared/node/hiro-pg-client` | Direct PG queries against Hiro DB |
41
- | `@secondlayer/shared/lib/plans` | Plan definitions |
@@ -14,15 +14,59 @@ var __export = (target, all) => {
14
14
  });
15
15
  };
16
16
 
17
+ // src/mode.ts
18
+ var VALID_MODES = ["oss", "dedicated", "platform"];
19
+ function getInstanceMode() {
20
+ const raw = process.env.INSTANCE_MODE?.trim().toLowerCase();
21
+ if (raw && VALID_MODES.includes(raw)) {
22
+ return raw;
23
+ }
24
+ return "oss";
25
+ }
26
+ function isPlatformMode() {
27
+ return getInstanceMode() === "platform";
28
+ }
29
+ function isOssMode() {
30
+ return getInstanceMode() === "oss";
31
+ }
32
+ function isDedicatedMode() {
33
+ return getInstanceMode() === "dedicated";
34
+ }
35
+
17
36
  // src/crypto/secrets.ts
18
37
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
38
+ import { appendFileSync, existsSync, readFileSync } from "node:fs";
39
+ import { resolve } from "node:path";
19
40
  var KEY_ENV = "SECONDLAYER_SECRETS_KEY";
20
41
  var IV_LEN = 12;
21
42
  var TAG_LEN = 16;
43
+ function bootstrapOssKey() {
44
+ const envPath = resolve(process.cwd(), ".env.local");
45
+ if (existsSync(envPath)) {
46
+ const contents = readFileSync(envPath, "utf8");
47
+ const match = contents.match(/^SECONDLAYER_SECRETS_KEY=([a-fA-F0-9]{64})/m);
48
+ if (match) {
49
+ process.env[KEY_ENV] = match[1];
50
+ return match[1];
51
+ }
52
+ }
53
+ const hex = randomBytes(32).toString("hex");
54
+ const line = `${existsSync(envPath) ? `
55
+ ` : ""}${KEY_ENV}=${hex}
56
+ `;
57
+ appendFileSync(envPath, line, { mode: 384 });
58
+ process.env[KEY_ENV] = hex;
59
+ console.log(`[secondlayer] generated ${KEY_ENV}; saved to ${envPath} (mode 0600)`);
60
+ return hex;
61
+ }
22
62
  function loadKey() {
23
- const hex = process.env[KEY_ENV];
63
+ let hex = process.env[KEY_ENV];
24
64
  if (!hex) {
25
- throw new Error(`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`);
65
+ if (getInstanceMode() === "oss") {
66
+ hex = bootstrapOssKey();
67
+ } else {
68
+ throw new Error(`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`);
69
+ }
26
70
  }
27
71
  const key = Buffer.from(hex, "hex");
28
72
  if (key.length !== 32) {
@@ -65,5 +109,5 @@ export {
65
109
  decryptSecret
66
110
  };
67
111
 
68
- //# debugId=0FD7F496A1099A3864756E2164756E21
112
+ //# debugId=982B15A94DFBA98464756E2164756E21
69
113
  //# sourceMappingURL=secrets.js.map
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/crypto/secrets.ts"],
3
+ "sources": ["../src/mode.ts", "../src/crypto/secrets.ts"],
4
4
  "sourcesContent": [
5
- "import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\n\n/**\n * AES-256-GCM symmetric envelope for workflow signer secrets.\n *\n * Ciphertext layout: `iv (12 bytes) || authTag (16 bytes) || ciphertext`\n *\n * The key comes from `SECONDLAYER_SECRETS_KEY` 32 bytes hex. Callers must\n * load + cache the key once per process. Rotation strategy: when a customer\n * wants to rotate keys, re-encrypt all rows with the new key and swap the\n * env var. Not zero-downtime, but acceptable at v2 scale.\n *\n * For real KMS (AWS KMS, HashiCorp Vault, GCP KMS), wrap the same byte\n * layout behind an `EncryptSecret` / `DecryptSecret` interface in the\n * runner and swap the implementation at startup.\n */\n\nconst KEY_ENV = \"SECONDLAYER_SECRETS_KEY\";\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nfunction loadKey(): Buffer {\n\tconst hex = process.env[KEY_ENV];\n\tif (!hex) {\n\t\tthrow new Error(\n\t\t\t`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`,\n\t\t);\n\t}\n\tconst key = Buffer.from(hex, \"hex\");\n\tif (key.length !== 32) {\n\t\tthrow new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);\n\t}\n\treturn key;\n}\n\nlet _cachedKey: Buffer | null = null;\nfunction getKey(): Buffer {\n\tif (!_cachedKey) _cachedKey = loadKey();\n\treturn _cachedKey;\n}\n\nexport function encryptSecret(plaintext: string): Buffer {\n\tconst key = getKey();\n\tconst iv = randomBytes(IV_LEN);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, \"utf8\"),\n\t\tcipher.final(),\n\t]);\n\tconst tag = cipher.getAuthTag();\n\treturn Buffer.concat([iv, tag, ciphertext]);\n}\n\nexport function decryptSecret(envelope: Buffer): string {\n\tconst key = getKey();\n\tconst iv = envelope.subarray(0, IV_LEN);\n\tconst tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);\n\tconst ciphertext = envelope.subarray(IV_LEN + TAG_LEN);\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n\tdecipher.setAuthTag(tag);\n\treturn decipher.update(ciphertext).toString(\"utf8\") + decipher.final(\"utf8\");\n}\n\n/** Generate a fresh 32-byte hex key suitable for `SECONDLAYER_SECRETS_KEY`. */\nexport function generateSecretsKey(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n"
5
+ "/**\n * Instance modes for the Secondlayer platform.\n *\n * - `oss`: self-hosted, single-tenant. No auth middleware, no platform routes\n * (projects, admin, workflows). Everything runs against a single\n * `DATABASE_URL`. Intended for `docker compose up`.\n *\n * - `dedicated`: per-customer managed instance. JWT-based auth (anon =\n * read-only, service = full). Dual-DB mode shared source indexer DB for\n * block reads, per-tenant target DB for subgraph data. No platform-wide\n * routes mounted (no cross-tenant accounts).\n *\n * - `platform`: control-plane mode. Magic-link auth, API keys, projects,\n * tenants, admin. Serves the dashboard + CLI against a single shared DB.\n */\n\nexport type InstanceMode = \"oss\" | \"dedicated\" | \"platform\";\n\nconst VALID_MODES: readonly InstanceMode[] = [\"oss\", \"dedicated\", \"platform\"];\n\n/**\n * Resolve the active instance mode from `process.env.INSTANCE_MODE`.\n * Defaults to `\"oss\"` the safest default for self-hosters who deploy\n * without setting the variable.\n */\nexport function getInstanceMode(): InstanceMode {\n\tconst raw = process.env.INSTANCE_MODE?.trim().toLowerCase();\n\tif (raw && (VALID_MODES as readonly string[]).includes(raw)) {\n\t\treturn raw as InstanceMode;\n\t}\n\treturn \"oss\";\n}\n\n/** True when the active mode is `\"platform\"` (shared multi-tenant). */\nexport function isPlatformMode(): boolean {\n\treturn getInstanceMode() === \"platform\";\n}\n\n/** True when the active mode is `\"oss\"` (self-hosted). */\nexport function isOssMode(): boolean {\n\treturn getInstanceMode() === \"oss\";\n}\n\n/** True when the active mode is `\"dedicated\"` (per-tenant managed). */\nexport function isDedicatedMode(): boolean {\n\treturn getInstanceMode() === \"dedicated\";\n}\n",
6
+ "import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\nimport { appendFileSync, existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { getInstanceMode } from \"../mode.ts\";\n\n/**\n * AES-256-GCM symmetric envelope for encrypted secrets at rest (tenant keys,\n * subscription signing secrets, etc.).\n *\n * Ciphertext layout: `iv (12 bytes) || authTag (16 bytes) || ciphertext`\n *\n * The key comes from `SECONDLAYER_SECRETS_KEY` — 32 bytes hex. In OSS mode,\n * if the env var is unset on first use we autogenerate a key and persist it\n * to `.env.local` in the current working directory so subsequent restarts\n * pick it up without user intervention. Dedicated/platform modes throw —\n * those runtimes must provision the key explicitly.\n *\n * Rotation strategy: re-encrypt all rows with the new key and swap the env\n * var. Not zero-downtime, but acceptable at v2 scale. For real KMS (AWS\n * KMS, Vault, GCP KMS), wrap the same byte layout behind an\n * `EncryptSecret`/`DecryptSecret` interface and swap at startup.\n */\n\nconst KEY_ENV = \"SECONDLAYER_SECRETS_KEY\";\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nfunction bootstrapOssKey(): string {\n\tconst envPath = resolve(process.cwd(), \".env.local\");\n\n\t// Check existing .env.local first — prior run may have written it.\n\tif (existsSync(envPath)) {\n\t\tconst contents = readFileSync(envPath, \"utf8\");\n\t\tconst match = contents.match(/^SECONDLAYER_SECRETS_KEY=([a-fA-F0-9]{64})/m);\n\t\tif (match) {\n\t\t\tprocess.env[KEY_ENV] = match[1];\n\t\t\treturn match[1];\n\t\t}\n\t}\n\n\tconst hex = randomBytes(32).toString(\"hex\");\n\tconst line = `${existsSync(envPath) ? \"\\n\" : \"\"}${KEY_ENV}=${hex}\\n`;\n\tappendFileSync(envPath, line, { mode: 0o600 });\n\tprocess.env[KEY_ENV] = hex;\n\tconsole.log(\n\t\t`[secondlayer] generated ${KEY_ENV}; saved to ${envPath} (mode 0600)`,\n\t);\n\treturn hex;\n}\n\nfunction loadKey(): Buffer {\n\tlet hex = process.env[KEY_ENV];\n\tif (!hex) {\n\t\tif (getInstanceMode() === \"oss\") {\n\t\t\thex = bootstrapOssKey();\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`,\n\t\t\t);\n\t\t}\n\t}\n\tconst key = Buffer.from(hex, \"hex\");\n\tif (key.length !== 32) {\n\t\tthrow new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);\n\t}\n\treturn key;\n}\n\nlet _cachedKey: Buffer | null = null;\nfunction getKey(): Buffer {\n\tif (!_cachedKey) _cachedKey = loadKey();\n\treturn _cachedKey;\n}\n\nexport function encryptSecret(plaintext: string): Buffer {\n\tconst key = getKey();\n\tconst iv = randomBytes(IV_LEN);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, \"utf8\"),\n\t\tcipher.final(),\n\t]);\n\tconst tag = cipher.getAuthTag();\n\treturn Buffer.concat([iv, tag, ciphertext]);\n}\n\nexport function decryptSecret(envelope: Buffer): string {\n\tconst key = getKey();\n\tconst iv = envelope.subarray(0, IV_LEN);\n\tconst tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);\n\tconst ciphertext = envelope.subarray(IV_LEN + TAG_LEN);\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n\tdecipher.setAuthTag(tag);\n\treturn decipher.update(ciphertext).toString(\"utf8\") + decipher.final(\"utf8\");\n}\n\n/** Generate a fresh 32-byte hex key suitable for `SECONDLAYER_SECRETS_KEY`. */\nexport function generateSecretsKey(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n"
6
7
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAiBA,IAAM,UAAU;AAChB,IAAM,SAAS;AACf,IAAM,UAAU;AAEhB,SAAS,OAAO,GAAW;AAAA,EAC1B,MAAM,MAAM,QAAQ,IAAI;AAAA,EACxB,IAAI,CAAC,KAAK;AAAA,IACT,MAAM,IAAI,MACT,GAAG,0DACJ;AAAA,EACD;AAAA,EACA,MAAM,MAAM,OAAO,KAAK,KAAK,KAAK;AAAA,EAClC,IAAI,IAAI,WAAW,IAAI;AAAA,IACtB,MAAM,IAAI,MAAM,GAAG,qCAAqC,IAAI,SAAS;AAAA,EACtE;AAAA,EACA,OAAO;AAAA;AAGR,IAAI,aAA4B;AAChC,SAAS,MAAM,GAAW;AAAA,EACzB,IAAI,CAAC;AAAA,IAAY,aAAa,QAAQ;AAAA,EACtC,OAAO;AAAA;AAGD,SAAS,aAAa,CAAC,WAA2B;AAAA,EACxD,MAAM,MAAM,OAAO;AAAA,EACnB,MAAM,KAAK,YAAY,MAAM;AAAA,EAC7B,MAAM,SAAS,eAAe,eAAe,KAAK,EAAE;AAAA,EACpD,MAAM,aAAa,OAAO,OAAO;AAAA,IAChC,OAAO,OAAO,WAAW,MAAM;AAAA,IAC/B,OAAO,MAAM;AAAA,EACd,CAAC;AAAA,EACD,MAAM,MAAM,OAAO,WAAW;AAAA,EAC9B,OAAO,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC;AAAA;AAGpC,SAAS,aAAa,CAAC,UAA0B;AAAA,EACvD,MAAM,MAAM,OAAO;AAAA,EACnB,MAAM,KAAK,SAAS,SAAS,GAAG,MAAM;AAAA,EACtC,MAAM,MAAM,SAAS,SAAS,QAAQ,SAAS,OAAO;AAAA,EACtD,MAAM,aAAa,SAAS,SAAS,SAAS,OAAO;AAAA,EACrD,MAAM,WAAW,iBAAiB,eAAe,KAAK,EAAE;AAAA,EACxD,SAAS,WAAW,GAAG;AAAA,EACvB,OAAO,SAAS,OAAO,UAAU,EAAE,SAAS,MAAM,IAAI,SAAS,MAAM,MAAM;AAAA;AAIrE,SAAS,kBAAkB,GAAW;AAAA,EAC5C,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAAA;",
8
- "debugId": "0FD7F496A1099A3864756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "982B15A94DFBA98464756E2164756E21",
9
10
  "names": []
10
11
  }
@@ -3,8 +3,12 @@ import { RawBuilder } from "kysely";
3
3
  * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.
4
4
  * Kysely + postgres.js double-encodes JSON when using parameterized queries
5
5
  * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.
6
+ *
7
+ * Generic parameter lets callers set the RawBuilder's output type so they
8
+ * don't need to cast at the insert site. Default is `unknown` — widen at
9
+ * the call site when the column type is narrower, e.g. `jsonb<MyShape>(...)`.
6
10
  */
7
- declare function jsonb(value: unknown): RawBuilder<unknown>;
11
+ declare function jsonb<T = unknown>(value: T): RawBuilder<T>;
8
12
  /**
9
13
  * Safely parse a JSONB value from the database.
10
14
  * Handles double-encoded strings where postgres.js returns a JSON string
@@ -74,10 +78,6 @@ interface SubgraphsTable {
74
78
  handler_code: string | null;
75
79
  source_code: string | null;
76
80
  project_id: string | null;
77
- is_public: Generated<boolean>;
78
- tags: Generated<string[]>;
79
- description: string | null;
80
- forked_from_id: string | null;
81
81
  created_at: Generated<Date>;
82
82
  updated_at: Generated<Date>;
83
83
  }
@@ -112,6 +112,7 @@ interface AccountsTable {
112
112
  bio: string | null;
113
113
  avatar_url: string | null;
114
114
  slug: string | null;
115
+ stripe_customer_id: string | null;
115
116
  created_at: Generated<Date>;
116
117
  }
117
118
  interface SessionsTable {
@@ -137,6 +138,7 @@ interface MagicLinksTable {
137
138
  }
138
139
  interface UsageDailyTable {
139
140
  account_id: string;
141
+ tenant_id: string | null;
140
142
  date: string;
141
143
  api_requests: Generated<number>;
142
144
  deliveries: Generated<number>;
@@ -263,83 +265,6 @@ interface ChatMessagesTable {
263
265
  metadata: unknown | null;
264
266
  created_at: Generated<Date>;
265
267
  }
266
- interface WorkflowDefinitionsTable {
267
- id: Generated<string>;
268
- name: string;
269
- version: Generated<string>;
270
- status: Generated<string>;
271
- trigger_type: string;
272
- trigger_config: unknown;
273
- handler_path: string;
274
- source_code: string | null;
275
- retries_config: unknown | null;
276
- timeout_ms: number | null;
277
- api_key_id: string;
278
- project_id: string | null;
279
- created_at: Generated<Date>;
280
- updated_at: Generated<Date>;
281
- }
282
- interface WorkflowRunsTable {
283
- id: Generated<string>;
284
- definition_id: string;
285
- status: Generated<string>;
286
- trigger_type: string;
287
- trigger_data: unknown | null;
288
- dedup_key: string | null;
289
- error: string | null;
290
- started_at: Date | null;
291
- completed_at: Date | null;
292
- duration_ms: number | null;
293
- total_ai_tokens: Generated<number>;
294
- created_at: Generated<Date>;
295
- }
296
- interface WorkflowStepsTable {
297
- id: Generated<string>;
298
- run_id: string;
299
- step_index: number;
300
- step_id: string;
301
- step_type: string;
302
- status: Generated<string>;
303
- input: unknown | null;
304
- output: unknown | null;
305
- error: string | null;
306
- retry_count: Generated<number>;
307
- ai_tokens_used: Generated<number>;
308
- started_at: Date | null;
309
- completed_at: Date | null;
310
- duration_ms: number | null;
311
- memo_key: string | null;
312
- parent_step_id: string | null;
313
- created_at: Generated<Date>;
314
- }
315
- interface WorkflowQueueTable {
316
- id: Generated<string>;
317
- run_id: string;
318
- status: Generated<string>;
319
- attempts: Generated<number>;
320
- max_attempts: Generated<number>;
321
- scheduled_for: Generated<Date>;
322
- locked_at: Date | null;
323
- locked_by: string | null;
324
- error: string | null;
325
- created_at: Generated<Date>;
326
- completed_at: Date | null;
327
- }
328
- interface WorkflowSchedulesTable {
329
- id: Generated<string>;
330
- definition_id: string;
331
- cron_expr: string;
332
- timezone: Generated<string>;
333
- next_run_at: Date;
334
- last_run_at: Date | null;
335
- enabled: Generated<boolean>;
336
- created_at: Generated<Date>;
337
- }
338
- interface WorkflowCursorsTable {
339
- name: string;
340
- block_height: Generated<number>;
341
- updated_at: Generated<Date>;
342
- }
343
268
  interface Database {
344
269
  blocks: BlocksTable;
345
270
  transactions: TransactionsTable;
@@ -365,17 +290,14 @@ interface Database {
365
290
  team_invitations: TeamInvitationsTable;
366
291
  chat_sessions: ChatSessionsTable;
367
292
  chat_messages: ChatMessagesTable;
368
- workflow_definitions: WorkflowDefinitionsTable;
369
- workflow_runs: WorkflowRunsTable;
370
- workflow_steps: WorkflowStepsTable;
371
- workflow_queue: WorkflowQueueTable;
372
- workflow_schedules: WorkflowSchedulesTable;
373
- workflow_cursors: WorkflowCursorsTable;
374
- workflow_signer_secrets: WorkflowSignerSecretsTable;
375
- workflow_budgets: WorkflowBudgetsTable;
376
293
  tenants: TenantsTable;
377
294
  tenant_usage_monthly: TenantUsageMonthlyTable;
295
+ tenant_compute_addons: TenantComputeAddonsTable;
296
+ account_spend_caps: AccountSpendCapsTable;
378
297
  provisioning_audit_log: ProvisioningAuditLogTable;
298
+ subscriptions: SubscriptionsTable;
299
+ subscription_outbox: SubscriptionOutboxTable;
300
+ subscription_deliveries: SubscriptionDeliveriesTable;
379
301
  }
380
302
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
381
303
  interface TenantsTable {
@@ -397,9 +319,9 @@ interface TenantsTable {
397
319
  service_key_enc: Buffer;
398
320
  api_url_internal: string;
399
321
  api_url_public: string;
400
- trial_ends_at: Date;
401
322
  suspended_at: Date | null;
402
323
  last_health_check_at: Date | null;
324
+ last_active_at: Generated<Date>;
403
325
  service_gen: Generated<number>;
404
326
  anon_gen: Generated<number>;
405
327
  project_id: string | null;
@@ -423,6 +345,34 @@ interface TenantUsageMonthlyTable {
423
345
  type TenantUsageMonthly = Selectable<TenantUsageMonthlyTable>;
424
346
  type InsertTenantUsageMonthly = Insertable<TenantUsageMonthlyTable>;
425
347
  type UpdateTenantUsageMonthly = Updateable<TenantUsageMonthlyTable>;
348
+ interface TenantComputeAddonsTable {
349
+ id: Generated<string>;
350
+ tenant_id: string;
351
+ memory_mb_delta: Generated<number>;
352
+ cpu_delta: Generated<number | string>;
353
+ storage_mb_delta: Generated<number>;
354
+ effective_from: Generated<Date>;
355
+ effective_until: Date | null;
356
+ stripe_subscription_item_id: string | null;
357
+ created_at: Generated<Date>;
358
+ }
359
+ type TenantComputeAddon = Selectable<TenantComputeAddonsTable>;
360
+ type InsertTenantComputeAddon = Insertable<TenantComputeAddonsTable>;
361
+ type UpdateTenantComputeAddon = Updateable<TenantComputeAddonsTable>;
362
+ interface AccountSpendCapsTable {
363
+ account_id: string;
364
+ monthly_cap_cents: number | null;
365
+ compute_cap_cents: number | null;
366
+ storage_cap_cents: number | null;
367
+ ai_cap_cents: number | null;
368
+ alert_threshold_pct: Generated<number>;
369
+ alert_sent_at: Date | null;
370
+ frozen_at: Date | null;
371
+ updated_at: Generated<Date>;
372
+ }
373
+ type AccountSpendCap = Selectable<AccountSpendCapsTable>;
374
+ type InsertAccountSpendCap = Insertable<AccountSpendCapsTable>;
375
+ type UpdateAccountSpendCap = Updateable<AccountSpendCapsTable>;
426
376
  type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
427
377
  type ProvisioningAuditStatus = "ok" | "error";
428
378
  interface ProvisioningAuditLogTable {
@@ -439,30 +389,6 @@ interface ProvisioningAuditLogTable {
439
389
  }
440
390
  type ProvisioningAuditLog = Selectable<ProvisioningAuditLogTable>;
441
391
  type InsertProvisioningAuditLog = Insertable<ProvisioningAuditLogTable>;
442
- interface WorkflowBudgetsTable {
443
- id: Generated<string>;
444
- workflow_definition_id: string;
445
- /** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
446
- period: string;
447
- ai_usd_used: Generated<string>;
448
- ai_tokens_used: Generated<string>;
449
- chain_microstx_used: Generated<string>;
450
- chain_tx_count: Generated<number>;
451
- run_count: Generated<number>;
452
- step_count: Generated<number>;
453
- reset_at: Date;
454
- created_at: Generated<Date>;
455
- updated_at: Generated<Date>;
456
- }
457
- interface WorkflowSignerSecretsTable {
458
- id: Generated<string>;
459
- account_id: string;
460
- name: string;
461
- /** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
462
- encrypted_value: Buffer;
463
- created_at: Generated<Date>;
464
- updated_at: Generated<Date>;
465
- }
466
392
  type Block = Selectable<BlocksTable>;
467
393
  type InsertBlock = Insertable<BlocksTable>;
468
394
  type UpdateBlock = Updateable<BlocksTable>;
@@ -499,27 +425,6 @@ type SubgraphGap = Selectable<SubgraphGapsTable>;
499
425
  type InsertSubgraphGap = Insertable<SubgraphGapsTable>;
500
426
  type SubgraphUsageDaily = Selectable<SubgraphUsageDailyTable>;
501
427
  type InsertSubgraphUsageDaily = Insertable<SubgraphUsageDailyTable>;
502
- type WorkflowDefinition = Selectable<WorkflowDefinitionsTable>;
503
- type InsertWorkflowDefinition = Insertable<WorkflowDefinitionsTable>;
504
- type UpdateWorkflowDefinition = Updateable<WorkflowDefinitionsTable>;
505
- type WorkflowRun = Selectable<WorkflowRunsTable>;
506
- type InsertWorkflowRun = Insertable<WorkflowRunsTable>;
507
- type UpdateWorkflowRun = Updateable<WorkflowRunsTable>;
508
- type WorkflowStep = Selectable<WorkflowStepsTable>;
509
- type InsertWorkflowStep = Insertable<WorkflowStepsTable>;
510
- type UpdateWorkflowStep = Updateable<WorkflowStepsTable>;
511
- type WorkflowQueueItem = Selectable<WorkflowQueueTable>;
512
- type InsertWorkflowQueueItem = Insertable<WorkflowQueueTable>;
513
- type WorkflowSchedule = Selectable<WorkflowSchedulesTable>;
514
- type InsertWorkflowSchedule = Insertable<WorkflowSchedulesTable>;
515
- type UpdateWorkflowSchedule = Updateable<WorkflowSchedulesTable>;
516
- type WorkflowCursor = Selectable<WorkflowCursorsTable>;
517
- type WorkflowSignerSecret = Selectable<WorkflowSignerSecretsTable>;
518
- type InsertWorkflowSignerSecret = Insertable<WorkflowSignerSecretsTable>;
519
- type UpdateWorkflowSignerSecret = Updateable<WorkflowSignerSecretsTable>;
520
- type WorkflowBudget = Selectable<WorkflowBudgetsTable>;
521
- type InsertWorkflowBudget = Insertable<WorkflowBudgetsTable>;
522
- type UpdateWorkflowBudget = Updateable<WorkflowBudgetsTable>;
523
428
  type Project = Selectable<ProjectsTable>;
524
429
  type InsertProject = Insertable<ProjectsTable>;
525
430
  type UpdateProject = Updateable<ProjectsTable>;
@@ -532,6 +437,76 @@ type InsertChatSession = Insertable<ChatSessionsTable>;
532
437
  type UpdateChatSession = Updateable<ChatSessionsTable>;
533
438
  type ChatMessage = Selectable<ChatMessagesTable>;
534
439
  type InsertChatMessage = Insertable<ChatMessagesTable>;
440
+ type SubscriptionStatus = "active" | "paused" | "error";
441
+ type SubscriptionFormat = "standard-webhooks" | "inngest" | "trigger" | "cloudflare" | "cloudevents" | "raw";
442
+ type SubscriptionRuntime = "inngest" | "trigger" | "cloudflare" | "node";
443
+ interface SubscriptionsTable {
444
+ id: Generated<string>;
445
+ account_id: string;
446
+ project_id: string | null;
447
+ name: string;
448
+ status: ColumnType<SubscriptionStatus, SubscriptionStatus | undefined, SubscriptionStatus>;
449
+ subgraph_name: string;
450
+ table_name: string;
451
+ filter: Generated<unknown>;
452
+ format: ColumnType<SubscriptionFormat, SubscriptionFormat | undefined, SubscriptionFormat>;
453
+ runtime: SubscriptionRuntime | null;
454
+ url: string;
455
+ signing_secret_enc: Buffer;
456
+ auth_config: Generated<unknown>;
457
+ max_retries: Generated<number>;
458
+ timeout_ms: Generated<number>;
459
+ concurrency: Generated<number>;
460
+ circuit_failures: Generated<number>;
461
+ circuit_opened_at: Date | null;
462
+ last_delivery_at: Date | null;
463
+ last_success_at: Date | null;
464
+ last_error: string | null;
465
+ created_at: Generated<Date>;
466
+ updated_at: Generated<Date>;
467
+ }
468
+ type Subscription = Selectable<SubscriptionsTable>;
469
+ type InsertSubscription = Insertable<SubscriptionsTable>;
470
+ type UpdateSubscription = Updateable<SubscriptionsTable>;
471
+ type OutboxStatus = "pending" | "delivered" | "dead";
472
+ interface SubscriptionOutboxTable {
473
+ id: Generated<string>;
474
+ subscription_id: string;
475
+ subgraph_name: string;
476
+ table_name: string;
477
+ block_height: number | bigint;
478
+ tx_id: string | null;
479
+ row_pk: unknown;
480
+ event_type: string;
481
+ payload: unknown;
482
+ dedup_key: string;
483
+ attempt: Generated<number>;
484
+ next_attempt_at: Generated<Date>;
485
+ status: ColumnType<OutboxStatus, OutboxStatus | undefined, OutboxStatus>;
486
+ is_replay: Generated<boolean>;
487
+ delivered_at: Date | null;
488
+ failed_at: Date | null;
489
+ locked_by: string | null;
490
+ locked_until: Date | null;
491
+ created_at: Generated<Date>;
492
+ }
493
+ type SubscriptionOutbox = Selectable<SubscriptionOutboxTable>;
494
+ type InsertSubscriptionOutbox = Insertable<SubscriptionOutboxTable>;
495
+ type UpdateSubscriptionOutbox = Updateable<SubscriptionOutboxTable>;
496
+ interface SubscriptionDeliveriesTable {
497
+ id: Generated<string>;
498
+ outbox_id: string;
499
+ subscription_id: string;
500
+ attempt: number;
501
+ status_code: number | null;
502
+ response_headers: unknown | null;
503
+ response_body: string | null;
504
+ error_message: string | null;
505
+ duration_ms: number | null;
506
+ dispatched_at: Generated<Date>;
507
+ }
508
+ type SubscriptionDelivery = Selectable<SubscriptionDeliveriesTable>;
509
+ type InsertSubscriptionDelivery = Insertable<SubscriptionDeliveriesTable>;
535
510
  import { sql } from "kysely";
536
511
  /**
537
512
  * Kysely instance for the SOURCE DB (block/tx/event reads from the shared
@@ -557,4 +532,4 @@ declare function getDb(connectionString?: string): Kysely<Database>;
557
532
  declare function getRawClient(role?: "source" | "target"): ReturnType<typeof postgres>;
558
533
  /** Close all DB connection pools. Call in CLI commands to allow process exit. */
559
534
  declare function closeDb(): Promise<void>;
560
- export { sql, parseJsonb, jsonb, getTargetDb, getSourceDb, getRawClient, getDb, closeDb, WorkflowStepsTable, WorkflowStep, WorkflowSignerSecretsTable, WorkflowSignerSecret, WorkflowSchedulesTable, WorkflowSchedule, WorkflowRunsTable, WorkflowRun, WorkflowQueueTable, WorkflowQueueItem, WorkflowDefinitionsTable, WorkflowDefinition, WorkflowCursorsTable, WorkflowCursor, WorkflowBudgetsTable, WorkflowBudget, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateWorkflowStep, UpdateWorkflowSignerSecret, UpdateWorkflowSchedule, UpdateWorkflowRun, UpdateWorkflowDefinition, UpdateWorkflowBudget, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenant, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantStatus, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, MagicLinksTable, MagicLink, InsertWorkflowStep, InsertWorkflowSignerSecret, InsertWorkflowSchedule, InsertWorkflowRun, InsertWorkflowQueueItem, InsertWorkflowDefinition, InsertWorkflowBudget, InsertTransaction, InsertTenantUsageMonthly, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubgraphUsageDaily, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Database, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
535
+ export { sql, parseJsonb, jsonb, getTargetDb, getSourceDb, getRawClient, getDb, closeDb, WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraph, UpdateProject, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, UpdateAccountSpendCap, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantStatus, TenantComputeAddonsTable, TenantComputeAddon, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubscriptionsTable, SubscriptionStatus, SubscriptionRuntime, SubscriptionOutboxTable, SubscriptionOutbox, SubscriptionFormat, SubscriptionDelivery, SubscriptionDeliveriesTable, Subscription, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphProcessingStatsTable, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, OutboxStatus, MagicLinksTable, MagicLink, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, Database, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/jsonb.ts", "../src/db/index.ts"],
4
4
  "sourcesContent": [
5
- "import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n\tconst escaped = JSON.stringify(value, (_k, v) =>\n\t\ttypeof v === \"bigint\" ? v.toString() : v,\n\t).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n",
5
+ "import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n *\n * Generic parameter lets callers set the RawBuilder's output type so they\n * don't need to cast at the insert site. Default is `unknown` — widen at\n * the call site when the column type is narrower, e.g. `jsonb<MyShape>(...)`.\n */\nexport function jsonb<T = unknown>(value: T): RawBuilder<T> {\n\tconst escaped = JSON.stringify(value, (_k, v) =>\n\t\ttypeof v === \"bigint\" ? v.toString() : v,\n\t).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n",
6
6
  "import { Kysely } from \"kysely\";\nimport { PostgresJSDialect } from \"kysely-postgres-js\";\nimport postgres from \"postgres\";\nimport type { Database } from \"./types.ts\";\n\nconst DEFAULT_URL =\n\t\"postgres://postgres:postgres@localhost:5432/secondlayer_dev\";\n\ninterface PoolEntry {\n\tdb: Kysely<Database>;\n\trawClient: ReturnType<typeof postgres>;\n}\n\n/**\n * Cache of Kysely + raw postgres.js pools keyed by resolved URL.\n * Two getters resolving to the same URL share one entry (single pool) —\n * this is the single-DB backward-compat contract: when only `DATABASE_URL`\n * is set, `getSourceDb() === getTargetDb()` (zero regression vs. pre-dual-DB).\n */\nconst pools = new Map<string, PoolEntry>();\n\nfunction resolveSourceUrl(): string {\n\treturn (\n\t\tprocess.env.SOURCE_DATABASE_URL || process.env.DATABASE_URL || DEFAULT_URL\n\t);\n}\n\nfunction resolveTargetUrl(): string {\n\treturn (\n\t\tprocess.env.TARGET_DATABASE_URL || process.env.DATABASE_URL || DEFAULT_URL\n\t);\n}\n\nfunction getOrCreatePool(url: string): PoolEntry {\n\tconst existing = pools.get(url);\n\tif (existing) return existing;\n\n\t// \"Local\" = we skip TLS. Any Docker service alias (single-label hostname\n\t// with no dots) is on an internal network and won't serve TLS.\n\tconst host = (() => {\n\t\ttry {\n\t\t\treturn new URL(url).hostname;\n\t\t} catch {\n\t\t\treturn \"\";\n\t\t}\n\t})();\n\tconst isLocal =\n\t\thost === \"localhost\" || host === \"127.0.0.1\" || !host.includes(\".\");\n\tconst poolMax = Number.parseInt(process.env.DATABASE_POOL_MAX ?? \"20\", 10);\n\tconst rawClient = postgres(url, {\n\t\tmax: poolMax,\n\t\tssl: isLocal\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\trejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== \"0\",\n\t\t\t\t},\n\t});\n\tconst db = new Kysely<Database>({\n\t\tdialect: new PostgresJSDialect({ postgres: rawClient }),\n\t});\n\tconst entry: PoolEntry = { db, rawClient };\n\tpools.set(url, entry);\n\treturn entry;\n}\n\n/**\n * Kysely instance for the SOURCE DB (block/tx/event reads from the shared\n * indexer). Resolution: `SOURCE_DATABASE_URL || DATABASE_URL`.\n */\nexport function getSourceDb(): Kysely<Database> {\n\treturn getOrCreatePool(resolveSourceUrl()).db;\n}\n\n/**\n * Kysely instance for the TARGET DB (subgraph schemas, subgraphs table,\n * account-scoped data — tenant-side writes). Resolution:\n * `TARGET_DATABASE_URL || DATABASE_URL`.\n */\nexport function getTargetDb(): Kysely<Database> {\n\treturn getOrCreatePool(resolveTargetUrl()).db;\n}\n\n/**\n * Backward-compat alias for `getTargetDb()`. Accepts an optional\n * `connectionString` override used by seed/test helpers — when supplied,\n * bypasses env resolution and uses the provided URL directly (still cached).\n */\nexport function getDb(connectionString?: string): Kysely<Database> {\n\tif (connectionString) return getOrCreatePool(connectionString).db;\n\treturn getTargetDb();\n}\n\n/**\n * Raw postgres.js client for dynamic schema DDL (CREATE SCHEMA, DROP, etc.).\n * Defaults to the target role (tenant schemas live in the target DB).\n */\nexport function getRawClient(\n\trole: \"source\" | \"target\" = \"target\",\n): ReturnType<typeof postgres> {\n\tconst url = role === \"source\" ? resolveSourceUrl() : resolveTargetUrl();\n\treturn getOrCreatePool(url).rawClient;\n}\n\n/** Close all DB connection pools. Call in CLI commands to allow process exit. */\nexport async function closeDb(): Promise<void> {\n\tfor (const entry of pools.values()) {\n\t\tawait entry.db.destroy();\n\t\tawait entry.rawClient.end();\n\t}\n\tpools.clear();\n}\n\nimport { sql } from \"kysely\";\nexport { sql };\nexport * from \"./types.ts\";\nexport { jsonb, parseJsonb } from \"./jsonb.ts\";\n"
7
7
  ],
8
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EAC1D,MAAM,UAAU,KAAK,UAAU,OAAO,CAAC,IAAI,MAC1C,OAAO,MAAM,WAAW,EAAE,SAAS,IAAI,CACxC,EAAE,QAAQ,MAAM,IAAI;AAAA,EACpB,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQpC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EAC1D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,KAAK,MAAM,KAAK;AAAA,MACtB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;;;AC3BnB;AACA;AACA;AA8GA,gBAAS;AA3GT,IAAM,cACL;AAaD,IAAM,QAAQ,IAAI;AAElB,SAAS,gBAAgB,GAAW;AAAA,EACnC,OACC,QAAQ,IAAI,uBAAuB,QAAQ,IAAI,gBAAgB;AAAA;AAIjE,SAAS,gBAAgB,GAAW;AAAA,EACnC,OACC,QAAQ,IAAI,uBAAuB,QAAQ,IAAI,gBAAgB;AAAA;AAIjE,SAAS,eAAe,CAAC,KAAwB;AAAA,EAChD,MAAM,WAAW,MAAM,IAAI,GAAG;AAAA,EAC9B,IAAI;AAAA,IAAU,OAAO;AAAA,EAIrB,MAAM,QAAQ,MAAM;AAAA,IACnB,IAAI;AAAA,MACH,OAAO,IAAI,IAAI,GAAG,EAAE;AAAA,MACnB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,KAEN;AAAA,EACH,MAAM,UACL,SAAS,eAAe,SAAS,eAAe,CAAC,KAAK,SAAS,GAAG;AAAA,EACnE,MAAM,UAAU,OAAO,SAAS,QAAQ,IAAI,qBAAqB,MAAM,EAAE;AAAA,EACzE,MAAM,YAAY,SAAS,KAAK;AAAA,IAC/B,KAAK;AAAA,IACL,KAAK,UACF,YACA;AAAA,MACA,oBAAoB,QAAQ,IAAI,iCAAiC;AAAA,IAClE;AAAA,EACH,CAAC;AAAA,EACD,MAAM,KAAK,IAAI,OAAiB;AAAA,IAC/B,SAAS,IAAI,kBAAkB,EAAE,UAAU,UAAU,CAAC;AAAA,EACvD,CAAC;AAAA,EACD,MAAM,QAAmB,EAAE,IAAI,UAAU;AAAA,EACzC,MAAM,IAAI,KAAK,KAAK;AAAA,EACpB,OAAO;AAAA;AAOD,SAAS,WAAW,GAAqB;AAAA,EAC/C,OAAO,gBAAgB,iBAAiB,CAAC,EAAE;AAAA;AAQrC,SAAS,WAAW,GAAqB;AAAA,EAC/C,OAAO,gBAAgB,iBAAiB,CAAC,EAAE;AAAA;AAQrC,SAAS,KAAK,CAAC,kBAA6C;AAAA,EAClE,IAAI;AAAA,IAAkB,OAAO,gBAAgB,gBAAgB,EAAE;AAAA,EAC/D,OAAO,YAAY;AAAA;AAOb,SAAS,YAAY,CAC3B,OAA4B,UACE;AAAA,EAC9B,MAAM,MAAM,SAAS,WAAW,iBAAiB,IAAI,iBAAiB;AAAA,EACtE,OAAO,gBAAgB,GAAG,EAAE;AAAA;AAI7B,eAAsB,OAAO,GAAkB;AAAA,EAC9C,WAAW,SAAS,MAAM,OAAO,GAAG;AAAA,IACnC,MAAM,MAAM,GAAG,QAAQ;AAAA,IACvB,MAAM,MAAM,UAAU,IAAI;AAAA,EAC3B;AAAA,EACA,MAAM,MAAM;AAAA;",
8
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAWO,SAAS,KAAkB,CAAC,OAAyB;AAAA,EAC3D,MAAM,UAAU,KAAK,UAAU,OAAO,CAAC,IAAI,MAC1C,OAAO,MAAM,WAAW,EAAE,SAAS,IAAI,CACxC,EAAE,QAAQ,MAAM,IAAI;AAAA,EACpB,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQpC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EAC1D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,KAAK,MAAM,KAAK;AAAA,MACtB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;;;AC/BnB;AACA;AACA;AA8GA,gBAAS;AA3GT,IAAM,cACL;AAaD,IAAM,QAAQ,IAAI;AAElB,SAAS,gBAAgB,GAAW;AAAA,EACnC,OACC,QAAQ,IAAI,uBAAuB,QAAQ,IAAI,gBAAgB;AAAA;AAIjE,SAAS,gBAAgB,GAAW;AAAA,EACnC,OACC,QAAQ,IAAI,uBAAuB,QAAQ,IAAI,gBAAgB;AAAA;AAIjE,SAAS,eAAe,CAAC,KAAwB;AAAA,EAChD,MAAM,WAAW,MAAM,IAAI,GAAG;AAAA,EAC9B,IAAI;AAAA,IAAU,OAAO;AAAA,EAIrB,MAAM,QAAQ,MAAM;AAAA,IACnB,IAAI;AAAA,MACH,OAAO,IAAI,IAAI,GAAG,EAAE;AAAA,MACnB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,KAEN;AAAA,EACH,MAAM,UACL,SAAS,eAAe,SAAS,eAAe,CAAC,KAAK,SAAS,GAAG;AAAA,EACnE,MAAM,UAAU,OAAO,SAAS,QAAQ,IAAI,qBAAqB,MAAM,EAAE;AAAA,EACzE,MAAM,YAAY,SAAS,KAAK;AAAA,IAC/B,KAAK;AAAA,IACL,KAAK,UACF,YACA;AAAA,MACA,oBAAoB,QAAQ,IAAI,iCAAiC;AAAA,IAClE;AAAA,EACH,CAAC;AAAA,EACD,MAAM,KAAK,IAAI,OAAiB;AAAA,IAC/B,SAAS,IAAI,kBAAkB,EAAE,UAAU,UAAU,CAAC;AAAA,EACvD,CAAC;AAAA,EACD,MAAM,QAAmB,EAAE,IAAI,UAAU;AAAA,EACzC,MAAM,IAAI,KAAK,KAAK;AAAA,EACpB,OAAO;AAAA;AAOD,SAAS,WAAW,GAAqB;AAAA,EAC/C,OAAO,gBAAgB,iBAAiB,CAAC,EAAE;AAAA;AAQrC,SAAS,WAAW,GAAqB;AAAA,EAC/C,OAAO,gBAAgB,iBAAiB,CAAC,EAAE;AAAA;AAQrC,SAAS,KAAK,CAAC,kBAA6C;AAAA,EAClE,IAAI;AAAA,IAAkB,OAAO,gBAAgB,gBAAgB,EAAE;AAAA,EAC/D,OAAO,YAAY;AAAA;AAOb,SAAS,YAAY,CAC3B,OAA4B,UACE;AAAA,EAC9B,MAAM,MAAM,SAAS,WAAW,iBAAiB,IAAI,iBAAiB;AAAA,EACtE,OAAO,gBAAgB,GAAG,EAAE;AAAA;AAI7B,eAAsB,OAAO,GAAkB;AAAA,EAC9C,WAAW,SAAS,MAAM,OAAO,GAAG;AAAA,IACnC,MAAM,MAAM,GAAG,QAAQ;AAAA,IACvB,MAAM,MAAM,UAAU,IAAI;AAAA,EAC3B;AAAA,EACA,MAAM,MAAM;AAAA;",
9
9
  "debugId": "C0DD7408E000897364756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -3,8 +3,12 @@ import { RawBuilder } from "kysely";
3
3
  * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.
4
4
  * Kysely + postgres.js double-encodes JSON when using parameterized queries
5
5
  * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.
6
+ *
7
+ * Generic parameter lets callers set the RawBuilder's output type so they
8
+ * don't need to cast at the insert site. Default is `unknown` — widen at
9
+ * the call site when the column type is narrower, e.g. `jsonb<MyShape>(...)`.
6
10
  */
7
- declare function jsonb(value: unknown): RawBuilder<unknown>;
11
+ declare function jsonb<T = unknown>(value: T): RawBuilder<T>;
8
12
  /**
9
13
  * Safely parse a JSONB value from the database.
10
14
  * Handles double-encoded strings where postgres.js returns a JSON string
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/jsonb.ts"],
4
4
  "sourcesContent": [
5
- "import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n */\nexport function jsonb(value: unknown): RawBuilder<unknown> {\n\tconst escaped = JSON.stringify(value, (_k, v) =>\n\t\ttypeof v === \"bigint\" ? v.toString() : v,\n\t).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n"
5
+ "import { type RawBuilder, sql } from \"kysely\";\n\n/**\n * Safely encode a JS value as a JSONB literal for Kysely inserts/updates.\n * Kysely + postgres.js double-encodes JSON when using parameterized queries\n * with ::jsonb casts. This uses sql.raw to inline a properly escaped literal.\n *\n * Generic parameter lets callers set the RawBuilder's output type so they\n * don't need to cast at the insert site. Default is `unknown` — widen at\n * the call site when the column type is narrower, e.g. `jsonb<MyShape>(...)`.\n */\nexport function jsonb<T = unknown>(value: T): RawBuilder<T> {\n\tconst escaped = JSON.stringify(value, (_k, v) =>\n\t\ttypeof v === \"bigint\" ? v.toString() : v,\n\t).replace(/'/g, \"''\");\n\treturn sql`${sql.raw(`'${escaped}'::jsonb`)}`;\n}\n\n/**\n * Safely parse a JSONB value from the database.\n * Handles double-encoded strings where postgres.js returns a JSON string\n * instead of a parsed object.\n */\nexport function parseJsonb<T = unknown>(value: unknown): T {\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\treturn JSON.parse(value) as T;\n\t\t} catch {\n\t\t\treturn value as T;\n\t\t}\n\t}\n\treturn (value ?? {}) as T;\n}\n"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAOO,SAAS,KAAK,CAAC,OAAqC;AAAA,EAC1D,MAAM,UAAU,KAAK,UAAU,OAAO,CAAC,IAAI,MAC1C,OAAO,MAAM,WAAW,EAAE,SAAS,IAAI,CACxC,EAAE,QAAQ,MAAM,IAAI;AAAA,EACpB,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQpC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EAC1D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,KAAK,MAAM,KAAK;AAAA,MACtB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;",
7
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAWO,SAAS,KAAkB,CAAC,OAAyB;AAAA,EAC3D,MAAM,UAAU,KAAK,UAAU,OAAO,CAAC,IAAI,MAC1C,OAAO,MAAM,WAAW,EAAE,SAAS,IAAI,CACxC,EAAE,QAAQ,MAAM,IAAI;AAAA,EACpB,OAAO,MAAM,IAAI,IAAI,IAAI,iBAAiB;AAAA;AAQpC,SAAS,UAAuB,CAAC,OAAmB;AAAA,EAC1D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC9B,IAAI;AAAA,MACH,OAAO,KAAK,MAAM,KAAK;AAAA,MACtB,MAAM;AAAA,MACP,OAAO;AAAA;AAAA,EAET;AAAA,EACA,OAAQ,SAAS,CAAC;AAAA;",
8
8
  "debugId": "DA6ABA81E2ABDC7864756E2164756E21",
9
9
  "names": []
10
10
  }