@secondlayer/shared 0.12.3 → 1.1.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.
Files changed (70) hide show
  1. package/README.md +5 -7
  2. package/dist/src/constants.d.ts +1 -1
  3. package/dist/src/constants.js.map +2 -2
  4. package/dist/src/crypto/secrets.d.ts +5 -0
  5. package/dist/src/crypto/secrets.js +69 -0
  6. package/dist/src/crypto/secrets.js.map +10 -0
  7. package/dist/src/db/index.d.ts +37 -64
  8. package/dist/src/db/index.js +2 -2
  9. package/dist/src/db/index.js.map +2 -2
  10. package/dist/src/db/queries/accounts.d.ts +30 -51
  11. package/dist/src/db/queries/integrity.d.ts +30 -51
  12. package/dist/src/db/queries/marketplace.d.ts +30 -51
  13. package/dist/src/db/queries/projects.d.ts +30 -51
  14. package/dist/src/db/queries/subgraph-gaps.d.ts +30 -51
  15. package/dist/src/db/queries/subgraphs.d.ts +31 -51
  16. package/dist/src/db/queries/subgraphs.js +3 -1
  17. package/dist/src/db/queries/subgraphs.js.map +3 -3
  18. package/dist/src/db/queries/usage.d.ts +31 -56
  19. package/dist/src/db/queries/usage.js +1 -19
  20. package/dist/src/db/queries/usage.js.map +4 -4
  21. package/dist/src/db/queries/workflows.d.ts +35 -53
  22. package/dist/src/db/queries/workflows.js +130 -13
  23. package/dist/src/db/queries/workflows.js.map +5 -4
  24. package/dist/src/db/schema.d.ts +37 -64
  25. package/dist/src/errors.d.ts +19 -50
  26. package/dist/src/errors.js +28 -45
  27. package/dist/src/errors.js.map +3 -3
  28. package/dist/src/index.d.ts +59 -256
  29. package/dist/src/index.js +32 -234
  30. package/dist/src/index.js.map +7 -9
  31. package/dist/src/lib/plans.d.ts +0 -1
  32. package/dist/src/lib/plans.js +1 -2
  33. package/dist/src/lib/plans.js.map +3 -3
  34. package/dist/src/node/local-client.d.ts +30 -51
  35. package/dist/src/pricing.d.ts +28 -0
  36. package/dist/src/pricing.js +47 -0
  37. package/dist/src/pricing.js.map +10 -0
  38. package/dist/src/queue/listener.d.ts +1 -18
  39. package/dist/src/queue/listener.js +2 -12
  40. package/dist/src/queue/listener.js.map +3 -3
  41. package/dist/src/schemas/filters.d.ts +3 -3
  42. package/dist/src/schemas/filters.js +3 -3
  43. package/dist/src/schemas/filters.js.map +3 -3
  44. package/dist/src/schemas/index.d.ts +5 -100
  45. package/dist/src/schemas/index.js +4 -88
  46. package/dist/src/schemas/index.js.map +5 -6
  47. package/dist/src/schemas/subgraphs.d.ts +2 -0
  48. package/dist/src/schemas/subgraphs.js +2 -1
  49. package/dist/src/schemas/subgraphs.js.map +3 -3
  50. package/dist/src/schemas/workflows.d.ts +4 -0
  51. package/dist/src/schemas/workflows.js +5 -1
  52. package/dist/src/schemas/workflows.js.map +3 -3
  53. package/dist/src/types.d.ts +1 -53
  54. package/migrations/0030_workflow_source_code.ts +21 -0
  55. package/migrations/0031_subgraph_source_code.ts +18 -0
  56. package/migrations/0032_drop_streams_tables.ts +43 -0
  57. package/migrations/0033_workflow_steps_memo_key.ts +54 -0
  58. package/migrations/0034_workflow_signer_secrets.ts +42 -0
  59. package/migrations/0035_workflow_budgets.ts +53 -0
  60. package/migrations/0036_tx_confirmed_notify.ts +36 -0
  61. package/package.json +11 -15
  62. package/dist/src/db/queries/metrics.d.ts +0 -419
  63. package/dist/src/db/queries/metrics.js +0 -55
  64. package/dist/src/db/queries/metrics.js.map +0 -10
  65. package/dist/src/queue/index.d.ts +0 -50
  66. package/dist/src/queue/index.js +0 -184
  67. package/dist/src/queue/index.js.map +0 -12
  68. package/dist/src/queue/recovery.d.ts +0 -14
  69. package/dist/src/queue/recovery.js +0 -108
  70. package/dist/src/queue/recovery.js.map +0 -12
@@ -1,5 +1,4 @@
1
1
  interface PlanLimits {
2
- streams: number;
3
2
  subgraphs: number;
4
3
  apiRequestsPerDay: number;
5
4
  deliveriesPerMonth: number;
@@ -40,40 +39,6 @@ interface EventsTable {
40
39
  data: unknown;
41
40
  created_at: Generated<Date>;
42
41
  }
43
- interface StreamsTable {
44
- id: Generated<string>;
45
- name: string;
46
- status: Generated<string>;
47
- filters: unknown;
48
- options: Generated<unknown>;
49
- endpoint_url: string;
50
- signing_secret: string | null;
51
- api_key_id: string;
52
- project_id: string | null;
53
- created_at: Generated<Date>;
54
- updated_at: Generated<Date>;
55
- }
56
- interface StreamMetricsTable {
57
- stream_id: string;
58
- last_triggered_at: Date | null;
59
- last_triggered_block: number | null;
60
- total_deliveries: Generated<number>;
61
- failed_deliveries: Generated<number>;
62
- error_message: string | null;
63
- }
64
- interface JobsTable {
65
- id: Generated<string>;
66
- stream_id: string;
67
- block_height: number;
68
- status: Generated<string>;
69
- attempts: Generated<number>;
70
- locked_at: Date | null;
71
- locked_by: string | null;
72
- error: string | null;
73
- backfill: Generated<boolean>;
74
- created_at: Generated<Date>;
75
- completed_at: Date | null;
76
- }
77
42
  interface IndexProgressTable {
78
43
  network: string;
79
44
  last_indexed_block: Generated<number>;
@@ -81,19 +46,6 @@ interface IndexProgressTable {
81
46
  highest_seen_block: Generated<number>;
82
47
  updated_at: Generated<Date>;
83
48
  }
84
- interface DeliveriesTable {
85
- id: Generated<string>;
86
- stream_id: string;
87
- job_id: string | null;
88
- block_height: number;
89
- status: string;
90
- status_code: number | null;
91
- response_time_ms: number | null;
92
- attempts: Generated<number>;
93
- error: string | null;
94
- payload: unknown;
95
- created_at: Generated<Date>;
96
- }
97
49
  interface SubgraphsTable {
98
50
  id: Generated<string>;
99
51
  name: string;
@@ -114,6 +66,7 @@ interface SubgraphsTable {
114
66
  api_key_id: string | null;
115
67
  account_id: string;
116
68
  handler_code: string | null;
69
+ source_code: string | null;
117
70
  project_id: string | null;
118
71
  is_public: Generated<boolean>;
119
72
  tags: Generated<string[]>;
@@ -312,6 +265,7 @@ interface WorkflowDefinitionsTable {
312
265
  trigger_type: string;
313
266
  trigger_config: unknown;
314
267
  handler_path: string;
268
+ source_code: string | null;
315
269
  retries_config: unknown | null;
316
270
  timeout_ms: number | null;
317
271
  api_key_id: string;
@@ -348,6 +302,8 @@ interface WorkflowStepsTable {
348
302
  started_at: Date | null;
349
303
  completed_at: Date | null;
350
304
  duration_ms: number | null;
305
+ memo_key: string | null;
306
+ parent_step_id: string | null;
351
307
  created_at: Generated<Date>;
352
308
  }
353
309
  interface WorkflowQueueTable {
@@ -382,11 +338,7 @@ interface Database {
382
338
  blocks: BlocksTable;
383
339
  transactions: TransactionsTable;
384
340
  events: EventsTable;
385
- streams: StreamsTable;
386
- stream_metrics: StreamMetricsTable;
387
- jobs: JobsTable;
388
341
  index_progress: IndexProgressTable;
389
- deliveries: DeliveriesTable;
390
342
  subgraphs: SubgraphsTable;
391
343
  api_keys: ApiKeysTable;
392
344
  accounts: AccountsTable;
@@ -413,11 +365,35 @@ interface Database {
413
365
  workflow_queue: WorkflowQueueTable;
414
366
  workflow_schedules: WorkflowSchedulesTable;
415
367
  workflow_cursors: WorkflowCursorsTable;
368
+ workflow_signer_secrets: WorkflowSignerSecretsTable;
369
+ workflow_budgets: WorkflowBudgetsTable;
370
+ }
371
+ interface WorkflowBudgetsTable {
372
+ id: Generated<string>;
373
+ workflow_definition_id: string;
374
+ /** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
375
+ period: string;
376
+ ai_usd_used: Generated<string>;
377
+ ai_tokens_used: Generated<string>;
378
+ chain_microstx_used: Generated<string>;
379
+ chain_tx_count: Generated<number>;
380
+ run_count: Generated<number>;
381
+ step_count: Generated<number>;
382
+ reset_at: Date;
383
+ created_at: Generated<Date>;
384
+ updated_at: Generated<Date>;
385
+ }
386
+ interface WorkflowSignerSecretsTable {
387
+ id: Generated<string>;
388
+ account_id: string;
389
+ name: string;
390
+ /** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
391
+ encrypted_value: Buffer;
392
+ created_at: Generated<Date>;
393
+ updated_at: Generated<Date>;
416
394
  }
417
395
  /** Increment API request counter for today. Fire-and-forget safe. */
418
396
  declare function incrementApiRequests(db: Kysely<Database>, accountId: string): Promise<void>;
419
- /** Increment delivery counter for today. */
420
- declare function incrementDeliveries(db: Kysely<Database>, accountId: string, count?: number): Promise<void>;
421
397
  interface UsageSummary {
422
398
  apiRequestsToday: number;
423
399
  deliveriesThisMonth: number;
@@ -436,7 +412,6 @@ interface LimitCheck {
436
412
  allowed: boolean;
437
413
  limits: ReturnType<typeof getPlanLimits>;
438
414
  current: UsageSummary & {
439
- streams: number
440
415
  subgraphs: number
441
416
  };
442
417
  exceeded?: string;
@@ -448,4 +423,4 @@ declare function checkLimits(db: Kysely<Database>, accountId: string, plan: stri
448
423
  * for each tenant's subgraph schemas.
449
424
  */
450
425
  declare function measureStorage(db: Kysely<Database>): Promise<void>;
451
- export { measureStorage, incrementDeliveries, incrementApiRequests, getUsage, getDailyUsage, checkLimits, UsageSummary, LimitCheck, DailyUsage };
426
+ export { measureStorage, incrementApiRequests, getUsage, getDailyUsage, checkLimits, UsageSummary, LimitCheck, DailyUsage };
@@ -16,7 +16,6 @@ var __export = (target, all) => {
16
16
 
17
17
  // src/lib/plans.ts
18
18
  var FREE_PLAN = {
19
- streams: 3,
20
19
  subgraphs: 2,
21
20
  apiRequestsPerDay: 1000,
22
21
  deliveriesPerMonth: 5000,
@@ -43,17 +42,6 @@ async function incrementApiRequests(db, accountId) {
43
42
  api_requests: sql`usage_daily.api_requests + 1`
44
43
  })).execute();
45
44
  }
46
- async function incrementDeliveries(db, accountId, count = 1) {
47
- const today = new Date().toISOString().slice(0, 10);
48
- await db.insertInto("usage_daily").values({
49
- account_id: accountId,
50
- date: today,
51
- api_requests: 0,
52
- deliveries: count
53
- }).onConflict((oc) => oc.columns(["account_id", "date"]).doUpdateSet({
54
- deliveries: sql`usage_daily.deliveries + ${count}`
55
- })).execute();
56
- }
57
45
  async function getUsage(db, accountId) {
58
46
  const today = new Date().toISOString().slice(0, 10);
59
47
  const monthStart = today.slice(0, 7) + "-01";
@@ -90,16 +78,11 @@ async function getDailyUsage(db, accountId) {
90
78
  async function checkLimits(db, accountId, plan) {
91
79
  const limits = getPlanLimits(plan);
92
80
  const usage = await getUsage(db, accountId);
93
- 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();
94
81
  const subgraphCount = await db.selectFrom("subgraphs").innerJoin("api_keys", "subgraphs.api_key_id", "api_keys.id").select(sql`count(*)`.as("count")).where("api_keys.account_id", "=", accountId).executeTakeFirst();
95
82
  const current = {
96
83
  ...usage,
97
- streams: Number(streamCount?.count ?? 0),
98
84
  subgraphs: Number(subgraphCount?.count ?? 0)
99
85
  };
100
- if (current.streams >= limits.streams) {
101
- return { allowed: false, limits, current, exceeded: "streams" };
102
- }
103
86
  if (current.subgraphs >= limits.subgraphs) {
104
87
  return { allowed: false, limits, current, exceeded: "subgraphs" };
105
88
  }
@@ -142,12 +125,11 @@ async function measureStorage(db) {
142
125
  }
143
126
  export {
144
127
  measureStorage,
145
- incrementDeliveries,
146
128
  incrementApiRequests,
147
129
  getUsage,
148
130
  getDailyUsage,
149
131
  checkLimits
150
132
  };
151
133
 
152
- //# debugId=C9AA7973D9E596F264756E2164756E21
134
+ //# debugId=0844CA82D7046A1564756E2164756E21
153
135
  //# sourceMappingURL=usage.js.map
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/lib/plans.ts", "../src/db/queries/usage.ts"],
4
4
  "sourcesContent": [
5
- "export interface PlanLimits {\n\tstreams: number;\n\tsubgraphs: number;\n\tapiRequestsPerDay: number;\n\tdeliveriesPerMonth: number;\n\tstorageBytes: number;\n}\n\nexport const FREE_PLAN: PlanLimits = {\n\tstreams: 3,\n\tsubgraphs: 2,\n\tapiRequestsPerDay: 1_000,\n\tdeliveriesPerMonth: 5_000,\n\tstorageBytes: 100 * 1024 * 1024, // 100MB\n};\n\nexport function getPlanLimits(plan: string): PlanLimits {\n\tswitch (plan) {\n\t\tcase \"free\":\n\t\tdefault:\n\t\t\treturn FREE_PLAN;\n\t}\n}\n",
6
- "import { type Kysely, sql } from \"kysely\";\nimport { getPlanLimits } from \"../../lib/plans.ts\";\nimport type { Database } from \"../types.ts\";\n\n/** Increment API request counter for today. Fire-and-forget safe. */\nexport async function incrementApiRequests(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<void> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tawait db\n\t\t.insertInto(\"usage_daily\")\n\t\t.values({\n\t\t\taccount_id: accountId,\n\t\t\tdate: today,\n\t\t\tapi_requests: 1,\n\t\t\tdeliveries: 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"account_id\", \"date\"]).doUpdateSet({\n\t\t\t\tapi_requests: sql`usage_daily.api_requests + 1`,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\n/** Increment delivery counter for today. */\nexport async function incrementDeliveries(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tcount = 1,\n): Promise<void> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tawait db\n\t\t.insertInto(\"usage_daily\")\n\t\t.values({\n\t\t\taccount_id: accountId,\n\t\t\tdate: today,\n\t\t\tapi_requests: 0,\n\t\t\tdeliveries: count,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"account_id\", \"date\"]).doUpdateSet({\n\t\t\t\tdeliveries: sql`usage_daily.deliveries + ${count}`,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\nexport interface UsageSummary {\n\tapiRequestsToday: number;\n\tdeliveriesThisMonth: number;\n\tstorageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<UsageSummary> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tconst monthStart = today.slice(0, 7) + \"-01\"; // YYYY-MM-01\n\n\t// Today's API requests\n\tconst dailyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(\"api_requests\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \"=\", today)\n\t\t.executeTakeFirst();\n\n\t// This month's deliveries\n\tconst monthlyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", monthStart)\n\t\t.executeTakeFirst();\n\n\t// Latest storage snapshot\n\tconst storageRow = await db\n\t\t.selectFrom(\"usage_snapshots\")\n\t\t.select(\"storage_bytes\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.orderBy(\"measured_at\", \"desc\")\n\t\t.limit(1)\n\t\t.executeTakeFirst();\n\n\treturn {\n\t\tapiRequestsToday: dailyRow?.api_requests ?? 0,\n\t\tdeliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n\t\tstorageBytes: Number(storageRow?.storage_bytes ?? 0),\n\t};\n}\n\nexport interface DailyUsage {\n\tdate: string;\n\tapiRequests: number;\n\tdeliveries: number;\n}\n\n/** Get last 7 days of daily usage, filling missing days with 0. */\nexport async function getDailyUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<DailyUsage[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select([\"date\", \"api_requests\", \"deliveries\"])\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", sql<string>`NOW()::date - 6`)\n\t\t.orderBy(\"date\", \"asc\")\n\t\t.execute();\n\n\t// Fill missing days with 0 (normalize date to YYYY-MM-DD string)\n\tconst byDate = new Map(\n\t\trows.map((r) => {\n\t\t\tconst d = r.date as unknown;\n\t\t\tconst key =\n\t\t\t\td instanceof Date\n\t\t\t\t\t? d.toISOString().slice(0, 10)\n\t\t\t\t\t: String(d).slice(0, 10);\n\t\t\treturn [key, r];\n\t\t}),\n\t);\n\tconst result: DailyUsage[] = [];\n\tfor (let i = 6; i >= 0; i--) {\n\t\tconst d = new Date();\n\t\td.setDate(d.getDate() - i);\n\t\tconst dateStr = d.toISOString().slice(0, 10);\n\t\tconst row = byDate.get(dateStr);\n\t\tresult.push({\n\t\t\tdate: dateStr,\n\t\t\tapiRequests: row?.api_requests ?? 0,\n\t\t\tdeliveries: row?.deliveries ?? 0,\n\t\t});\n\t}\n\treturn result;\n}\n\nexport interface LimitCheck {\n\tallowed: boolean;\n\tlimits: ReturnType<typeof getPlanLimits>;\n\tcurrent: UsageSummary & { streams: number; subgraphs: number };\n\texceeded?: string;\n}\n\n/** Check if an account is within plan limits. */\nexport async function checkLimits(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n): Promise<LimitCheck> {\n\tconst limits = getPlanLimits(plan);\n\tconst usage = await getUsage(db, accountId);\n\n\t// Count streams owned by this account's keys\n\tconst streamCount = await db\n\t\t.selectFrom(\"streams\")\n\t\t.innerJoin(\"api_keys\", \"streams.api_key_id\", \"api_keys.id\")\n\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t.where(\"api_keys.account_id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\n\tconst subgraphCount = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t.where(\"api_keys.account_id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\n\tconst current = {\n\t\t...usage,\n\t\tstreams: Number(streamCount?.count ?? 0),\n\t\tsubgraphs: Number(subgraphCount?.count ?? 0),\n\t};\n\n\t// Check each limit\n\tif (current.streams >= limits.streams) {\n\t\treturn { allowed: false, limits, current, exceeded: \"streams\" };\n\t}\n\tif (current.subgraphs >= limits.subgraphs) {\n\t\treturn { allowed: false, limits, current, exceeded: \"subgraphs\" };\n\t}\n\tif (current.apiRequestsToday >= limits.apiRequestsPerDay) {\n\t\treturn { allowed: false, limits, current, exceeded: \"api_requests\" };\n\t}\n\tif (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {\n\t\treturn { allowed: false, limits, current, exceeded: \"deliveries\" };\n\t}\n\tif (current.storageBytes >= limits.storageBytes) {\n\t\treturn { allowed: false, limits, current, exceeded: \"storage\" };\n\t}\n\n\treturn { allowed: true, limits, current };\n}\n\n/**\n * Measure storage for all accounts by querying pg_total_relation_size\n * for each tenant's subgraph schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n\t// Get all accounts with subgraphs\n\tconst accountSubgraphs = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n\t\t.select([\"api_keys.account_id\", \"subgraphs.schema_name\"])\n\t\t.where(\"subgraphs.schema_name\", \"is not\", null)\n\t\t.execute();\n\n\t// Group schemas by account\n\tconst byAccount = new Map<string, string[]>();\n\tfor (const row of accountSubgraphs) {\n\t\tconst schemas = byAccount.get(row.account_id) ?? [];\n\t\tif (row.schema_name) schemas.push(row.schema_name);\n\t\tbyAccount.set(row.account_id, schemas);\n\t}\n\n\tfor (const [accountId, schemas] of byAccount) {\n\t\tlet totalBytes = 0;\n\t\tfor (const schema of schemas) {\n\t\t\ttry {\n\t\t\t\tconst 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\t\t\t\ttotalBytes += Number((result.rows[0] as any)?.size ?? 0);\n\t\t\t} catch {\n\t\t\t\t// Schema may not exist\n\t\t\t}\n\t\t}\n\n\t\tawait db\n\t\t\t.insertInto(\"usage_snapshots\")\n\t\t\t.values({\n\t\t\t\taccount_id: accountId,\n\t\t\t\tstorage_bytes: totalBytes,\n\t\t\t})\n\t\t\t.execute();\n\t}\n}\n"
5
+ "export interface PlanLimits {\n\tsubgraphs: number;\n\tapiRequestsPerDay: number;\n\tdeliveriesPerMonth: number;\n\tstorageBytes: number;\n}\n\nexport const FREE_PLAN: PlanLimits = {\n\tsubgraphs: 2,\n\tapiRequestsPerDay: 1_000,\n\tdeliveriesPerMonth: 5_000,\n\tstorageBytes: 100 * 1024 * 1024,\n};\n\nexport function getPlanLimits(plan: string): PlanLimits {\n\tswitch (plan) {\n\t\tcase \"free\":\n\t\tdefault:\n\t\t\treturn FREE_PLAN;\n\t}\n}\n",
6
+ "import { type Kysely, sql } from \"kysely\";\nimport { getPlanLimits } from \"../../lib/plans.ts\";\nimport type { Database } from \"../types.ts\";\n\n/** Increment API request counter for today. Fire-and-forget safe. */\nexport async function incrementApiRequests(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<void> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tawait db\n\t\t.insertInto(\"usage_daily\")\n\t\t.values({\n\t\t\taccount_id: accountId,\n\t\t\tdate: today,\n\t\t\tapi_requests: 1,\n\t\t\tdeliveries: 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"account_id\", \"date\"]).doUpdateSet({\n\t\t\t\tapi_requests: sql`usage_daily.api_requests + 1`,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\nexport interface UsageSummary {\n\tapiRequestsToday: number;\n\tdeliveriesThisMonth: number;\n\tstorageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<UsageSummary> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tconst monthStart = today.slice(0, 7) + \"-01\"; // YYYY-MM-01\n\n\t// Today's API requests\n\tconst dailyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(\"api_requests\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \"=\", today)\n\t\t.executeTakeFirst();\n\n\t// This month's deliveries\n\tconst monthlyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", monthStart)\n\t\t.executeTakeFirst();\n\n\t// Latest storage snapshot\n\tconst storageRow = await db\n\t\t.selectFrom(\"usage_snapshots\")\n\t\t.select(\"storage_bytes\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.orderBy(\"measured_at\", \"desc\")\n\t\t.limit(1)\n\t\t.executeTakeFirst();\n\n\treturn {\n\t\tapiRequestsToday: dailyRow?.api_requests ?? 0,\n\t\tdeliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n\t\tstorageBytes: Number(storageRow?.storage_bytes ?? 0),\n\t};\n}\n\nexport interface DailyUsage {\n\tdate: string;\n\tapiRequests: number;\n\tdeliveries: number;\n}\n\n/** Get last 7 days of daily usage, filling missing days with 0. */\nexport async function getDailyUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<DailyUsage[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select([\"date\", \"api_requests\", \"deliveries\"])\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", sql<string>`NOW()::date - 6`)\n\t\t.orderBy(\"date\", \"asc\")\n\t\t.execute();\n\n\t// Fill missing days with 0 (normalize date to YYYY-MM-DD string)\n\tconst byDate = new Map(\n\t\trows.map((r) => {\n\t\t\tconst d = r.date as unknown;\n\t\t\tconst key =\n\t\t\t\td instanceof Date\n\t\t\t\t\t? d.toISOString().slice(0, 10)\n\t\t\t\t\t: String(d).slice(0, 10);\n\t\t\treturn [key, r];\n\t\t}),\n\t);\n\tconst result: DailyUsage[] = [];\n\tfor (let i = 6; i >= 0; i--) {\n\t\tconst d = new Date();\n\t\td.setDate(d.getDate() - i);\n\t\tconst dateStr = d.toISOString().slice(0, 10);\n\t\tconst row = byDate.get(dateStr);\n\t\tresult.push({\n\t\t\tdate: dateStr,\n\t\t\tapiRequests: row?.api_requests ?? 0,\n\t\t\tdeliveries: row?.deliveries ?? 0,\n\t\t});\n\t}\n\treturn result;\n}\n\nexport interface LimitCheck {\n\tallowed: boolean;\n\tlimits: ReturnType<typeof getPlanLimits>;\n\tcurrent: UsageSummary & { subgraphs: number };\n\texceeded?: string;\n}\n\n/** Check if an account is within plan limits. */\nexport async function checkLimits(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n): Promise<LimitCheck> {\n\tconst limits = getPlanLimits(plan);\n\tconst usage = await getUsage(db, accountId);\n\n\tconst subgraphCount = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t.where(\"api_keys.account_id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\n\tconst current = {\n\t\t...usage,\n\t\tsubgraphs: Number(subgraphCount?.count ?? 0),\n\t};\n\n\tif (current.subgraphs >= limits.subgraphs) {\n\t\treturn { allowed: false, limits, current, exceeded: \"subgraphs\" };\n\t}\n\tif (current.apiRequestsToday >= limits.apiRequestsPerDay) {\n\t\treturn { allowed: false, limits, current, exceeded: \"api_requests\" };\n\t}\n\tif (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {\n\t\treturn { allowed: false, limits, current, exceeded: \"deliveries\" };\n\t}\n\tif (current.storageBytes >= limits.storageBytes) {\n\t\treturn { allowed: false, limits, current, exceeded: \"storage\" };\n\t}\n\n\treturn { allowed: true, limits, current };\n}\n\n/**\n * Measure storage for all accounts by querying pg_total_relation_size\n * for each tenant's subgraph schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n\t// Get all accounts with subgraphs\n\tconst accountSubgraphs = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.innerJoin(\"api_keys\", \"subgraphs.api_key_id\", \"api_keys.id\")\n\t\t.select([\"api_keys.account_id\", \"subgraphs.schema_name\"])\n\t\t.where(\"subgraphs.schema_name\", \"is not\", null)\n\t\t.execute();\n\n\t// Group schemas by account\n\tconst byAccount = new Map<string, string[]>();\n\tfor (const row of accountSubgraphs) {\n\t\tconst schemas = byAccount.get(row.account_id) ?? [];\n\t\tif (row.schema_name) schemas.push(row.schema_name);\n\t\tbyAccount.set(row.account_id, schemas);\n\t}\n\n\tfor (const [accountId, schemas] of byAccount) {\n\t\tlet totalBytes = 0;\n\t\tfor (const schema of schemas) {\n\t\t\ttry {\n\t\t\t\tconst 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\t\t\t\ttotalBytes += Number((result.rows[0] as any)?.size ?? 0);\n\t\t\t} catch {\n\t\t\t\t// Schema may not exist\n\t\t\t}\n\t\t}\n\n\t\tawait db\n\t\t\t.insertInto(\"usage_snapshots\")\n\t\t\t.values({\n\t\t\t\taccount_id: accountId,\n\t\t\t\tstorage_bytes: totalBytes,\n\t\t\t})\n\t\t\t.execute();\n\t}\n}\n"
7
7
  ],
8
- "mappings": "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",
9
- "debugId": "C9AA7973D9E596F264756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "0844CA82D7046A1564756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -32,40 +32,6 @@ interface EventsTable {
32
32
  data: unknown;
33
33
  created_at: Generated<Date>;
34
34
  }
35
- interface StreamsTable {
36
- id: Generated<string>;
37
- name: string;
38
- status: Generated<string>;
39
- filters: unknown;
40
- options: Generated<unknown>;
41
- endpoint_url: string;
42
- signing_secret: string | null;
43
- api_key_id: string;
44
- project_id: string | null;
45
- created_at: Generated<Date>;
46
- updated_at: Generated<Date>;
47
- }
48
- interface StreamMetricsTable {
49
- stream_id: string;
50
- last_triggered_at: Date | null;
51
- last_triggered_block: number | null;
52
- total_deliveries: Generated<number>;
53
- failed_deliveries: Generated<number>;
54
- error_message: string | null;
55
- }
56
- interface JobsTable {
57
- id: Generated<string>;
58
- stream_id: string;
59
- block_height: number;
60
- status: Generated<string>;
61
- attempts: Generated<number>;
62
- locked_at: Date | null;
63
- locked_by: string | null;
64
- error: string | null;
65
- backfill: Generated<boolean>;
66
- created_at: Generated<Date>;
67
- completed_at: Date | null;
68
- }
69
35
  interface IndexProgressTable {
70
36
  network: string;
71
37
  last_indexed_block: Generated<number>;
@@ -73,19 +39,6 @@ interface IndexProgressTable {
73
39
  highest_seen_block: Generated<number>;
74
40
  updated_at: Generated<Date>;
75
41
  }
76
- interface DeliveriesTable {
77
- id: Generated<string>;
78
- stream_id: string;
79
- job_id: string | null;
80
- block_height: number;
81
- status: string;
82
- status_code: number | null;
83
- response_time_ms: number | null;
84
- attempts: Generated<number>;
85
- error: string | null;
86
- payload: unknown;
87
- created_at: Generated<Date>;
88
- }
89
42
  interface SubgraphsTable {
90
43
  id: Generated<string>;
91
44
  name: string;
@@ -106,6 +59,7 @@ interface SubgraphsTable {
106
59
  api_key_id: string | null;
107
60
  account_id: string;
108
61
  handler_code: string | null;
62
+ source_code: string | null;
109
63
  project_id: string | null;
110
64
  is_public: Generated<boolean>;
111
65
  tags: Generated<string[]>;
@@ -304,6 +258,7 @@ interface WorkflowDefinitionsTable {
304
258
  trigger_type: string;
305
259
  trigger_config: unknown;
306
260
  handler_path: string;
261
+ source_code: string | null;
307
262
  retries_config: unknown | null;
308
263
  timeout_ms: number | null;
309
264
  api_key_id: string;
@@ -340,6 +295,8 @@ interface WorkflowStepsTable {
340
295
  started_at: Date | null;
341
296
  completed_at: Date | null;
342
297
  duration_ms: number | null;
298
+ memo_key: string | null;
299
+ parent_step_id: string | null;
343
300
  created_at: Generated<Date>;
344
301
  }
345
302
  interface WorkflowQueueTable {
@@ -374,11 +331,7 @@ interface Database {
374
331
  blocks: BlocksTable;
375
332
  transactions: TransactionsTable;
376
333
  events: EventsTable;
377
- streams: StreamsTable;
378
- stream_metrics: StreamMetricsTable;
379
- jobs: JobsTable;
380
334
  index_progress: IndexProgressTable;
381
- deliveries: DeliveriesTable;
382
335
  subgraphs: SubgraphsTable;
383
336
  api_keys: ApiKeysTable;
384
337
  accounts: AccountsTable;
@@ -405,10 +358,38 @@ interface Database {
405
358
  workflow_queue: WorkflowQueueTable;
406
359
  workflow_schedules: WorkflowSchedulesTable;
407
360
  workflow_cursors: WorkflowCursorsTable;
361
+ workflow_signer_secrets: WorkflowSignerSecretsTable;
362
+ workflow_budgets: WorkflowBudgetsTable;
363
+ }
364
+ interface WorkflowBudgetsTable {
365
+ id: Generated<string>;
366
+ workflow_definition_id: string;
367
+ /** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
368
+ period: string;
369
+ ai_usd_used: Generated<string>;
370
+ ai_tokens_used: Generated<string>;
371
+ chain_microstx_used: Generated<string>;
372
+ chain_tx_count: Generated<number>;
373
+ run_count: Generated<number>;
374
+ step_count: Generated<number>;
375
+ reset_at: Date;
376
+ created_at: Generated<Date>;
377
+ updated_at: Generated<Date>;
378
+ }
379
+ interface WorkflowSignerSecretsTable {
380
+ id: Generated<string>;
381
+ account_id: string;
382
+ name: string;
383
+ /** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
384
+ encrypted_value: Buffer;
385
+ created_at: Generated<Date>;
386
+ updated_at: Generated<Date>;
408
387
  }
409
388
  type WorkflowDefinition = Selectable<WorkflowDefinitionsTable>;
410
389
  type WorkflowRun = Selectable<WorkflowRunsTable>;
411
390
  type WorkflowStep = Selectable<WorkflowStepsTable>;
391
+ /** Bump the patch digit of a semver string. Falls back to "1.0.1" on malformed input. */
392
+ declare function bumpPatch(version: string): string;
412
393
  declare function listWorkflowDefinitions(db: Kysely<Database>, apiKeyIds?: string[]): Promise<WorkflowDefinition[]>;
413
394
  declare function getWorkflowDefinition(db: Kysely<Database>, name: string, apiKeyIds?: string[]): Promise<WorkflowDefinition | null>;
414
395
  declare function upsertWorkflowDefinition(db: Kysely<Database>, data: {
@@ -420,7 +401,8 @@ declare function upsertWorkflowDefinition(db: Kysely<Database>, data: {
420
401
  projectId?: string
421
402
  retriesConfig?: Record<string, unknown>
422
403
  timeoutMs?: number
423
- version?: string
404
+ sourceCode?: string
405
+ expectedVersion?: string
424
406
  }): Promise<WorkflowDefinition>;
425
407
  declare function updateWorkflowStatus(db: Kysely<Database>, name: string, apiKeyId: string, status: string): Promise<void>;
426
408
  declare function deleteWorkflowDefinition(db: Kysely<Database>, name: string, apiKeyId: string): Promise<void>;
@@ -437,4 +419,4 @@ declare function listWorkflowRuns(db: Kysely<Database>, definitionId: string, pa
437
419
  offset?: number
438
420
  }): Promise<WorkflowRun[]>;
439
421
  declare function getWorkflowSteps(db: Kysely<Database>, runId: string): Promise<WorkflowStep[]>;
440
- export { upsertWorkflowDefinition, updateWorkflowStatus, listWorkflowRuns, listWorkflowDefinitions, getWorkflowSteps, getWorkflowRun, getWorkflowDefinition, deleteWorkflowDefinition, createWorkflowRun };
422
+ export { upsertWorkflowDefinition, updateWorkflowStatus, listWorkflowRuns, listWorkflowDefinitions, getWorkflowSteps, getWorkflowRun, getWorkflowDefinition, deleteWorkflowDefinition, createWorkflowRun, bumpPatch };
@@ -31,7 +31,113 @@ function parseJsonb(value) {
31
31
  return value ?? {};
32
32
  }
33
33
 
34
+ // src/errors.ts
35
+ var ErrorCodes = {
36
+ VALIDATION_ERROR: "VALIDATION_ERROR",
37
+ DATABASE_ERROR: "DATABASE_ERROR",
38
+ AUTHENTICATION_ERROR: "AUTHENTICATION_ERROR",
39
+ AUTHORIZATION_ERROR: "AUTHORIZATION_ERROR",
40
+ RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR",
41
+ FORBIDDEN: "FORBIDDEN",
42
+ VERSION_CONFLICT: "VERSION_CONFLICT",
43
+ NOT_FOUND: "NOT_FOUND"
44
+ };
45
+
46
+ class SecondLayerError extends Error {
47
+ code;
48
+ cause;
49
+ constructor(code, message, cause) {
50
+ super(message);
51
+ this.code = code;
52
+ this.cause = cause;
53
+ this.name = this.constructor.name;
54
+ Error.captureStackTrace?.(this, this.constructor);
55
+ }
56
+ toJSON() {
57
+ return {
58
+ name: this.name,
59
+ code: this.code,
60
+ message: this.message,
61
+ stack: this.stack,
62
+ cause: this.cause
63
+ };
64
+ }
65
+ }
66
+
67
+ class NotFoundError extends SecondLayerError {
68
+ constructor(message) {
69
+ super("NOT_FOUND", message);
70
+ }
71
+ }
72
+
73
+ class ValidationError extends SecondLayerError {
74
+ constructor(message, cause) {
75
+ super("VALIDATION_ERROR", message, cause);
76
+ }
77
+ }
78
+
79
+ class DatabaseError extends SecondLayerError {
80
+ constructor(message, cause) {
81
+ super("DATABASE_ERROR", message, cause);
82
+ }
83
+ }
84
+
85
+ class AuthenticationError extends SecondLayerError {
86
+ constructor(message) {
87
+ super("AUTHENTICATION_ERROR", message);
88
+ }
89
+ }
90
+
91
+ class AuthorizationError extends SecondLayerError {
92
+ constructor(message) {
93
+ super("AUTHORIZATION_ERROR", message);
94
+ }
95
+ }
96
+
97
+ class RateLimitError extends SecondLayerError {
98
+ constructor(message) {
99
+ super("RATE_LIMIT_ERROR", message);
100
+ }
101
+ }
102
+
103
+ class ForbiddenError extends SecondLayerError {
104
+ constructor(message = "Forbidden") {
105
+ super("FORBIDDEN", message);
106
+ }
107
+ }
108
+
109
+ class VersionConflictError extends SecondLayerError {
110
+ currentVersion;
111
+ expectedVersion;
112
+ constructor(currentVersion, expectedVersion) {
113
+ super("VERSION_CONFLICT", `Version conflict: expected ${expectedVersion}, current ${currentVersion}`);
114
+ this.currentVersion = currentVersion;
115
+ this.expectedVersion = expectedVersion;
116
+ }
117
+ }
118
+ var CODE_TO_STATUS = {
119
+ AUTHENTICATION_ERROR: 401,
120
+ AUTHORIZATION_ERROR: 403,
121
+ RATE_LIMIT_ERROR: 429,
122
+ FORBIDDEN: 403,
123
+ NOT_FOUND: 404,
124
+ VALIDATION_ERROR: 400
125
+ };
126
+ function getErrorMessage(err) {
127
+ return err instanceof Error ? err.message : String(err);
128
+ }
129
+
34
130
  // src/db/queries/workflows.ts
131
+ function bumpPatch(version) {
132
+ const parts = version.split(".");
133
+ if (parts.length !== 3)
134
+ return "1.0.1";
135
+ const [major, minor, patch] = parts.map((p) => Number.parseInt(p, 10));
136
+ if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch) || major === undefined || minor === undefined || patch === undefined) {
137
+ return "1.0.1";
138
+ }
139
+ return `${major}.${minor}.${patch + 1}`;
140
+ }
35
141
  async function listWorkflowDefinitions(db, apiKeyIds) {
36
142
  let query = db.selectFrom("workflow_definitions").selectAll().where("status", "!=", "deleted").orderBy("created_at", "desc");
37
143
  if (apiKeyIds?.length) {
@@ -47,26 +153,36 @@ async function getWorkflowDefinition(db, name, apiKeyIds) {
47
153
  return await query.executeTakeFirst() ?? null;
48
154
  }
49
155
  async function upsertWorkflowDefinition(db, data) {
156
+ const existing = await db.selectFrom("workflow_definitions").selectAll().where("name", "=", data.name).where("api_key_id", "=", data.apiKeyId).executeTakeFirst();
157
+ if (existing) {
158
+ if (data.expectedVersion !== undefined && existing.version !== data.expectedVersion) {
159
+ throw new VersionConflictError(existing.version, data.expectedVersion);
160
+ }
161
+ const nextVersion = bumpPatch(existing.version);
162
+ return await db.updateTable("workflow_definitions").set({
163
+ trigger_type: data.triggerType,
164
+ trigger_config: jsonb(data.triggerConfig),
165
+ handler_path: data.handlerPath,
166
+ source_code: data.sourceCode ?? existing.source_code,
167
+ retries_config: data.retriesConfig ? jsonb(data.retriesConfig) : null,
168
+ timeout_ms: data.timeoutMs ?? null,
169
+ version: nextVersion,
170
+ status: "active",
171
+ updated_at: new Date
172
+ }).where("id", "=", existing.id).returningAll().executeTakeFirstOrThrow();
173
+ }
50
174
  return await db.insertInto("workflow_definitions").values({
51
175
  name: data.name,
52
176
  trigger_type: data.triggerType,
53
177
  trigger_config: jsonb(data.triggerConfig),
54
178
  handler_path: data.handlerPath,
179
+ source_code: data.sourceCode ?? null,
55
180
  api_key_id: data.apiKeyId,
56
181
  project_id: data.projectId ?? null,
57
182
  retries_config: data.retriesConfig ? jsonb(data.retriesConfig) : null,
58
183
  timeout_ms: data.timeoutMs ?? null,
59
- version: data.version ?? "1.0.0"
60
- }).onConflict((oc) => oc.columns(["name", "api_key_id"]).doUpdateSet({
61
- trigger_type: data.triggerType,
62
- trigger_config: jsonb(data.triggerConfig),
63
- handler_path: data.handlerPath,
64
- retries_config: data.retriesConfig ? jsonb(data.retriesConfig) : null,
65
- timeout_ms: data.timeoutMs ?? null,
66
- version: data.version ?? "1.0.0",
67
- status: "active",
68
- updated_at: new Date
69
- })).returningAll().executeTakeFirstOrThrow();
184
+ version: "1.0.0"
185
+ }).returningAll().executeTakeFirstOrThrow();
70
186
  }
71
187
  async function updateWorkflowStatus(db, name, apiKeyId, status) {
72
188
  await db.updateTable("workflow_definitions").set({ status, updated_at: new Date }).where("name", "=", name).where("api_key_id", "=", apiKeyId).execute();
@@ -108,8 +224,9 @@ export {
108
224
  getWorkflowRun,
109
225
  getWorkflowDefinition,
110
226
  deleteWorkflowDefinition,
111
- createWorkflowRun
227
+ createWorkflowRun,
228
+ bumpPatch
112
229
  };
113
230
 
114
- //# debugId=EDBC38F3000DD9C664756E2164756E21
231
+ //# debugId=A3CE9B539936424A64756E2164756E21
115
232
  //# sourceMappingURL=workflows.js.map