@secondlayer/shared 5.2.1 → 6.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 (42) hide show
  1. package/dist/src/db/index.d.ts +45 -1
  2. package/dist/src/db/queries/account-spend-caps.d.ts +44 -0
  3. package/dist/src/db/queries/account-usage.d.ts +44 -0
  4. package/dist/src/db/queries/account-usage.js +4 -29
  5. package/dist/src/db/queries/account-usage.js.map +3 -3
  6. package/dist/src/db/queries/accounts.d.ts +44 -0
  7. package/dist/src/db/queries/chain-reorgs.d.ts +532 -0
  8. package/dist/src/db/queries/chain-reorgs.js +322 -0
  9. package/dist/src/db/queries/chain-reorgs.js.map +14 -0
  10. package/dist/src/db/queries/integrity.d.ts +44 -0
  11. package/dist/src/db/queries/projects.d.ts +44 -0
  12. package/dist/src/db/queries/provisioning-audit.d.ts +44 -0
  13. package/dist/src/db/queries/subgraph-gaps.d.ts +44 -0
  14. package/dist/src/db/queries/subgraph-operations.d.ts +44 -0
  15. package/dist/src/db/queries/subgraphs.d.ts +44 -0
  16. package/dist/src/db/queries/subscriptions.d.ts +44 -0
  17. package/dist/src/db/queries/tenant-compute-addons.d.ts +44 -0
  18. package/dist/src/db/queries/tenants.d.ts +45 -7
  19. package/dist/src/db/queries/tenants.js +1 -5
  20. package/dist/src/db/queries/tenants.js.map +3 -3
  21. package/dist/src/db/queries/usage.d.ts +47 -1
  22. package/dist/src/db/queries/usage.js +20 -1
  23. package/dist/src/db/queries/usage.js.map +3 -3
  24. package/dist/src/db/schema.d.ts +45 -1
  25. package/dist/src/index.d.ts +47 -3
  26. package/dist/src/index.js.map +1 -1
  27. package/dist/src/node/local-client.d.ts +44 -0
  28. package/dist/src/pricing.d.ts +6 -5
  29. package/dist/src/pricing.js +4 -29
  30. package/dist/src/pricing.js.map +3 -3
  31. package/dist/src/schemas/index.d.ts +2 -2
  32. package/dist/src/schemas/index.js.map +1 -1
  33. package/dist/src/schemas/subgraphs.d.ts +2 -2
  34. package/dist/src/schemas/subgraphs.js.map +1 -1
  35. package/dist/src/subgraphs/spec.d.ts +2 -2
  36. package/dist/src/types.d.ts +2 -1
  37. package/migrations/0064_remove_hobby_plan.ts +28 -0
  38. package/migrations/0065_l2_decoded_events.ts +50 -0
  39. package/migrations/0066_public_l2_decoded_events.ts +83 -0
  40. package/migrations/0067_product_usage_counters.ts +18 -0
  41. package/migrations/0068_chain_reorgs_and_burn_block_hash.ts +48 -0
  42. package/package.json +5 -1
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/db/queries/usage.ts"],
4
4
  "sourcesContent": [
5
- "import { type Kysely, sql } from \"kysely\";\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 sql`\n\t\tINSERT INTO usage_daily (account_id, tenant_id, date, api_requests, deliveries)\n\t\tVALUES (${accountId}, NULL, ${today}, 1, 0)\n\t\tON CONFLICT (account_id, date) WHERE tenant_id IS NULL\n\t\tDO UPDATE SET api_requests = usage_daily.api_requests + 1\n\t`.execute(db);\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\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.select([\"account_id\", \"schema_name\"])\n\t\t.where(\"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\tconst row = result.rows[0] as { size?: string } | undefined;\n\t\t\t\ttotalBytes += Number(row?.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
+ "import { type Kysely, sql } from \"kysely\";\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 sql`\n\t\tINSERT INTO usage_daily (account_id, tenant_id, date, api_requests, deliveries)\n\t\tVALUES (${accountId}, NULL, ${today}, 1, 0)\n\t\tON CONFLICT (account_id, date) WHERE tenant_id IS NULL\n\t\tDO UPDATE SET api_requests = usage_daily.api_requests + 1\n\t`.execute(db);\n}\n\nasync function incrementAccountDailyCounter(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tcolumn: \"streams_events_returned\" | \"index_decoded_events_returned\",\n\tquantity: number,\n): Promise<void> {\n\tif (quantity <= 0) return;\n\tconst today = new Date().toISOString().slice(0, 10);\n\tawait sql`\n\t\tINSERT INTO usage_daily (account_id, tenant_id, date, api_requests, deliveries, ${sql.raw(column)})\n\t\tVALUES (${accountId}, NULL, ${today}, 0, 0, ${quantity})\n\t\tON CONFLICT (account_id, date) WHERE tenant_id IS NULL\n\t\tDO UPDATE SET ${sql.raw(column)} = usage_daily.${sql.raw(column)} + ${quantity}\n\t`.execute(db);\n}\n\nexport async function incrementStreamsEventsReturned(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tquantity: number,\n): Promise<void> {\n\tawait incrementAccountDailyCounter(\n\t\tdb,\n\t\taccountId,\n\t\t\"streams_events_returned\",\n\t\tquantity,\n\t);\n}\n\nexport async function incrementIndexDecodedEventsReturned(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tquantity: number,\n): Promise<void> {\n\tawait incrementAccountDailyCounter(\n\t\tdb,\n\t\taccountId,\n\t\t\"index_decoded_events_returned\",\n\t\tquantity,\n\t);\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\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.select([\"account_id\", \"schema_name\"])\n\t\t.where(\"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\tconst row = result.rows[0] as { size?: string } | undefined;\n\t\t\t\ttotalBytes += Number(row?.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"
6
6
  ],
7
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAIA,eAAsB,oBAAoB,CACzC,IACA,WACgB;AAAA,EAChB,MAAM,QAAQ,IAAI,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EAClD,MAAM;AAAA;AAAA,YAEK,oBAAoB;AAAA;AAAA;AAAA,GAG7B,QAAQ,EAAE;AAAA;AAUb,eAAsB,QAAQ,CAC7B,IACA,WACwB;AAAA,EACxB,MAAM,QAAQ,IAAI,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EAClD,MAAM,aAAa,GAAG,MAAM,MAAM,GAAG,CAAC;AAAA,EAGtC,MAAM,WAAW,MAAM,GACrB,WAAW,aAAa,EACxB,OAAO,cAAc,EACrB,MAAM,cAAc,KAAK,SAAS,EAClC,MAAM,QAAQ,KAAK,KAAK,EACxB,iBAAiB;AAAA,EAGnB,MAAM,aAAa,MAAM,GACvB,WAAW,aAAa,EACxB,OAAO,kCAA0C,GAAG,OAAO,CAAC,EAC5D,MAAM,cAAc,KAAK,SAAS,EAClC,MAAM,QAAQ,MAAM,UAAU,EAC9B,iBAAiB;AAAA,EAGnB,MAAM,aAAa,MAAM,GACvB,WAAW,iBAAiB,EAC5B,OAAO,eAAe,EACtB,MAAM,cAAc,KAAK,SAAS,EAClC,QAAQ,eAAe,MAAM,EAC7B,MAAM,CAAC,EACP,iBAAiB;AAAA,EAEnB,OAAO;AAAA,IACN,kBAAkB,UAAU,gBAAgB;AAAA,IAC5C,qBAAqB,OAAO,YAAY,SAAS,CAAC;AAAA,IAClD,cAAc,OAAO,YAAY,iBAAiB,CAAC;AAAA,EACpD;AAAA;AAOD,eAAsB,cAAc,CAAC,IAAqC;AAAA,EAEzE,MAAM,mBAAmB,MAAM,GAC7B,WAAW,WAAW,EACtB,OAAO,CAAC,cAAc,aAAa,CAAC,EACpC,MAAM,eAAe,UAAU,IAAI,EACnC,QAAQ;AAAA,EAGV,MAAM,YAAY,IAAI;AAAA,EACtB,WAAW,OAAO,kBAAkB;AAAA,IACnC,MAAM,UAAU,UAAU,IAAI,IAAI,UAAU,KAAK,CAAC;AAAA,IAClD,IAAI,IAAI;AAAA,MAAa,QAAQ,KAAK,IAAI,WAAW;AAAA,IACjD,UAAU,IAAI,IAAI,YAAY,OAAO;AAAA,EACtC;AAAA,EAEA,YAAY,WAAW,YAAY,WAAW;AAAA,IAC7C,IAAI,aAAa;AAAA,IACjB,WAAW,UAAU,SAAS;AAAA,MAC7B,IAAI;AAAA,QACH,MAAM,SAAS,MAAM;AAAA;AAAA,8CAEqB;AAAA,UACpC,QAAQ,EAAE;AAAA,QAChB,MAAM,MAAM,OAAO,KAAK;AAAA,QACxB,cAAc,OAAO,KAAK,QAAQ,CAAC;AAAA,QAClC,MAAM;AAAA,IAGT;AAAA,IAEA,MAAM,GACJ,WAAW,iBAAiB,EAC5B,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,eAAe;AAAA,IAChB,CAAC,EACA,QAAQ;AAAA,EACX;AAAA;",
8
- "debugId": "D8C08A844119816364756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "0A5108029A948ED964756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -4,6 +4,7 @@ interface BlocksTable {
4
4
  hash: string;
5
5
  parent_hash: string;
6
6
  burn_block_height: number;
7
+ burn_block_hash: ColumnType<string | null, string | null | undefined, string | null>;
7
8
  timestamp: number;
8
9
  canonical: Generated<boolean>;
9
10
  created_at: Generated<Date>;
@@ -144,6 +145,8 @@ interface UsageDailyTable {
144
145
  date: string;
145
146
  api_requests: Generated<number>;
146
147
  deliveries: Generated<number>;
148
+ streams_events_returned: Generated<number>;
149
+ index_decoded_events_returned: Generated<number>;
147
150
  }
148
151
  interface UsageSnapshotsTable {
149
152
  id: Generated<string>;
@@ -272,6 +275,44 @@ interface ProcessedStripeEventsTable {
272
275
  event_type: string;
273
276
  processed_at: Generated<Date>;
274
277
  }
278
+ interface DecodedEventsTable {
279
+ cursor: string;
280
+ block_height: number;
281
+ tx_id: string;
282
+ tx_index: number;
283
+ event_index: number;
284
+ event_type: string;
285
+ microblock_hash: string | null;
286
+ canonical: Generated<boolean>;
287
+ contract_id: string | null;
288
+ sender: string | null;
289
+ recipient: string | null;
290
+ amount: string | null;
291
+ asset_identifier: string | null;
292
+ value: string | null;
293
+ memo: string | null;
294
+ source_cursor: string;
295
+ created_at: Generated<Date>;
296
+ }
297
+ interface L2DecoderCheckpointsTable {
298
+ decoder_name: string;
299
+ last_cursor: string | null;
300
+ updated_at: Generated<Date>;
301
+ }
302
+ interface ChainReorgsTable {
303
+ id: Generated<string>;
304
+ detected_at: Generated<Date>;
305
+ fork_point_height: number;
306
+ old_index_block_hash: string | null;
307
+ new_index_block_hash: string | null;
308
+ orphaned_from_height: number;
309
+ orphaned_from_event_index: number;
310
+ orphaned_to_height: number;
311
+ orphaned_to_event_index: number;
312
+ new_canonical_height: number;
313
+ new_canonical_event_index: number;
314
+ created_at: Generated<Date>;
315
+ }
275
316
  interface Database {
276
317
  blocks: BlocksTable;
277
318
  transactions: TransactionsTable;
@@ -307,6 +348,9 @@ interface Database {
307
348
  subscriptions: SubscriptionsTable;
308
349
  subscription_outbox: SubscriptionOutboxTable;
309
350
  subscription_deliveries: SubscriptionDeliveriesTable;
351
+ decoded_events: DecodedEventsTable;
352
+ l2_decoder_checkpoints: L2DecoderCheckpointsTable;
353
+ chain_reorgs: ChainReorgsTable;
310
354
  }
311
355
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
312
356
  interface TenantsTable {
@@ -518,4 +562,4 @@ interface SubscriptionDeliveriesTable {
518
562
  }
519
563
  type SubscriptionDelivery = Selectable<SubscriptionDeliveriesTable>;
520
564
  type InsertSubscriptionDelivery = Insertable<SubscriptionDeliveriesTable>;
521
- export { WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, 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, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, ProcessedStripeEventsTable, OutboxStatus, MagicLinksTable, MagicLink, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, 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 };
565
+ export { WaitlistTable, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, 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, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGap, Subgraph, SessionsTable, Session, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, ProcessedStripeEventsTable, OutboxStatus, MagicLinksTable, MagicLink, L2DecoderCheckpointsTable, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, EventsTable, Event, DecodedEventsTable, Database, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, ChainReorgsTable, BlocksTable, Block, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
@@ -4,6 +4,7 @@ interface BlocksTable {
4
4
  hash: string;
5
5
  parent_hash: string;
6
6
  burn_block_height: number;
7
+ burn_block_hash: ColumnType<string | null, string | null | undefined, string | null>;
7
8
  timestamp: number;
8
9
  canonical: Generated<boolean>;
9
10
  created_at: Generated<Date>;
@@ -144,6 +145,8 @@ interface UsageDailyTable {
144
145
  date: string;
145
146
  api_requests: Generated<number>;
146
147
  deliveries: Generated<number>;
148
+ streams_events_returned: Generated<number>;
149
+ index_decoded_events_returned: Generated<number>;
147
150
  }
148
151
  interface UsageSnapshotsTable {
149
152
  id: Generated<string>;
@@ -272,6 +275,44 @@ interface ProcessedStripeEventsTable {
272
275
  event_type: string;
273
276
  processed_at: Generated<Date>;
274
277
  }
278
+ interface DecodedEventsTable {
279
+ cursor: string;
280
+ block_height: number;
281
+ tx_id: string;
282
+ tx_index: number;
283
+ event_index: number;
284
+ event_type: string;
285
+ microblock_hash: string | null;
286
+ canonical: Generated<boolean>;
287
+ contract_id: string | null;
288
+ sender: string | null;
289
+ recipient: string | null;
290
+ amount: string | null;
291
+ asset_identifier: string | null;
292
+ value: string | null;
293
+ memo: string | null;
294
+ source_cursor: string;
295
+ created_at: Generated<Date>;
296
+ }
297
+ interface L2DecoderCheckpointsTable {
298
+ decoder_name: string;
299
+ last_cursor: string | null;
300
+ updated_at: Generated<Date>;
301
+ }
302
+ interface ChainReorgsTable {
303
+ id: Generated<string>;
304
+ detected_at: Generated<Date>;
305
+ fork_point_height: number;
306
+ old_index_block_hash: string | null;
307
+ new_index_block_hash: string | null;
308
+ orphaned_from_height: number;
309
+ orphaned_from_event_index: number;
310
+ orphaned_to_height: number;
311
+ orphaned_to_event_index: number;
312
+ new_canonical_height: number;
313
+ new_canonical_event_index: number;
314
+ created_at: Generated<Date>;
315
+ }
275
316
  interface Database {
276
317
  blocks: BlocksTable;
277
318
  transactions: TransactionsTable;
@@ -307,6 +348,9 @@ interface Database {
307
348
  subscriptions: SubscriptionsTable;
308
349
  subscription_outbox: SubscriptionOutboxTable;
309
350
  subscription_deliveries: SubscriptionDeliveriesTable;
351
+ decoded_events: DecodedEventsTable;
352
+ l2_decoder_checkpoints: L2DecoderCheckpointsTable;
353
+ chain_reorgs: ChainReorgsTable;
310
354
  }
311
355
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
312
356
  interface TenantsTable {
@@ -846,9 +890,9 @@ interface SubgraphSyncInfo {
846
890
  integrity: "complete" | "gaps_detected";
847
891
  }
848
892
  interface SubgraphResourceWarning {
849
- code: "HOBBY_LARGE_REINDEX";
893
+ code: string;
850
894
  message: string;
851
- plan: "hobby";
895
+ plan?: string;
852
896
  blockRange: number;
853
897
  processorMemoryMb: number;
854
898
  recommendedPlan: "launch";
@@ -1128,4 +1172,4 @@ declare function createSignatureHeader(payload: string, secret: string, timestam
1128
1172
  * Returns true if valid, false otherwise
1129
1173
  */
1130
1174
  declare function verifySignatureHeader(payload: string, header: string, secret: string, toleranceSeconds?: number): boolean;
1131
- export { validateSubscriptionFilterForTable, sql, parseJsonb, logger, jsonb, getTargetDb, getSourceDb, getRawClient, getErrorMessage, getEnv, getDb, generateSubgraphSpec, generateSubgraphOpenApi, generateSubgraphMarkdown, generateSubgraphAgentSchema, formatSubscriptionSchemaErrors, exports_hmac as crypto, closeDb, WaitlistTable, VersionConflictError, ValidationError, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionRequestSchema, UpdateSubscriptionRequest, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, UpdateSubgraph, UpdateProject, UpdateProfileRequestSchema, UpdateProfileRequest, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, UpdateAccountSpendCap, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantSuspendedError, TenantStatus, TenantComputeAddonsTable, TenantComputeAddon, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubscriptionsTable, SubscriptionSummary, SubscriptionStatusSchema, SubscriptionStatus, SubscriptionSchemaTables, SubscriptionSchemaTable, SubscriptionSchemaColumn, SubscriptionRuntimeSchema, SubscriptionRuntime, SubscriptionOutboxTable, SubscriptionOutbox, SubscriptionFormatSchema, SubscriptionFormat, SubscriptionFilterSchema, SubscriptionFilterPrimitiveSchema, SubscriptionFilterPrimitive, SubscriptionFilterOperatorSchema, SubscriptionFilterOperator, SubscriptionFilterClauseSchema, SubscriptionFilterClause, SubscriptionFilter, SubscriptionDetail, SubscriptionDelivery, SubscriptionDeliveriesTable, Subscription, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphSyncInfo, SubgraphSummary, SubgraphSpecOptions, SubgraphSpecFormat, SubgraphResourceWarning, SubgraphQueryParams, SubgraphProcessingStatsTable, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGapsResponse, SubgraphGapRange, SubgraphGapEntry, SubgraphGap, SubgraphDetail, SubgraphAgentSchema, Subgraph, StxTransferFilterSchema, StxTransferFilter, StxMintFilterSchema, StxMintFilter, StxLockFilterSchema, StxLockFilter, StxBurnFilterSchema, StxBurnFilter, SessionsTable, Session, SecondLayerError, SUBSCRIPTION_STATUSES, SUBSCRIPTION_RUNTIMES, SUBSCRIPTION_FORMATS, SUBSCRIPTION_FILTER_OPERATORS, RotateSecretResponse, ReplaySubscriptionRequestSchema, ReplaySubscriptionRequest, ReplayResult, ReindexResponse, RateLimitError, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, ProcessedStripeEventsTable, PrintEventFilterSchema, PrintEventFilter, ParsedUpdateSubscriptionRequest, ParsedReplaySubscriptionRequest, ParsedCreateSubscriptionRequest, OutboxStatus, NotFoundError, NftTransferFilterSchema, NftTransferFilter, NftMintFilterSchema, NftMintFilter, NftBurnFilterSchema, NftBurnFilter, MagicLinksTable, MagicLink, KeyRotatedError, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, FtTransferFilterSchema, FtTransferFilter, FtMintFilterSchema, FtMintFilter, FtBurnFilterSchema, FtBurnFilter, ForbiddenError, EventsTable, EventFilterSchema, EventFilter, Event, ErrorCodes, ErrorCode, Env, DeploySubgraphResponse, DeploySubgraphRequestSchema, DeploySubgraphRequest, DeliveryRow, DeadRow, DatabaseError, Database, CreateSubscriptionResponse, CreateSubscriptionRequestSchema, CreateSubscriptionRequest, ContractDeployFilterSchema, ContractDeployFilter, ContractCallFilterSchema, ContractCallFilter, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, CODE_TO_STATUS, BlocksTable, Block, AuthorizationError, AuthenticationError, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
1175
+ export { validateSubscriptionFilterForTable, sql, parseJsonb, logger, jsonb, getTargetDb, getSourceDb, getRawClient, getErrorMessage, getEnv, getDb, generateSubgraphSpec, generateSubgraphOpenApi, generateSubgraphMarkdown, generateSubgraphAgentSchema, formatSubscriptionSchemaErrors, exports_hmac as crypto, closeDb, WaitlistTable, VersionConflictError, ValidationError, UsageSnapshotsTable, UsageSnapshot, UsageDailyTable, UsageDaily, UpdateTransaction, UpdateTenantUsageMonthly, UpdateTenantComputeAddon, UpdateTenant, UpdateSubscriptionRequestSchema, UpdateSubscriptionRequest, UpdateSubscriptionOutbox, UpdateSubscription, UpdateSubgraphOperation, UpdateSubgraph, UpdateProject, UpdateProfileRequestSchema, UpdateProfileRequest, UpdateIndexProgress, UpdateEvent, UpdateChatSession, UpdateBlock, UpdateApiKey, UpdateAccountSpendCap, TransactionsTable, Transaction, TenantsTable, TenantUsageMonthlyTable, TenantUsageMonthly, TenantSuspendedError, TenantStatus, TenantComputeAddonsTable, TenantComputeAddon, Tenant, TeamMembersTable, TeamMember, TeamInvitationsTable, TeamInvitation, SubscriptionsTable, SubscriptionSummary, SubscriptionStatusSchema, SubscriptionStatus, SubscriptionSchemaTables, SubscriptionSchemaTable, SubscriptionSchemaColumn, SubscriptionRuntimeSchema, SubscriptionRuntime, SubscriptionOutboxTable, SubscriptionOutbox, SubscriptionFormatSchema, SubscriptionFormat, SubscriptionFilterSchema, SubscriptionFilterPrimitiveSchema, SubscriptionFilterPrimitive, SubscriptionFilterOperatorSchema, SubscriptionFilterOperator, SubscriptionFilterClauseSchema, SubscriptionFilterClause, SubscriptionFilter, SubscriptionDetail, SubscriptionDelivery, SubscriptionDeliveriesTable, Subscription, SubgraphsTable, SubgraphUsageDailyTable, SubgraphUsageDaily, SubgraphTableSnapshotsTable, SubgraphSyncInfo, SubgraphSummary, SubgraphSpecOptions, SubgraphSpecFormat, SubgraphResourceWarning, SubgraphQueryParams, SubgraphProcessingStatsTable, SubgraphOperationsTable, SubgraphOperationStatus, SubgraphOperationKind, SubgraphOperation, SubgraphHealthSnapshotsTable, SubgraphHealthSnapshot, SubgraphGapsTable, SubgraphGapsResponse, SubgraphGapRange, SubgraphGapEntry, SubgraphGap, SubgraphDetail, SubgraphAgentSchema, Subgraph, StxTransferFilterSchema, StxTransferFilter, StxMintFilterSchema, StxMintFilter, StxLockFilterSchema, StxLockFilter, StxBurnFilterSchema, StxBurnFilter, SessionsTable, Session, SecondLayerError, SUBSCRIPTION_STATUSES, SUBSCRIPTION_RUNTIMES, SUBSCRIPTION_FORMATS, SUBSCRIPTION_FILTER_OPERATORS, RotateSecretResponse, ReplaySubscriptionRequestSchema, ReplaySubscriptionRequest, ReplayResult, ReindexResponse, RateLimitError, ProvisioningAuditStatus, ProvisioningAuditLogTable, ProvisioningAuditLog, ProvisioningAuditEvent, ProjectsTable, Project, ProcessedStripeEventsTable, PrintEventFilterSchema, PrintEventFilter, ParsedUpdateSubscriptionRequest, ParsedReplaySubscriptionRequest, ParsedCreateSubscriptionRequest, OutboxStatus, NotFoundError, NftTransferFilterSchema, NftTransferFilter, NftMintFilterSchema, NftMintFilter, NftBurnFilterSchema, NftBurnFilter, MagicLinksTable, MagicLink, L2DecoderCheckpointsTable, KeyRotatedError, InsertTransaction, InsertTenantUsageMonthly, InsertTenantComputeAddon, InsertTenant, InsertTeamMember, InsertTeamInvitation, InsertSubscriptionOutbox, InsertSubscriptionDelivery, InsertSubscription, InsertSubgraphUsageDaily, InsertSubgraphOperation, InsertSubgraphHealthSnapshot, InsertSubgraphGap, InsertSubgraph, InsertSession, InsertProvisioningAuditLog, InsertProject, InsertMagicLink, InsertIndexProgress, InsertEvent, InsertChatSession, InsertChatMessage, InsertBlock, InsertApiKey, InsertAccountSpendCap, InsertAccountInsight, InsertAccountAgentRun, InsertAccount, IndexProgressTable, IndexProgress, FtTransferFilterSchema, FtTransferFilter, FtMintFilterSchema, FtMintFilter, FtBurnFilterSchema, FtBurnFilter, ForbiddenError, EventsTable, EventFilterSchema, EventFilter, Event, ErrorCodes, ErrorCode, Env, DeploySubgraphResponse, DeploySubgraphRequestSchema, DeploySubgraphRequest, DeliveryRow, DecodedEventsTable, DeadRow, DatabaseError, Database, CreateSubscriptionResponse, CreateSubscriptionRequestSchema, CreateSubscriptionRequest, ContractDeployFilterSchema, ContractDeployFilter, ContractCallFilterSchema, ContractCallFilter, ChatSessionsTable, ChatSession, ChatMessagesTable, ChatMessage, ChainReorgsTable, CODE_TO_STATUS, BlocksTable, Block, AuthorizationError, AuthenticationError, ApiKeysTable, ApiKey, AccountsTable, AccountSpendCapsTable, AccountSpendCap, AccountInsightsTable, AccountInsight, AccountAgentRunsTable, AccountAgentRun, Account };
@@ -9,7 +9,7 @@
9
9
  "export const ErrorCodes = {\n\tVALIDATION_ERROR: \"VALIDATION_ERROR\",\n\tDATABASE_ERROR: \"DATABASE_ERROR\",\n\tAUTHENTICATION_ERROR: \"AUTHENTICATION_ERROR\",\n\tAUTHORIZATION_ERROR: \"AUTHORIZATION_ERROR\",\n\tRATE_LIMIT_ERROR: \"RATE_LIMIT_ERROR\",\n\tFORBIDDEN: \"FORBIDDEN\",\n\tVERSION_CONFLICT: \"VERSION_CONFLICT\",\n\tNOT_FOUND: \"NOT_FOUND\",\n\t// Tenant lifecycle (CLI surfaces these verbatim)\n\tKEY_ROTATED: \"KEY_ROTATED\",\n\tTENANT_SUSPENDED: \"TENANT_SUSPENDED\",\n\tNO_TENANT_FOR_PROJECT: \"NO_TENANT_FOR_PROJECT\",\n\tINSTANCE_EXISTS: \"INSTANCE_EXISTS\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\n/** Base error class for all Secondlayer errors. */\nexport class SecondLayerError extends Error {\n\tpublic code: ErrorCode;\n\tpublic override cause?: unknown;\n\n\tconstructor(code: ErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t\tthis.name = this.constructor.name;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): {\n\t\tname: string;\n\t\tcode: string;\n\t\tmessage: string;\n\t\tstack: string | undefined;\n\t\tcause: unknown;\n\t} {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tcode: this.code,\n\t\t\tmessage: this.message,\n\t\t\tstack: this.stack,\n\t\t\tcause: this.cause,\n\t\t};\n\t}\n}\n\nexport class NotFoundError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"NOT_FOUND\", message);\n\t}\n}\n\nexport class ValidationError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"VALIDATION_ERROR\", message, cause);\n\t}\n}\n\nexport class DatabaseError extends SecondLayerError {\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(\"DATABASE_ERROR\", message, cause);\n\t}\n}\n\nexport class AuthenticationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHENTICATION_ERROR\", message);\n\t}\n}\n\nexport class AuthorizationError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"AUTHORIZATION_ERROR\", message);\n\t}\n}\n\nexport class RateLimitError extends SecondLayerError {\n\tconstructor(message: string) {\n\t\tsuper(\"RATE_LIMIT_ERROR\", message);\n\t}\n}\n\nexport class ForbiddenError extends SecondLayerError {\n\tconstructor(message = \"Forbidden\") {\n\t\tsuper(\"FORBIDDEN\", message);\n\t}\n}\n\nexport class VersionConflictError extends SecondLayerError {\n\tpublic currentVersion: string;\n\tpublic expectedVersion: string;\n\n\tconstructor(currentVersion: string, expectedVersion: string) {\n\t\tsuper(\n\t\t\t\"VERSION_CONFLICT\",\n\t\t\t`Version conflict: expected ${expectedVersion}, current ${currentVersion}`,\n\t\t);\n\t\tthis.currentVersion = currentVersion;\n\t\tthis.expectedVersion = expectedVersion;\n\t}\n}\n\nexport class KeyRotatedError extends SecondLayerError {\n\tconstructor(message = \"Token has been rotated\") {\n\t\tsuper(\"KEY_ROTATED\", message);\n\t}\n}\n\nexport class TenantSuspendedError extends SecondLayerError {\n\tconstructor(message = \"Instance is suspended\") {\n\t\tsuper(\"TENANT_SUSPENDED\", message);\n\t}\n}\n\n/** Error code → HTTP status. Used by API middleware for code-based matching\n * (avoids cross-bundle instanceof failures from bunup class duplication). */\n// String literal map — codes don't have to be in the central ErrorCode\n// enum (route-local error classes can supply any code; we just map the\n// HTTP status here). This keeps cross-bundle instanceof failures out of\n// the equation.\nexport const CODE_TO_STATUS: Record<\n\tstring,\n\t400 | 401 | 403 | 404 | 409 | 423 | 429\n> = {\n\tAUTHENTICATION_ERROR: 401,\n\tAUTHORIZATION_ERROR: 403,\n\tRATE_LIMIT_ERROR: 429,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tVALIDATION_ERROR: 400,\n\tKEY_ROTATED: 401,\n\tTENANT_SUSPENDED: 423,\n\tNO_TENANT_FOR_PROJECT: 404,\n\tINSTANCE_EXISTS: 409,\n\tSUBGRAPH_NOT_FOUND: 404,\n} as const;\n\nexport function getErrorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n",
10
10
  "import { z } from \"zod/v4\";\n\n/**\n * Account profile shapes. Unrelated to marketplace — previously lived in\n * schemas/marketplace.ts alongside public-directory types. Kept here now\n * that marketplace is gone so the profile fields (display_name, bio, slug)\n * have a stable home.\n */\n\nexport interface UpdateProfileRequest {\n\tdisplay_name?: string;\n\tbio?: string;\n\tslug?: string;\n}\n\nexport const UpdateProfileRequestSchema: z.ZodType<UpdateProfileRequest> =\n\tz.object({\n\t\tdisplay_name: z.string().max(50).optional(),\n\t\tbio: z.string().max(300).optional(),\n\t\tslug: z\n\t\t\t.string()\n\t\t\t.regex(/^[a-z0-9-]+$/, \"lowercase alphanumeric + hyphens only\")\n\t\t\t.min(3)\n\t\t\t.max(30)\n\t\t\t.optional(),\n\t});\n",
11
11
  "import { isValidAddress as _isValidAddress } from \"@secondlayer/stacks\";\nimport { z } from \"zod/v4\";\n\nconst isValidAddress = _isValidAddress as (addr: string) => boolean;\n\n/** Validate a Stacks principal (standard or contract, e.g. SP2J...ABC or SP2J...ABC.contract-name) */\nconst stacksPrincipal = z.string().refine((val) => {\n\tconst parts = val.split(\".\");\n\tif (parts.length > 2) return false;\n\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\treturn isValidAddress(parts[0]!);\n}, \"Invalid Stacks principal address\");\n\n// Base filter with common fields\nconst baseFilter = {\n\t// Optional: filter by sender\n\tsender: stacksPrincipal.optional(),\n\t// Optional: filter by recipient\n\trecipient: stacksPrincipal.optional(),\n};\n\n// Type exports — defined first so they can annotate schemas\nexport interface StxTransferFilter {\n\ttype: \"stx_transfer\";\n\tsender?: string;\n\trecipient?: string;\n\tminAmount?: number;\n\tmaxAmount?: number;\n}\n\nexport interface StxMintFilter {\n\ttype: \"stx_mint\";\n\trecipient?: string;\n\tminAmount?: number;\n}\n\nexport interface StxBurnFilter {\n\ttype: \"stx_burn\";\n\tsender?: string;\n\tminAmount?: number;\n}\n\nexport interface StxLockFilter {\n\ttype: \"stx_lock\";\n\tlockedAddress?: string;\n\tminAmount?: number;\n}\n\nexport interface FtTransferFilter {\n\ttype: \"ft_transfer\";\n\tsender?: string;\n\trecipient?: string;\n\tassetIdentifier?: string;\n\tminAmount?: number;\n}\n\nexport interface FtMintFilter {\n\ttype: \"ft_mint\";\n\trecipient?: string;\n\tassetIdentifier?: string;\n\tminAmount?: number;\n}\n\nexport interface FtBurnFilter {\n\ttype: \"ft_burn\";\n\tsender?: string;\n\tassetIdentifier?: string;\n\tminAmount?: number;\n}\n\nexport interface NftTransferFilter {\n\ttype: \"nft_transfer\";\n\tsender?: string;\n\trecipient?: string;\n\tassetIdentifier?: string;\n\ttokenId?: string;\n}\n\nexport interface NftMintFilter {\n\ttype: \"nft_mint\";\n\trecipient?: string;\n\tassetIdentifier?: string;\n\ttokenId?: string;\n}\n\nexport interface NftBurnFilter {\n\ttype: \"nft_burn\";\n\tsender?: string;\n\tassetIdentifier?: string;\n\ttokenId?: string;\n}\n\nexport interface ContractCallFilter {\n\ttype: \"contract_call\";\n\tcontractId?: string;\n\tfunctionName?: string;\n\tcaller?: string;\n}\n\nexport interface ContractDeployFilter {\n\ttype: \"contract_deploy\";\n\tdeployer?: string;\n\tcontractName?: string;\n}\n\nexport interface PrintEventFilter {\n\ttype: \"print_event\";\n\tcontractId?: string;\n\ttopic?: string;\n\tcontains?: string;\n}\n\nexport type EventFilter =\n\t| StxTransferFilter\n\t| StxMintFilter\n\t| StxBurnFilter\n\t| StxLockFilter\n\t| FtTransferFilter\n\t| FtMintFilter\n\t| FtBurnFilter\n\t| NftTransferFilter\n\t| NftMintFilter\n\t| NftBurnFilter\n\t| ContractCallFilter\n\t| ContractDeployFilter\n\t| PrintEventFilter;\n\n// STX Transfer Filter\nexport const StxTransferFilterSchema: z.ZodType<StxTransferFilter> = z.object({\n\ttype: z.literal(\"stx_transfer\"),\n\t...baseFilter,\n\t// Optional: minimum amount in microSTX\n\tminAmount: z.coerce.number().int().positive().optional(),\n\t// Optional: maximum amount in microSTX\n\tmaxAmount: z.coerce.number().int().positive().optional(),\n});\n\n// STX Mint Filter\nexport const StxMintFilterSchema: z.ZodType<StxMintFilter> = z.object({\n\ttype: z.literal(\"stx_mint\"),\n\trecipient: stacksPrincipal.optional(),\n\tminAmount: z.coerce.number().int().positive().optional(),\n});\n\n// STX Burn Filter\nexport const StxBurnFilterSchema: z.ZodType<StxBurnFilter> = z.object({\n\ttype: z.literal(\"stx_burn\"),\n\tsender: stacksPrincipal.optional(),\n\tminAmount: z.coerce.number().int().positive().optional(),\n});\n\n// STX Lock Filter\nexport const StxLockFilterSchema: z.ZodType<StxLockFilter> = z.object({\n\ttype: z.literal(\"stx_lock\"),\n\tlockedAddress: stacksPrincipal.optional(),\n\tminAmount: z.coerce.number().int().positive().optional(),\n});\n\n// FT Transfer Filter\nexport const FtTransferFilterSchema: z.ZodType<FtTransferFilter> = z.object({\n\ttype: z.literal(\"ft_transfer\"),\n\t...baseFilter,\n\t// Contract that defines the token (e.g., SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-wstx)\n\tassetIdentifier: z.string().optional(),\n\tminAmount: z.coerce.number().int().positive().optional(),\n});\n\n// FT Mint Filter\nexport const FtMintFilterSchema: z.ZodType<FtMintFilter> = z.object({\n\ttype: z.literal(\"ft_mint\"),\n\trecipient: stacksPrincipal.optional(),\n\tassetIdentifier: z.string().optional(),\n\tminAmount: z.coerce.number().int().positive().optional(),\n});\n\n// FT Burn Filter\nexport const FtBurnFilterSchema: z.ZodType<FtBurnFilter> = z.object({\n\ttype: z.literal(\"ft_burn\"),\n\tsender: stacksPrincipal.optional(),\n\tassetIdentifier: z.string().optional(),\n\tminAmount: z.coerce.number().int().positive().optional(),\n});\n\n// NFT Transfer Filter\nexport const NftTransferFilterSchema: z.ZodType<NftTransferFilter> = z.object({\n\ttype: z.literal(\"nft_transfer\"),\n\t...baseFilter,\n\tassetIdentifier: z.string().optional(),\n\t// Optional: filter by specific token ID (Clarity value as hex)\n\ttokenId: z.string().optional(),\n});\n\n// NFT Mint Filter\nexport const NftMintFilterSchema: z.ZodType<NftMintFilter> = z.object({\n\ttype: z.literal(\"nft_mint\"),\n\trecipient: stacksPrincipal.optional(),\n\tassetIdentifier: z.string().optional(),\n\ttokenId: z.string().optional(),\n});\n\n// NFT Burn Filter\nexport const NftBurnFilterSchema: z.ZodType<NftBurnFilter> = z.object({\n\ttype: z.literal(\"nft_burn\"),\n\tsender: stacksPrincipal.optional(),\n\tassetIdentifier: z.string().optional(),\n\ttokenId: z.string().optional(),\n});\n\n// Contract Call Filter\nexport const ContractCallFilterSchema: z.ZodType<ContractCallFilter> = z.object(\n\t{\n\t\ttype: z.literal(\"contract_call\"),\n\t\t// Contract being called\n\t\tcontractId: stacksPrincipal.optional(),\n\t\t// Function name (supports wildcards with *)\n\t\tfunctionName: z.string().optional(),\n\t\t// Caller address\n\t\tcaller: stacksPrincipal.optional(),\n\t},\n);\n\n// Contract Deploy Filter\nexport const ContractDeployFilterSchema: z.ZodType<ContractDeployFilter> =\n\tz.object({\n\t\ttype: z.literal(\"contract_deploy\"),\n\t\t// Deployer address\n\t\tdeployer: stacksPrincipal.optional(),\n\t\t// Contract name pattern (supports wildcards)\n\t\tcontractName: z.string().optional(),\n\t});\n\n// Print Event Filter (smart contract events)\nexport const PrintEventFilterSchema: z.ZodType<PrintEventFilter> = z.object({\n\ttype: z.literal(\"print_event\"),\n\t// Contract emitting the event\n\tcontractId: stacksPrincipal.optional(),\n\t// Topic/name of the event\n\ttopic: z.string().optional(),\n\t// Search for substring in event data\n\tcontains: z.string().optional(),\n});\n\n// Union of all filter types\nexport const EventFilterSchema: z.ZodType<EventFilter> = z.discriminatedUnion(\n\t\"type\",\n\t[\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tStxTransferFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tStxMintFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tStxBurnFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tStxLockFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tFtTransferFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tFtMintFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tFtBurnFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tNftTransferFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tNftMintFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tNftBurnFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tContractCallFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tContractDeployFilterSchema as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tPrintEventFilterSchema as any,\n\t],\n);\n",
12
- "import { z } from \"zod/v4\";\n\n// ── Deploy Subgraph Request ─────────────────────────────────────────────────\n\nexport interface DeploySubgraphRequest {\n\tname: string;\n\tversion?: string;\n\tdescription?: string;\n\tsources: Record<string, Record<string, unknown>>;\n\tschema: Record<string, unknown>;\n\thandlerCode: string;\n\t/** Override the definition's startBlock for this deploy only. */\n\tstartBlock?: number;\n\t/** Original TypeScript source, persisted so chat can read/diff/edit later. */\n\tsourceCode?: string;\n}\n\nexport const DeploySubgraphRequestSchema: z.ZodType<DeploySubgraphRequest> =\n\tz.object({\n\t\tname: z\n\t\t\t.string()\n\t\t\t.regex(/^[a-z0-9-]+$/, \"lowercase alphanumeric + hyphens only\")\n\t\t\t.max(63),\n\t\tversion: z.string().optional(),\n\t\tdescription: z.string().optional(),\n\t\tsources: z\n\t\t\t.record(z.string(), z.record(z.string(), z.unknown()))\n\t\t\t.refine(\n\t\t\t\t(s) => Object.keys(s).length > 0,\n\t\t\t\t\"Must have at least one source\",\n\t\t\t),\n\t\tschema: z.record(z.string(), z.unknown()),\n\t\thandlerCode: z.string().max(1_048_576, \"handler code exceeds 1MB limit\"),\n\t\tstartBlock: z.number().int().nonnegative().optional(),\n\t\tsourceCode: z\n\t\t\t.string()\n\t\t\t.max(1_048_576, \"source code exceeds 1MB limit\")\n\t\t\t.optional(),\n\t});\n\nexport interface DeploySubgraphResponse {\n\taction: \"created\" | \"unchanged\" | \"updated\" | \"reindexed\";\n\tsubgraphId: string;\n\tversion: string;\n\tmessage: string;\n\toperationId?: string;\n\treindexStarted?: boolean;\n\tdiff?: {\n\t\taddedTables: string[];\n\t\tremovedTables: string[];\n\t\taddedColumns: Record<string, string[]>;\n\t\tbreakingChanges: string[];\n\t};\n}\n\n// Subgraph API response types\n\nexport interface SubgraphSummary {\n\tname: string;\n\tversion: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\ttotalProcessed: number;\n\ttotalErrors: number;\n\ttables: string[];\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tprogress: number;\n\tblocksRemaining?: number;\n\tsyncMode?: \"sync\" | \"reindex\";\n\tresourceWarning?: SubgraphResourceWarning;\n\tgapCount: number;\n\tintegrity: \"complete\" | \"gaps_detected\";\n\tcreatedAt: string;\n}\n\nexport interface SubgraphGapRange {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n}\n\nexport interface SubgraphSyncInfo {\n\tstatus: \"synced\" | \"catching_up\" | \"reindexing\" | \"error\";\n\tmode?: \"sync\" | \"reindex\";\n\tstartBlock: number;\n\tlastProcessedBlock: number;\n\t/**\n\t * Backward-compatible denominator for progress displays. During reindexing,\n\t * this is the reindex target block rather than the live source chain tip.\n\t */\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tblocksRemaining: number;\n\tprocessedBlocks?: number;\n\ttotalBlocks?: number;\n\tprogress: number;\n\tresourceWarning?: SubgraphResourceWarning;\n\tgaps: {\n\t\tcount: number;\n\t\ttotalMissingBlocks: number;\n\t\tranges: SubgraphGapRange[];\n\t};\n\tintegrity: \"complete\" | \"gaps_detected\";\n}\n\nexport interface SubgraphResourceWarning {\n\tcode: \"HOBBY_LARGE_REINDEX\";\n\tmessage: string;\n\tplan: \"hobby\";\n\tblockRange: number;\n\tprocessorMemoryMb: number;\n\trecommendedPlan: \"launch\";\n}\n\nexport interface SubgraphDetail {\n\tname: string;\n\tversion: string;\n\tschemaHash?: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\tdescription?: string;\n\tsources?: Record<string, unknown>;\n\tdefinition?: Record<string, unknown>;\n\thealth: {\n\t\ttotalProcessed: number;\n\t\ttotalErrors: number;\n\t\terrorRate: number;\n\t\tlastError: string | null;\n\t\tlastErrorAt: string | null;\n\t};\n\tsync: SubgraphSyncInfo;\n\ttables: Record<\n\t\tstring,\n\t\t{\n\t\t\tendpoint: string;\n\t\t\tcolumns: Record<\n\t\t\t\tstring,\n\t\t\t\t{\n\t\t\t\t\ttype: string;\n\t\t\t\t\tnullable?: boolean;\n\t\t\t\t\tindexed?: boolean;\n\t\t\t\t\tsearchable?: boolean;\n\t\t\t\t\tdefault?: string | number | boolean;\n\t\t\t\t}\n\t\t\t>;\n\t\t\trowCount: number;\n\t\t\texample: string;\n\t\t\tindexes?: string[][];\n\t\t\tuniqueKeys?: string[][];\n\t\t}\n\t>;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface SubgraphGapEntry {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n\tdetectedAt: string;\n\tresolvedAt: string | null;\n}\n\nexport interface SubgraphGapsResponse {\n\tdata: SubgraphGapEntry[];\n\tmeta: {\n\t\ttotal: number;\n\t\ttotalMissingBlocks: number;\n\t\tlimit: number;\n\t\toffset: number;\n\t};\n}\n\nexport interface ReindexResponse {\n\tmessage: string;\n\tfromBlock: number;\n\ttoBlock: number | string;\n\toperationId?: string;\n\tstatus?: \"queued\" | \"running\" | \"cancel_requested\";\n}\n\nexport interface SubgraphQueryParams {\n\tsort?: string;\n\torder?: string;\n\tlimit?: number;\n\toffset?: number;\n\tfields?: string;\n\tfilters?: Record<string, string>;\n}\n",
12
+ "import { z } from \"zod/v4\";\n\n// ── Deploy Subgraph Request ─────────────────────────────────────────────────\n\nexport interface DeploySubgraphRequest {\n\tname: string;\n\tversion?: string;\n\tdescription?: string;\n\tsources: Record<string, Record<string, unknown>>;\n\tschema: Record<string, unknown>;\n\thandlerCode: string;\n\t/** Override the definition's startBlock for this deploy only. */\n\tstartBlock?: number;\n\t/** Original TypeScript source, persisted so chat can read/diff/edit later. */\n\tsourceCode?: string;\n}\n\nexport const DeploySubgraphRequestSchema: z.ZodType<DeploySubgraphRequest> =\n\tz.object({\n\t\tname: z\n\t\t\t.string()\n\t\t\t.regex(/^[a-z0-9-]+$/, \"lowercase alphanumeric + hyphens only\")\n\t\t\t.max(63),\n\t\tversion: z.string().optional(),\n\t\tdescription: z.string().optional(),\n\t\tsources: z\n\t\t\t.record(z.string(), z.record(z.string(), z.unknown()))\n\t\t\t.refine(\n\t\t\t\t(s) => Object.keys(s).length > 0,\n\t\t\t\t\"Must have at least one source\",\n\t\t\t),\n\t\tschema: z.record(z.string(), z.unknown()),\n\t\thandlerCode: z.string().max(1_048_576, \"handler code exceeds 1MB limit\"),\n\t\tstartBlock: z.number().int().nonnegative().optional(),\n\t\tsourceCode: z\n\t\t\t.string()\n\t\t\t.max(1_048_576, \"source code exceeds 1MB limit\")\n\t\t\t.optional(),\n\t});\n\nexport interface DeploySubgraphResponse {\n\taction: \"created\" | \"unchanged\" | \"updated\" | \"reindexed\";\n\tsubgraphId: string;\n\tversion: string;\n\tmessage: string;\n\toperationId?: string;\n\treindexStarted?: boolean;\n\tdiff?: {\n\t\taddedTables: string[];\n\t\tremovedTables: string[];\n\t\taddedColumns: Record<string, string[]>;\n\t\tbreakingChanges: string[];\n\t};\n}\n\n// Subgraph API response types\n\nexport interface SubgraphSummary {\n\tname: string;\n\tversion: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\ttotalProcessed: number;\n\ttotalErrors: number;\n\ttables: string[];\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tprogress: number;\n\tblocksRemaining?: number;\n\tsyncMode?: \"sync\" | \"reindex\";\n\tresourceWarning?: SubgraphResourceWarning;\n\tgapCount: number;\n\tintegrity: \"complete\" | \"gaps_detected\";\n\tcreatedAt: string;\n}\n\nexport interface SubgraphGapRange {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n}\n\nexport interface SubgraphSyncInfo {\n\tstatus: \"synced\" | \"catching_up\" | \"reindexing\" | \"error\";\n\tmode?: \"sync\" | \"reindex\";\n\tstartBlock: number;\n\tlastProcessedBlock: number;\n\t/**\n\t * Backward-compatible denominator for progress displays. During reindexing,\n\t * this is the reindex target block rather than the live source chain tip.\n\t */\n\tchainTip: number;\n\tsourceChainTip?: number;\n\ttargetBlock?: number;\n\tblocksRemaining: number;\n\tprocessedBlocks?: number;\n\ttotalBlocks?: number;\n\tprogress: number;\n\tresourceWarning?: SubgraphResourceWarning;\n\tgaps: {\n\t\tcount: number;\n\t\ttotalMissingBlocks: number;\n\t\tranges: SubgraphGapRange[];\n\t};\n\tintegrity: \"complete\" | \"gaps_detected\";\n}\n\nexport interface SubgraphResourceWarning {\n\tcode: string;\n\tmessage: string;\n\tplan?: string;\n\tblockRange: number;\n\tprocessorMemoryMb: number;\n\trecommendedPlan: \"launch\";\n}\n\nexport interface SubgraphDetail {\n\tname: string;\n\tversion: string;\n\tschemaHash?: string;\n\tstatus: string;\n\tlastProcessedBlock: number;\n\tdescription?: string;\n\tsources?: Record<string, unknown>;\n\tdefinition?: Record<string, unknown>;\n\thealth: {\n\t\ttotalProcessed: number;\n\t\ttotalErrors: number;\n\t\terrorRate: number;\n\t\tlastError: string | null;\n\t\tlastErrorAt: string | null;\n\t};\n\tsync: SubgraphSyncInfo;\n\ttables: Record<\n\t\tstring,\n\t\t{\n\t\t\tendpoint: string;\n\t\t\tcolumns: Record<\n\t\t\t\tstring,\n\t\t\t\t{\n\t\t\t\t\ttype: string;\n\t\t\t\t\tnullable?: boolean;\n\t\t\t\t\tindexed?: boolean;\n\t\t\t\t\tsearchable?: boolean;\n\t\t\t\t\tdefault?: string | number | boolean;\n\t\t\t\t}\n\t\t\t>;\n\t\t\trowCount: number;\n\t\t\texample: string;\n\t\t\tindexes?: string[][];\n\t\t\tuniqueKeys?: string[][];\n\t\t}\n\t>;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface SubgraphGapEntry {\n\tstart: number;\n\tend: number;\n\tsize: number;\n\treason: string;\n\tdetectedAt: string;\n\tresolvedAt: string | null;\n}\n\nexport interface SubgraphGapsResponse {\n\tdata: SubgraphGapEntry[];\n\tmeta: {\n\t\ttotal: number;\n\t\ttotalMissingBlocks: number;\n\t\tlimit: number;\n\t\toffset: number;\n\t};\n}\n\nexport interface ReindexResponse {\n\tmessage: string;\n\tfromBlock: number;\n\ttoBlock: number | string;\n\toperationId?: string;\n\tstatus?: \"queued\" | \"running\" | \"cancel_requested\";\n}\n\nexport interface SubgraphQueryParams {\n\tsort?: string;\n\torder?: string;\n\tlimit?: number;\n\toffset?: number;\n\tfields?: string;\n\tfilters?: Record<string, string>;\n}\n",
13
13
  "import { z } from \"zod/v4\";\n\nexport const SUBSCRIPTION_FORMATS = [\n\t\"standard-webhooks\",\n\t\"inngest\",\n\t\"trigger\",\n\t\"cloudflare\",\n\t\"cloudevents\",\n\t\"raw\",\n] as const;\n\nexport const SUBSCRIPTION_RUNTIMES = [\n\t\"inngest\",\n\t\"trigger\",\n\t\"cloudflare\",\n\t\"node\",\n] as const;\n\nexport const SUBSCRIPTION_STATUSES = [\"active\", \"paused\", \"error\"] as const;\n\nexport const SUBSCRIPTION_FILTER_OPERATORS = [\n\t\"eq\",\n\t\"neq\",\n\t\"gt\",\n\t\"gte\",\n\t\"lt\",\n\t\"lte\",\n\t\"in\",\n] as const;\n\nconst webhookUrl = z\n\t.string()\n\t.trim()\n\t.min(1)\n\t.refine(\n\t\t(value) => value.startsWith(\"http://\") || value.startsWith(\"https://\"),\n\t\t\"must be an http(s) URL\",\n\t);\n\nconst name = z.string().trim().min(1).max(128);\nconst resourceName = z.string().trim().min(1).max(128);\n\nexport const SubscriptionStatusSchema: z.ZodType<SubscriptionStatus> = z.enum(\n\tSUBSCRIPTION_STATUSES,\n);\nexport const SubscriptionFormatSchema: z.ZodType<SubscriptionFormat> =\n\tz.enum(SUBSCRIPTION_FORMATS);\nexport const SubscriptionRuntimeSchema: z.ZodType<SubscriptionRuntime> = z.enum(\n\tSUBSCRIPTION_RUNTIMES,\n);\n\nexport const SubscriptionFilterPrimitiveSchema: z.ZodType<SubscriptionFilterPrimitive> =\n\tz.union([z.string(), z.number().finite(), z.boolean()]);\n\nexport const SubscriptionFilterOperatorSchema: z.ZodType<SubscriptionFilterOperator> =\n\tz.union([\n\t\tz.object({ eq: SubscriptionFilterPrimitiveSchema }).strict(),\n\t\tz.object({ neq: SubscriptionFilterPrimitiveSchema }).strict(),\n\t\tz.object({ gt: z.union([z.string(), z.number().finite()]) }).strict(),\n\t\tz.object({ gte: z.union([z.string(), z.number().finite()]) }).strict(),\n\t\tz.object({ lt: z.union([z.string(), z.number().finite()]) }).strict(),\n\t\tz.object({ lte: z.union([z.string(), z.number().finite()]) }).strict(),\n\t\tz\n\t\t\t.object({\n\t\t\t\tin: z.array(SubscriptionFilterPrimitiveSchema).min(1),\n\t\t\t})\n\t\t\t.strict(),\n\t]);\n\nexport const SubscriptionFilterClauseSchema: z.ZodType<SubscriptionFilterClause> =\n\tz.union([\n\t\tSubscriptionFilterPrimitiveSchema,\n\t\tSubscriptionFilterOperatorSchema,\n\t]);\n\nexport const SubscriptionFilterSchema: z.ZodType<SubscriptionFilter> = z.record(\n\tz.string().min(1),\n\tSubscriptionFilterClauseSchema,\n);\n\nexport const CreateSubscriptionRequestSchema: z.ZodType<ParsedCreateSubscriptionRequest> =\n\tz.object({\n\t\tname,\n\t\tsubgraphName: resourceName,\n\t\ttableName: resourceName,\n\t\turl: webhookUrl,\n\t\tfilter: SubscriptionFilterSchema.optional(),\n\t\tformat: SubscriptionFormatSchema.default(\"standard-webhooks\"),\n\t\truntime: SubscriptionRuntimeSchema.nullable().optional(),\n\t\tauthConfig: z.record(z.string(), z.unknown()).optional(),\n\t\tmaxRetries: z.number().int().min(0).max(100).optional(),\n\t\ttimeoutMs: z.number().int().min(100).max(300_000).optional(),\n\t\tconcurrency: z.number().int().min(1).max(100).optional(),\n\t});\n\nexport const UpdateSubscriptionRequestSchema: z.ZodType<UpdateSubscriptionRequest> =\n\tz\n\t\t.object({\n\t\t\tname: name.optional(),\n\t\t\turl: webhookUrl.optional(),\n\t\t\tfilter: SubscriptionFilterSchema.optional(),\n\t\t\tformat: SubscriptionFormatSchema.optional(),\n\t\t\truntime: SubscriptionRuntimeSchema.nullable().optional(),\n\t\t\tauthConfig: z.record(z.string(), z.unknown()).optional(),\n\t\t\tmaxRetries: z.number().int().min(0).max(100).optional(),\n\t\t\ttimeoutMs: z.number().int().min(100).max(300_000).optional(),\n\t\t\tconcurrency: z.number().int().min(1).max(100).optional(),\n\t\t})\n\t\t.refine((value) => Object.keys(value).length > 0, {\n\t\t\tmessage: \"At least one field must be provided\",\n\t\t});\n\nexport const ReplaySubscriptionRequestSchema: z.ZodType<ReplaySubscriptionRequest> =\n\tz\n\t\t.object({\n\t\t\tfromBlock: z.number().int().nonnegative(),\n\t\t\ttoBlock: z.number().int().nonnegative(),\n\t\t\tforce: z.string().trim().min(1).max(64).optional(),\n\t\t})\n\t\t.refine((value) => value.fromBlock <= value.toBlock, {\n\t\t\tmessage: \"fromBlock must be less than or equal to toBlock\",\n\t\t\tpath: [\"toBlock\"],\n\t\t});\n\nexport type SubscriptionStatus = (typeof SUBSCRIPTION_STATUSES)[number];\nexport type SubscriptionFormat = (typeof SUBSCRIPTION_FORMATS)[number];\nexport type SubscriptionRuntime = (typeof SUBSCRIPTION_RUNTIMES)[number];\nexport type SubscriptionFilterPrimitive = string | number | boolean;\nexport type SubscriptionFilterOperator =\n\t| { eq: SubscriptionFilterPrimitive }\n\t| { neq: SubscriptionFilterPrimitive }\n\t| { gt: string | number }\n\t| { gte: string | number }\n\t| { lt: string | number }\n\t| { lte: string | number }\n\t| { in: SubscriptionFilterPrimitive[] };\nexport type SubscriptionFilterClause =\n\t| SubscriptionFilterPrimitive\n\t| SubscriptionFilterOperator;\nexport type SubscriptionFilter = Record<string, SubscriptionFilterClause>;\n\nexport interface CreateSubscriptionRequest {\n\tname: string;\n\tsubgraphName: string;\n\ttableName: string;\n\turl: string;\n\tfilter?: SubscriptionFilter;\n\tformat?: SubscriptionFormat;\n\truntime?: SubscriptionRuntime | null;\n\tauthConfig?: Record<string, unknown>;\n\tmaxRetries?: number;\n\ttimeoutMs?: number;\n\tconcurrency?: number;\n}\n\nexport interface ParsedCreateSubscriptionRequest\n\textends Omit<CreateSubscriptionRequest, \"format\"> {\n\tformat: SubscriptionFormat;\n}\n\nexport interface UpdateSubscriptionRequest {\n\tname?: string;\n\turl?: string;\n\tfilter?: SubscriptionFilter;\n\tformat?: SubscriptionFormat;\n\truntime?: SubscriptionRuntime | null;\n\tauthConfig?: Record<string, unknown>;\n\tmaxRetries?: number;\n\ttimeoutMs?: number;\n\tconcurrency?: number;\n}\n\nexport type ParsedUpdateSubscriptionRequest = UpdateSubscriptionRequest;\n\nexport interface ReplaySubscriptionRequest {\n\tfromBlock: number;\n\ttoBlock: number;\n\tforce?: string;\n}\n\nexport type ParsedReplaySubscriptionRequest = ReplaySubscriptionRequest;\n\nexport interface SubscriptionSummary {\n\tid: string;\n\tname: string;\n\tstatus: SubscriptionStatus;\n\tsubgraphName: string;\n\ttableName: string;\n\tformat: SubscriptionFormat;\n\truntime: SubscriptionRuntime | null;\n\turl: string;\n\tlastDeliveryAt: string | null;\n\tlastSuccessAt: string | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface SubscriptionDetail extends SubscriptionSummary {\n\tfilter: Record<string, unknown>;\n\tauthConfig: Record<string, unknown>;\n\tmaxRetries: number;\n\ttimeoutMs: number;\n\tconcurrency: number;\n\tcircuitFailures: number;\n\tcircuitOpenedAt: string | null;\n\tlastError: string | null;\n}\n\nexport interface CreateSubscriptionResponse {\n\tsubscription: SubscriptionDetail;\n\t/** Plaintext signing secret — surfaced ONCE. Store it server-side. */\n\tsigningSecret: string;\n}\n\nexport interface RotateSecretResponse {\n\tsubscription: SubscriptionDetail;\n\tsigningSecret: string;\n}\n\nexport interface DeliveryRow {\n\tid: string;\n\tattempt: number;\n\tstatusCode: number | null;\n\terrorMessage: string | null;\n\tdurationMs: number | null;\n\tresponseBody: string | null;\n\tdispatchedAt: string;\n}\n\nexport interface ReplayResult {\n\treplayId: string;\n\tenqueuedCount: number;\n\tscannedCount: number;\n}\n\nexport interface DeadRow {\n\tid: string;\n\teventType: string;\n\tattempt: number;\n\tblockHeight: number;\n\ttxId: string | null;\n\tpayload: Record<string, unknown>;\n\tfailedAt: string | null;\n\tcreatedAt: string;\n}\n\nexport interface SubscriptionSchemaColumn {\n\ttype?: unknown;\n}\n\nexport interface SubscriptionSchemaTable {\n\tcolumns: Record<string, SubscriptionSchemaColumn>;\n}\n\nexport type SubscriptionSchemaTables = Record<string, SubscriptionSchemaTable>;\n\nconst SCALAR_COLUMN_TYPES = new Set([\n\t\"text\",\n\t\"uint\",\n\t\"int\",\n\t\"principal\",\n\t\"boolean\",\n\t\"timestamp\",\n]);\n\nconst COMPARISON_COLUMN_TYPES = new Set([\"uint\", \"int\", \"timestamp\"]);\n\nfunction formatIssuePath(path: PropertyKey[]): string {\n\treturn path.length > 0 ? `${path.map(String).join(\".\")}: ` : \"\";\n}\n\nexport function formatSubscriptionSchemaErrors(error: z.ZodError): string[] {\n\treturn error.issues.map(\n\t\t(issue) => `${formatIssuePath(issue.path)}${issue.message}`,\n\t);\n}\n\nfunction operatorForClause(clause: SubscriptionFilterClause): string {\n\tif (clause === null || typeof clause !== \"object\" || Array.isArray(clause)) {\n\t\treturn \"eq\";\n\t}\n\treturn Object.keys(clause)[0] ?? \"eq\";\n}\n\nexport function validateSubscriptionFilterForTable(input: {\n\tsubgraphName?: string;\n\ttableName: string;\n\tfilter?: unknown;\n\ttables: SubscriptionSchemaTables;\n}): string[] {\n\tconst errors: string[] = [];\n\tconst table = input.tables[input.tableName];\n\tif (!table) {\n\t\tconst names = Object.keys(input.tables);\n\t\terrors.push(\n\t\t\t`Unknown table \"${input.tableName}\"${\n\t\t\t\tinput.subgraphName ? ` in subgraph \"${input.subgraphName}\"` : \"\"\n\t\t\t}.${names.length > 0 ? ` Available tables: ${names.join(\", \")}.` : \"\"}`,\n\t\t);\n\t\treturn errors;\n\t}\n\n\tif (input.filter === undefined) return errors;\n\n\tconst parsed = SubscriptionFilterSchema.safeParse(input.filter);\n\tif (!parsed.success) {\n\t\treturn formatSubscriptionSchemaErrors(parsed.error);\n\t}\n\n\tfor (const [field, clause] of Object.entries(parsed.data)) {\n\t\tconst column = table.columns[field];\n\t\tif (!column) {\n\t\t\terrors.push(\n\t\t\t\t`Unknown filter field \"${field}\" on table \"${input.tableName}\".`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst columnType =\n\t\t\ttypeof column.type === \"string\" ? column.type.toLowerCase() : \"\";\n\t\tif (!SCALAR_COLUMN_TYPES.has(columnType)) {\n\t\t\terrors.push(\n\t\t\t\t`Filter field \"${field}\" has unsupported type \"${columnType || \"unknown\"}\"; subscription filters require scalar columns.`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst operator = operatorForClause(clause);\n\t\tif (\n\t\t\t(operator === \"gt\" ||\n\t\t\t\toperator === \"gte\" ||\n\t\t\t\toperator === \"lt\" ||\n\t\t\t\toperator === \"lte\") &&\n\t\t\t!COMPARISON_COLUMN_TYPES.has(columnType)\n\t\t) {\n\t\t\terrors.push(\n\t\t\t\t`Operator \"${operator}\" is not supported for ${columnType} field \"${field}\".`,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn errors;\n}\n",
14
14
  "import type { SubgraphDetail } from \"../schemas/subgraphs.ts\";\n\nexport type SubgraphSpecFormat = \"openapi\" | \"agent\" | \"markdown\";\n\nexport interface SubgraphSpecOptions {\n\tserverUrl?: string;\n\tgeneratedAt?: string;\n}\n\nexport interface SubgraphAgentSchema {\n\tname: string;\n\tversion: string;\n\tdescription?: string;\n\tschemaHash?: string;\n\tgeneratedAt: string;\n\tserverUrl: string;\n\tsources?: Record<string, unknown>;\n\ttables: Record<\n\t\tstring,\n\t\t{\n\t\t\tendpoint: string;\n\t\t\tcountEndpoint: string;\n\t\t\trowCount: number;\n\t\t\tcolumns: SubgraphDetail[\"tables\"][string][\"columns\"];\n\t\t\tindexes?: string[][];\n\t\t\tuniqueKeys?: string[][];\n\t\t\tquery: {\n\t\t\t\tparameters: string[];\n\t\t\t\tsortable: string[];\n\t\t\t\tselectable: string[];\n\t\t\t\tsearchable: string[];\n\t\t\t\tfilters: string[];\n\t\t\t};\n\t\t\texamples: {\n\t\t\t\tlist: Record<string, unknown>;\n\t\t\t\tcount: { count: number };\n\t\t\t\tcurl: string;\n\t\t\t};\n\t\t}\n\t>;\n}\n\ntype ColumnMeta = SubgraphDetail[\"tables\"][string][\"columns\"][string];\n\nconst SYSTEM_COLUMNS = [\"_id\", \"_block_height\", \"_tx_id\", \"_created_at\"];\nconst BASE_QUERY_PARAMS = [\"_limit\", \"_offset\", \"_sort\", \"_order\", \"_fields\"];\nconst COMPARISON_OPS = [\"neq\", \"gt\", \"gte\", \"lt\", \"lte\"];\n\nfunction generatedAt(options: SubgraphSpecOptions): string {\n\treturn options.generatedAt ?? new Date().toISOString();\n}\n\nfunction normalizeServerUrl(serverUrl?: string): string {\n\treturn (serverUrl ?? \"https://api.secondlayer.tools\").replace(/\\/+$/, \"\");\n}\n\nfunction tablePath(subgraphName: string, tableName: string): string {\n\treturn `/api/subgraphs/${subgraphName}/${tableName}`;\n}\n\nfunction countPath(subgraphName: string, tableName: string): string {\n\treturn `${tablePath(subgraphName, tableName)}/count`;\n}\n\nfunction isTextLike(type: string): boolean {\n\treturn type === \"text\" || type === \"principal\" || type === \"timestamp\";\n}\n\nfunction isComparable(type: string): boolean {\n\treturn (\n\t\ttype === \"uint\" ||\n\t\ttype === \"int\" ||\n\t\ttype === \"bigint\" ||\n\t\ttype === \"serial\" ||\n\t\ttype === \"timestamp\"\n\t);\n}\n\nfunction exampleForColumn(type: string): unknown {\n\tswitch (type) {\n\t\tcase \"uint\":\n\t\tcase \"int\":\n\t\tcase \"bigint\":\n\t\t\treturn \"1000\";\n\t\tcase \"serial\":\n\t\t\treturn 1;\n\t\tcase \"principal\":\n\t\t\treturn \"SP000000000000000000002Q6VF78\";\n\t\tcase \"timestamp\":\n\t\t\treturn \"2026-01-01T00:00:00.000Z\";\n\t\tcase \"boolean\":\n\t\t\treturn true;\n\t\tcase \"jsonb\":\n\t\t\treturn { example: true };\n\t\tdefault:\n\t\t\treturn \"example\";\n\t}\n}\n\nfunction openApiSchemaForColumn(col: ColumnMeta): Record<string, unknown> {\n\tlet schema: Record<string, unknown>;\n\tswitch (col.type) {\n\t\tcase \"uint\":\n\t\tcase \"int\":\n\t\tcase \"bigint\":\n\t\t\tschema = { type: \"string\", pattern: \"^-?\\\\d+(\\\\.\\\\d+)?$\" };\n\t\t\tbreak;\n\t\tcase \"serial\":\n\t\t\tschema = { type: \"integer\" };\n\t\t\tbreak;\n\t\tcase \"principal\":\n\t\tcase \"text\":\n\t\t\tschema = { type: \"string\" };\n\t\t\tbreak;\n\t\tcase \"timestamp\":\n\t\t\tschema = { type: \"string\", format: \"date-time\" };\n\t\t\tbreak;\n\t\tcase \"boolean\":\n\t\t\tschema = { type: \"boolean\" };\n\t\t\tbreak;\n\t\tcase \"jsonb\":\n\t\t\tschema = { type: \"object\", additionalProperties: true };\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tschema = {};\n\t\t\tbreak;\n\t}\n\tif (col.nullable) {\n\t\tconst type = schema.type;\n\t\tif (typeof type === \"string\") schema.type = [type, \"null\"];\n\t}\n\treturn schema;\n}\n\nfunction columnEntries(table: SubgraphDetail[\"tables\"][string]) {\n\treturn Object.entries(table.columns);\n}\n\nfunction selectableColumns(table: SubgraphDetail[\"tables\"][string]): string[] {\n\treturn columnEntries(table).map(([name]) => name);\n}\n\nfunction searchableColumns(table: SubgraphDetail[\"tables\"][string]): string[] {\n\treturn columnEntries(table)\n\t\t.filter(([, col]) => col.searchable)\n\t\t.map(([name]) => name);\n}\n\nfunction filterNames(table: SubgraphDetail[\"tables\"][string]): string[] {\n\tconst result: string[] = [];\n\tfor (const [name, col] of columnEntries(table)) {\n\t\tresult.push(name);\n\t\tresult.push(`${name}.neq`);\n\t\tif (isComparable(col.type)) {\n\t\t\tfor (const op of COMPARISON_OPS.filter((op) => op !== \"neq\")) {\n\t\t\t\tresult.push(`${name}.${op}`);\n\t\t\t}\n\t\t}\n\t\tif (isTextLike(col.type)) result.push(`${name}.like`);\n\t}\n\treturn result;\n}\n\nfunction queryParameters(table: SubgraphDetail[\"tables\"][string]): string[] {\n\tconst params = [...BASE_QUERY_PARAMS];\n\tif (searchableColumns(table).length > 0) params.push(\"_search\");\n\treturn params;\n}\n\nfunction rowExample(table: SubgraphDetail[\"tables\"][string]) {\n\tconst row: Record<string, unknown> = {};\n\tfor (const [name, col] of columnEntries(table)) {\n\t\trow[name] = exampleForColumn(col.type);\n\t}\n\treturn row;\n}\n\nfunction openApiParameter(\n\tname: string,\n\tdescription: string,\n\tschema: Record<string, unknown> = { type: \"string\" },\n) {\n\treturn {\n\t\tname,\n\t\tin: \"query\",\n\t\trequired: false,\n\t\tdescription,\n\t\tschema,\n\t};\n}\n\nfunction tableParameters(table: SubgraphDetail[\"tables\"][string]) {\n\tconst parameters = [\n\t\topenApiParameter(\"_limit\", \"Maximum rows to return.\", {\n\t\t\ttype: \"integer\",\n\t\t\tdefault: 50,\n\t\t\tminimum: 1,\n\t\t\tmaximum: 1000,\n\t\t}),\n\t\topenApiParameter(\"_offset\", \"Rows to skip for pagination.\", {\n\t\t\ttype: \"integer\",\n\t\t\tdefault: 0,\n\t\t\tminimum: 0,\n\t\t}),\n\t\topenApiParameter(\"_sort\", \"Column to sort by.\", {\n\t\t\ttype: \"string\",\n\t\t\tenum: selectableColumns(table),\n\t\t}),\n\t\topenApiParameter(\"_order\", \"Sort direction.\", {\n\t\t\ttype: \"string\",\n\t\t\tenum: [\"asc\", \"desc\"],\n\t\t\tdefault: \"asc\",\n\t\t}),\n\t\topenApiParameter(\"_fields\", \"Comma-separated columns to include.\", {\n\t\t\ttype: \"string\",\n\t\t}),\n\t];\n\tif (searchableColumns(table).length > 0) {\n\t\tparameters.push(\n\t\t\topenApiParameter(\"_search\", \"Search across searchable columns.\", {\n\t\t\t\ttype: \"string\",\n\t\t\t}),\n\t\t);\n\t}\n\tfor (const [name, col] of columnEntries(table)) {\n\t\tparameters.push(\n\t\t\topenApiParameter(name, `Filter ${name} by equality.`, {\n\t\t\t\ttype: \"string\",\n\t\t\t}),\n\t\t);\n\t\tparameters.push(\n\t\t\topenApiParameter(`${name}.neq`, `Filter ${name} by inequality.`, {\n\t\t\t\ttype: \"string\",\n\t\t\t}),\n\t\t);\n\t\tif (isComparable(col.type)) {\n\t\t\tfor (const op of [\"gt\", \"gte\", \"lt\", \"lte\"]) {\n\t\t\t\tparameters.push(\n\t\t\t\t\topenApiParameter(`${name}.${op}`, `Filter ${name} with ${op}.`, {\n\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tif (isTextLike(col.type)) {\n\t\t\tparameters.push(\n\t\t\t\topenApiParameter(\n\t\t\t\t\t`${name}.like`,\n\t\t\t\t\t`Case-insensitive contains filter for ${name}.`,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn parameters;\n}\n\nexport function generateSubgraphAgentSchema(\n\tdetail: SubgraphDetail,\n\toptions: SubgraphSpecOptions = {},\n): SubgraphAgentSchema {\n\tconst serverUrl = normalizeServerUrl(options.serverUrl);\n\tconst tables: SubgraphAgentSchema[\"tables\"] = {};\n\tfor (const [tableName, table] of Object.entries(detail.tables)) {\n\t\tconst path = tablePath(detail.name, tableName);\n\t\ttables[tableName] = {\n\t\t\tendpoint: `${serverUrl}${path}`,\n\t\t\tcountEndpoint: `${serverUrl}${countPath(detail.name, tableName)}`,\n\t\t\trowCount: table.rowCount,\n\t\t\tcolumns: table.columns,\n\t\t\t...(table.indexes ? { indexes: table.indexes } : {}),\n\t\t\t...(table.uniqueKeys ? { uniqueKeys: table.uniqueKeys } : {}),\n\t\t\tquery: {\n\t\t\t\tparameters: queryParameters(table),\n\t\t\t\tsortable: selectableColumns(table),\n\t\t\t\tselectable: selectableColumns(table),\n\t\t\t\tsearchable: searchableColumns(table),\n\t\t\t\tfilters: filterNames(table),\n\t\t\t},\n\t\t\texamples: {\n\t\t\t\tlist: rowExample(table),\n\t\t\t\tcount: { count: table.rowCount },\n\t\t\t\tcurl: `curl '${serverUrl}${path}?_limit=10&_sort=_block_height&_order=desc'`,\n\t\t\t},\n\t\t};\n\t}\n\treturn {\n\t\tname: detail.name,\n\t\tversion: detail.version,\n\t\t...(detail.description ? { description: detail.description } : {}),\n\t\t...(detail.schemaHash ? { schemaHash: detail.schemaHash } : {}),\n\t\tgeneratedAt: generatedAt(options),\n\t\tserverUrl,\n\t\t...(detail.sources ? { sources: detail.sources } : {}),\n\t\ttables,\n\t};\n}\n\nexport function generateSubgraphOpenApi(\n\tdetail: SubgraphDetail,\n\toptions: SubgraphSpecOptions = {},\n): Record<string, unknown> {\n\tconst serverUrl = normalizeServerUrl(options.serverUrl);\n\tconst paths: Record<string, unknown> = {};\n\tconst schemas: Record<string, unknown> = {};\n\n\tfor (const [tableName, table] of Object.entries(detail.tables)) {\n\t\tconst schemaName = `${tableName}Row`;\n\t\tconst properties: Record<string, unknown> = {};\n\t\tconst required: string[] = [];\n\t\tfor (const [columnName, column] of columnEntries(table)) {\n\t\t\tproperties[columnName] = openApiSchemaForColumn(column);\n\t\t\tif (!column.nullable) required.push(columnName);\n\t\t}\n\t\tschemas[schemaName] = {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired,\n\t\t\texample: rowExample(table),\n\t\t};\n\n\t\tpaths[tablePath(detail.name, tableName)] = {\n\t\t\tget: {\n\t\t\t\tsummary: `Query ${detail.name}.${tableName}`,\n\t\t\t\toperationId: `query_${detail.name.replace(/-/g, \"_\")}_${tableName}`,\n\t\t\t\tparameters: tableParameters(table),\n\t\t\t\tresponses: {\n\t\t\t\t\t\"200\": {\n\t\t\t\t\t\tdescription: \"Rows returned from the subgraph table.\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\titems: { $ref: `#/components/schemas/${schemaName}` },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tmeta: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\ttotal: { type: \"integer\" },\n\t\t\t\t\t\t\t\t\t\t\t\tlimit: { type: \"integer\" },\n\t\t\t\t\t\t\t\t\t\t\t\toffset: { type: \"integer\" },\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tpaths[countPath(detail.name, tableName)] = {\n\t\t\tget: {\n\t\t\t\tsummary: `Count ${detail.name}.${tableName}`,\n\t\t\t\toperationId: `count_${detail.name.replace(/-/g, \"_\")}_${tableName}`,\n\t\t\t\tparameters: tableParameters(table),\n\t\t\t\tresponses: {\n\t\t\t\t\t\"200\": {\n\t\t\t\t\t\tdescription: \"Row count for the filtered table query.\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\tproperties: { count: { type: \"integer\" } },\n\t\t\t\t\t\t\t\t\trequired: [\"count\"],\n\t\t\t\t\t\t\t\t\texample: { count: table.rowCount },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\topenapi: \"3.1.0\",\n\t\tinfo: {\n\t\t\ttitle: `${detail.name} Subgraph API`,\n\t\t\tversion: detail.version,\n\t\t\t...(detail.description ? { description: detail.description } : {}),\n\t\t},\n\t\tservers: [{ url: serverUrl }],\n\t\tpaths,\n\t\tcomponents: { schemas },\n\t\t\"x-secondlayer-subgraph\": detail.name,\n\t\t\"x-secondlayer-version\": detail.version,\n\t\t\"x-secondlayer-schema-hash\": detail.schemaHash,\n\t\t\"x-secondlayer-generated-at\": generatedAt(options),\n\t\t\"x-secondlayer-sources\": detail.sources ?? {},\n\t\t\"x-secondlayer-tables\": Object.keys(detail.tables),\n\t};\n}\n\nexport function generateSubgraphMarkdown(\n\tdetail: SubgraphDetail,\n\toptions: SubgraphSpecOptions = {},\n): string {\n\tconst agent = generateSubgraphAgentSchema(detail, options);\n\tconst lines = [\n\t\t`# ${detail.name} Subgraph API`,\n\t\t\"\",\n\t\t`Version: ${detail.version}`,\n\t\tdetail.schemaHash ? `Schema hash: ${detail.schemaHash}` : undefined,\n\t\t`Server: ${agent.serverUrl}`,\n\t\t\"\",\n\t\tdetail.description,\n\t].filter((line): line is string => line !== undefined && line !== \"\");\n\n\tfor (const [tableName, table] of Object.entries(agent.tables)) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t`## ${tableName}`,\n\t\t\t\"\",\n\t\t\t`GET ${table.endpoint}`,\n\t\t\t`GET ${table.countEndpoint}`,\n\t\t\t\"\",\n\t\t\t`Rows: ${table.rowCount}`,\n\t\t\t\"\",\n\t\t\t\"### Columns\",\n\t\t\t\"\",\n\t\t\t\"| Column | Type | Attributes |\",\n\t\t\t\"| --- | --- | --- |\",\n\t\t);\n\t\tfor (const [columnName, col] of Object.entries(table.columns)) {\n\t\t\tconst attrs = [\n\t\t\t\tSYSTEM_COLUMNS.includes(columnName) ? \"system\" : undefined,\n\t\t\t\tcol.nullable ? \"nullable\" : undefined,\n\t\t\t\tcol.indexed ? \"indexed\" : undefined,\n\t\t\t\tcol.searchable ? \"searchable\" : undefined,\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\", \");\n\t\t\tlines.push(`| \\`${columnName}\\` | \\`${col.type}\\` | ${attrs || \"-\"} |`);\n\t\t}\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t\"### Query\",\n\t\t\t\"\",\n\t\t\t`Parameters: ${table.query.parameters.map((p) => `\\`${p}\\``).join(\", \")}`,\n\t\t\t`Filters: ${table.query.filters.map((p) => `\\`${p}\\``).join(\", \")}`,\n\t\t\t\"\",\n\t\t\t\"### Example\",\n\t\t\t\"\",\n\t\t\t\"```bash\",\n\t\t\ttable.examples.curl,\n\t\t\t\"```\",\n\t\t);\n\t}\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function generateSubgraphSpec(\n\tdetail: SubgraphDetail,\n\tformat: SubgraphSpecFormat,\n\toptions: SubgraphSpecOptions = {},\n): Record<string, unknown> | SubgraphAgentSchema | string {\n\tif (format === \"openapi\") return generateSubgraphOpenApi(detail, options);\n\tif (format === \"agent\") return generateSubgraphAgentSchema(detail, options);\n\treturn generateSubgraphMarkdown(detail, options);\n}\n",
15
15
  "import { createHmac, randomBytes } from \"node:crypto\";\n\n/**\n * Generate a random secret for delivery signing\n * Returns 32 bytes as a 64-character hex string\n */\nexport function generateSecret(): string {\n\treturn 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\tconst hmac = createHmac(\"sha256\", secret);\n\thmac.update(payload);\n\treturn 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\tpayload: string,\n\tsignature: string,\n\tsecret: string,\n): boolean {\n\tconst expectedSignature = signPayload(payload, secret);\n\n\t// Constant-time comparison\n\tif (signature.length !== expectedSignature.length) {\n\t\treturn false;\n\t}\n\n\tlet result = 0;\n\tfor (let i = 0; i < signature.length; i++) {\n\t\tresult |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);\n\t}\n\n\treturn result === 0;\n}\n\n/**\n * Create a Stripe-style signature header\n * Format: t=timestamp,v1=signature\n */\nexport function createSignatureHeader(\n\tpayload: string,\n\tsecret: string,\n\ttimestamp?: number,\n): string {\n\tconst ts = timestamp ?? Math.floor(Date.now() / 1000);\n\tconst signedPayload = `${ts}.${payload}`;\n\tconst signature = signPayload(signedPayload, secret);\n\n\treturn `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\tpayload: string,\n\theader: string,\n\tsecret: string,\n\ttoleranceSeconds = 300, // 5 minutes\n): boolean {\n\t// Parse header\n\tconst parts = header.split(\",\");\n\tconst timestamp = parts.find((p) => p.startsWith(\"t=\"))?.slice(2);\n\tconst signature = parts.find((p) => p.startsWith(\"v1=\"))?.slice(3);\n\n\tif (!timestamp || !signature) {\n\t\treturn false;\n\t}\n\n\tconst ts = Number.parseInt(timestamp, 10);\n\tif (Number.isNaN(ts)) {\n\t\treturn false;\n\t}\n\n\t// Check timestamp is within tolerance\n\tconst now = Math.floor(Date.now() / 1000);\n\tif (Math.abs(now - ts) > toleranceSeconds) {\n\t\treturn false;\n\t}\n\n\t// Verify signature\n\tconst signedPayload = `${ts}.${payload}`;\n\treturn verifySignature(signedPayload, signature, secret);\n}\n"
@@ -5,6 +5,7 @@ interface BlocksTable {
5
5
  hash: string;
6
6
  parent_hash: string;
7
7
  burn_block_height: number;
8
+ burn_block_hash: ColumnType<string | null, string | null | undefined, string | null>;
8
9
  timestamp: number;
9
10
  canonical: Generated<boolean>;
10
11
  created_at: Generated<Date>;
@@ -145,6 +146,8 @@ interface UsageDailyTable {
145
146
  date: string;
146
147
  api_requests: Generated<number>;
147
148
  deliveries: Generated<number>;
149
+ streams_events_returned: Generated<number>;
150
+ index_decoded_events_returned: Generated<number>;
148
151
  }
149
152
  interface UsageSnapshotsTable {
150
153
  id: Generated<string>;
@@ -273,6 +276,44 @@ interface ProcessedStripeEventsTable {
273
276
  event_type: string;
274
277
  processed_at: Generated<Date>;
275
278
  }
279
+ interface DecodedEventsTable {
280
+ cursor: string;
281
+ block_height: number;
282
+ tx_id: string;
283
+ tx_index: number;
284
+ event_index: number;
285
+ event_type: string;
286
+ microblock_hash: string | null;
287
+ canonical: Generated<boolean>;
288
+ contract_id: string | null;
289
+ sender: string | null;
290
+ recipient: string | null;
291
+ amount: string | null;
292
+ asset_identifier: string | null;
293
+ value: string | null;
294
+ memo: string | null;
295
+ source_cursor: string;
296
+ created_at: Generated<Date>;
297
+ }
298
+ interface L2DecoderCheckpointsTable {
299
+ decoder_name: string;
300
+ last_cursor: string | null;
301
+ updated_at: Generated<Date>;
302
+ }
303
+ interface ChainReorgsTable {
304
+ id: Generated<string>;
305
+ detected_at: Generated<Date>;
306
+ fork_point_height: number;
307
+ old_index_block_hash: string | null;
308
+ new_index_block_hash: string | null;
309
+ orphaned_from_height: number;
310
+ orphaned_from_event_index: number;
311
+ orphaned_to_height: number;
312
+ orphaned_to_event_index: number;
313
+ new_canonical_height: number;
314
+ new_canonical_event_index: number;
315
+ created_at: Generated<Date>;
316
+ }
276
317
  interface Database {
277
318
  blocks: BlocksTable;
278
319
  transactions: TransactionsTable;
@@ -308,6 +349,9 @@ interface Database {
308
349
  subscriptions: SubscriptionsTable;
309
350
  subscription_outbox: SubscriptionOutboxTable;
310
351
  subscription_deliveries: SubscriptionDeliveriesTable;
352
+ decoded_events: DecodedEventsTable;
353
+ l2_decoder_checkpoints: L2DecoderCheckpointsTable;
354
+ chain_reorgs: ChainReorgsTable;
311
355
  }
312
356
  type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
313
357
  interface TenantsTable {
@@ -11,7 +11,8 @@
11
11
  * the Stripe-side cleanup (archive lookup_key), and update env vars.
12
12
  */
13
13
  declare const BYTES_PER_GB: number;
14
- type PlanId = "hobby" | "launch" | "scale" | "enterprise";
14
+ type AccountPlanId = "none" | PlanId;
15
+ type PlanId = "launch" | "scale" | "enterprise";
15
16
  interface ContainerAlloc {
16
17
  memoryMb: number;
17
18
  cpus: number;
@@ -36,9 +37,9 @@ interface Plan {
36
37
  tagline: string;
37
38
  /** Display-only. Bullet list on the plan card. */
38
39
  features: string[];
39
- /** Stripe `lookup_key` for monthly recurring tier price. null for hobby/enterprise. */
40
+ /** Stripe `lookup_key` for monthly recurring tier price. null for enterprise. */
40
41
  stripeLookupKey: string | null;
41
- /** Stripe `lookup_key` for annual recurring tier price. null for hobby/enterprise. */
42
+ /** Stripe `lookup_key` for annual recurring tier price. null for enterprise. */
42
43
  stripeAnnualLookupKey: string | null;
43
44
  }
44
45
  /**
@@ -52,8 +53,8 @@ declare function getPlan(id: string): Plan;
52
53
  declare function isValidPlanId(id: string): id is PlanId;
53
54
  declare function getComputeAllowanceHours(_plan: string): number;
54
55
  declare function getStorageAllowanceBytes(plan: string): number;
55
- /** Hobby has a hard cap (no overage billing); paid tiers bill $2/GB over allowance. */
56
+ /** Paid tiers bill $2/GB over allowance. Accounts with no plan do not accrue overage. */
56
57
  declare function hasStorageOverage(plan: string): boolean;
57
58
  declare function getBasePriceCents(plan: string): number;
58
59
  declare function getPlanDisplayName(plan: string): string;
59
- export { isValidPlanId, hasStorageOverage, getStorageAllowanceBytes, getPlanDisplayName, getPlan, getComputeAllowanceHours, getBasePriceCents, allocForTotals, PlanId, Plan, PLAN_IDS, PLANS, ContainerAlloc, BYTES_PER_GB };
60
+ export { isValidPlanId, hasStorageOverage, getStorageAllowanceBytes, getPlanDisplayName, getPlan, getComputeAllowanceHours, getBasePriceCents, allocForTotals, PlanId, Plan, PLAN_IDS, PLANS, ContainerAlloc, BYTES_PER_GB, AccountPlanId };
@@ -55,26 +55,6 @@ function allocForTotals(totalMemoryMb, totalCpus) {
55
55
  return totalMemoryMb < 1024 ? allocTight(totalMemoryMb, totalCpus) : alloc(totalMemoryMb, totalCpus);
56
56
  }
57
57
  var PLANS = {
58
- hobby: {
59
- id: "hobby",
60
- displayName: "Hobby",
61
- monthlyPriceCents: 0,
62
- annualPriceCents: null,
63
- totalCpus: 0.5,
64
- totalMemoryMb: 1024,
65
- storageLimitMb: 10240,
66
- containers: alloc(1024, 0.5),
67
- tagline: "MVP/demo/side project",
68
- features: [
69
- "0.5 vCPU · 1 GB RAM",
70
- "10 GB storage · auto-pause 7d",
71
- "MVP demos + side projects",
72
- "Recent-range reindexing",
73
- "Community support"
74
- ],
75
- stripeLookupKey: null,
76
- stripeAnnualLookupKey: null
77
- },
78
58
  launch: {
79
59
  id: "launch",
80
60
  displayName: "Launch",
@@ -134,12 +114,7 @@ var PLANS = {
134
114
  stripeAnnualLookupKey: null
135
115
  }
136
116
  };
137
- var PLAN_IDS = [
138
- "hobby",
139
- "launch",
140
- "scale",
141
- "enterprise"
142
- ];
117
+ var PLAN_IDS = ["launch", "scale", "enterprise"];
143
118
  function getPlan(id) {
144
119
  const plan = PLANS[id];
145
120
  if (!plan)
@@ -155,13 +130,13 @@ function getComputeAllowanceHours(_plan) {
155
130
  function getStorageAllowanceBytes(plan) {
156
131
  const planDef = PLANS[plan];
157
132
  if (!planDef)
158
- return PLANS.hobby.storageLimitMb * 1024 * 1024;
133
+ return 0;
159
134
  if (planDef.storageLimitMb < 0)
160
135
  return Number.POSITIVE_INFINITY;
161
136
  return planDef.storageLimitMb * 1024 * 1024;
162
137
  }
163
138
  function hasStorageOverage(plan) {
164
- return plan !== "hobby" && plan !== "enterprise";
139
+ return plan !== "none" && plan !== "enterprise";
165
140
  }
166
141
  function getBasePriceCents(plan) {
167
142
  const planDef = PLANS[plan];
@@ -185,5 +160,5 @@ export {
185
160
  BYTES_PER_GB
186
161
  };
187
162
 
188
- //# debugId=176BF41B28FD6A0464756E2164756E21
163
+ //# debugId=D52F48DDE4C1BD2764756E2164756E21
189
164
  //# sourceMappingURL=pricing.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/pricing.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Single source of truth for plan tiers — capacity, price, display copy,\n * Stripe binding, and container allocations.\n *\n * Consumed by:\n * - Provisioner (`packages/provisioner/src/plans.ts` re-exports)\n * - API (`/api/accounts/usage` for allowance math + display)\n * - Web app (`/billing` page renders plan cards from this)\n *\n * Adding a tier? Add an entry to PLANS. Removing one? Drop here, run\n * the Stripe-side cleanup (archive lookup_key), and update env vars.\n */\n\nconst BYTES_PER_GB: number = 1024 ** 3;\n\nexport type PlanId = \"hobby\" | \"launch\" | \"scale\" | \"enterprise\";\n\nexport interface ContainerAlloc {\n\tmemoryMb: number;\n\tcpus: number;\n}\n\nexport interface Plan {\n\tid: PlanId;\n\tdisplayName: string;\n\t/** Monthly subscription price in cents. null = custom (Enterprise). */\n\tmonthlyPriceCents: number | null;\n\t/** Annual subscription price in cents. null = no self-serve annual price. */\n\tannualPriceCents: number | null;\n\ttotalCpus: number;\n\ttotalMemoryMb: number;\n\t/** Hard cap. -1 = unlimited (Enterprise). Storage overage bills past this. */\n\tstorageLimitMb: number;\n\tcontainers: {\n\t\tpostgres: ContainerAlloc;\n\t\tapi: ContainerAlloc;\n\t\tprocessor: ContainerAlloc;\n\t};\n\t/** Display-only. Marketing/short pitch. */\n\ttagline: string;\n\t/** Display-only. Bullet list on the plan card. */\n\tfeatures: string[];\n\t/** Stripe `lookup_key` for monthly recurring tier price. null for hobby/enterprise. */\n\tstripeLookupKey: string | null;\n\t/** Stripe `lookup_key` for annual recurring tier price. null for hobby/enterprise. */\n\tstripeAnnualLookupKey: string | null;\n}\n\n// ── Allocation helpers ──────────────────────────────────────────────\n//\n// Allocation within a plan (3 containers per tenant):\n// Default split (paid tiers) — PG 50% / proc 30% / api 20%\n// Sub-1GB total — PG 60% / proc 25% / api 15%\n//\n// Biased toward PG on Hobby because PG 17's default `shared_buffers` is\n// 128MB — a naive 50/30/20 split on tiny plans leaves PG too little RAM if\n// `shared_buffers` isn't also shrunk. Hobby now sits at 1GB and uses the\n// default paid split.\n//\n// Docker memory limit is a hard cap (OOM kill on overage). CPU is a soft\n// cap via `--cpus` (throttling, not killing). Storage is monitored\n// separately and billed as overage — PG crashes if we hard-cap it.\n\nfunction alloc(totalMb: number, totalCpus: number): Plan[\"containers\"] {\n\treturn {\n\t\tpostgres: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.5),\n\t\t\tcpus: round2(totalCpus * 0.5),\n\t\t},\n\t\tprocessor: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.3),\n\t\t\tcpus: round2(totalCpus * 0.3),\n\t\t},\n\t\tapi: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.2),\n\t\t\tcpus: round2(totalCpus * 0.2),\n\t\t},\n\t};\n}\n\nfunction allocTight(totalMb: number, totalCpus: number): Plan[\"containers\"] {\n\treturn {\n\t\tpostgres: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.6),\n\t\t\tcpus: round2(totalCpus * 0.6),\n\t\t},\n\t\tprocessor: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.25),\n\t\t\tcpus: round2(totalCpus * 0.25),\n\t\t},\n\t\tapi: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.15),\n\t\t\tcpus: round2(totalCpus * 0.15),\n\t\t},\n\t};\n}\n\nfunction round2(n: number): number {\n\treturn Math.round(n * 100) / 100;\n}\n\n/**\n * Split a compute envelope across (postgres, processor, api) containers.\n * Auto-biases PG-heavy (60/25/15) for sub-1GB totals.\n */\nexport function allocForTotals(\n\ttotalMemoryMb: number,\n\ttotalCpus: number,\n): Plan[\"containers\"] {\n\treturn totalMemoryMb < 1024\n\t\t? allocTight(totalMemoryMb, totalCpus)\n\t\t: alloc(totalMemoryMb, totalCpus);\n}\n\n// ── Canonical plan data ─────────────────────────────────────────────\n\nexport const PLANS: Record<PlanId, Plan> = {\n\thobby: {\n\t\tid: \"hobby\",\n\t\tdisplayName: \"Hobby\",\n\t\tmonthlyPriceCents: 0,\n\t\tannualPriceCents: null,\n\t\ttotalCpus: 0.5,\n\t\ttotalMemoryMb: 1_024,\n\t\tstorageLimitMb: 10_240,\n\t\tcontainers: alloc(1_024, 0.5),\n\t\ttagline: \"MVP/demo/side project\",\n\t\tfeatures: [\n\t\t\t\"0.5 vCPU · 1 GB RAM\",\n\t\t\t\"10 GB storage · auto-pause 7d\",\n\t\t\t\"MVP demos + side projects\",\n\t\t\t\"Recent-range reindexing\",\n\t\t\t\"Community support\",\n\t\t],\n\t\tstripeLookupKey: null,\n\t\tstripeAnnualLookupKey: null,\n\t},\n\tlaunch: {\n\t\tid: \"launch\",\n\t\tdisplayName: \"Launch\",\n\t\tmonthlyPriceCents: 9_900, // $99\n\t\tannualPriceCents: 99_000, // 2 months free\n\t\ttotalCpus: 2,\n\t\ttotalMemoryMb: 6_144,\n\t\tstorageLimitMb: 102_400, // 100 GB\n\t\tcontainers: alloc(6_144, 2),\n\t\ttagline: \"Real product\",\n\t\tfeatures: [\n\t\t\t\"2 vCPU · 6 GB RAM\",\n\t\t\t\"100 GB storage · always-on\",\n\t\t\t\"3-5 contracts\",\n\t\t\t\"Production reindex windows\",\n\t\t\t\"Spend caps + alerts\",\n\t\t\t\"Email support\",\n\t\t],\n\t\tstripeLookupKey: \"secondlayer_launch_monthly\",\n\t\tstripeAnnualLookupKey: \"secondlayer_launch_yearly\",\n\t},\n\tscale: {\n\t\tid: \"scale\",\n\t\tdisplayName: \"Scale\",\n\t\tmonthlyPriceCents: 29_900, // $299\n\t\tannualPriceCents: 299_000, // 2 months free\n\t\ttotalCpus: 8,\n\t\ttotalMemoryMb: 24_576,\n\t\tstorageLimitMb: 512_000, // 500 GB\n\t\tcontainers: alloc(24_576, 8),\n\t\ttagline: \"Full indexing\",\n\t\tfeatures: [\n\t\t\t\"8 vCPU · 24 GB RAM\",\n\t\t\t\"500 GB storage · always-on\",\n\t\t\t\"Heavy history + replay\",\n\t\t\t\"24h SLA · priority support\",\n\t\t],\n\t\tstripeLookupKey: \"secondlayer_scale_monthly\",\n\t\tstripeAnnualLookupKey: \"secondlayer_scale_yearly\",\n\t},\n\tenterprise: {\n\t\tid: \"enterprise\",\n\t\tdisplayName: \"Enterprise\",\n\t\tmonthlyPriceCents: null,\n\t\tannualPriceCents: null,\n\t\ttotalCpus: 16,\n\t\ttotalMemoryMb: 65_536,\n\t\tstorageLimitMb: -1,\n\t\tcontainers: alloc(65_536, 16),\n\t\ttagline: \"Whatever needed\",\n\t\tfeatures: [\n\t\t\t\"Custom compute + storage\",\n\t\t\t\"SLAs · regions · SSO\",\n\t\t\t\"Dedicated success engineer\",\n\t\t],\n\t\tstripeLookupKey: null,\n\t\tstripeAnnualLookupKey: null,\n\t},\n};\n\nexport const PLAN_IDS: readonly PlanId[] = [\n\t\"hobby\",\n\t\"launch\",\n\t\"scale\",\n\t\"enterprise\",\n];\n\nexport function getPlan(id: string): Plan {\n\tconst plan = (PLANS as Record<string, Plan | undefined>)[id];\n\tif (!plan) throw new Error(`Unknown plan: ${id}`);\n\treturn plan;\n}\n\nexport function isValidPlanId(id: string): id is PlanId {\n\treturn id in PLANS;\n}\n\n// ── Allowance helpers (used by /api/accounts/usage display) ─────────\n//\n// Compute is hard-capped by Docker `--cpus`, so there's no compute\n// overage billing. The function below returns ∞ for paid plans (display-\n// only — no metering).\n//\n// Storage IS metered and billed past the plan's allowance via the\n// `storage_gb_months` Stripe meter at $2/GB-mo.\n\nexport function getComputeAllowanceHours(_plan: string): number {\n\t// Compute overage was killed when we removed the `compute_hours` meter.\n\t// All plans are now hard-capped by Docker `--cpus`. ∞ here means \"no\n\t// overage tracked\" for display purposes.\n\treturn Number.POSITIVE_INFINITY;\n}\n\nexport function getStorageAllowanceBytes(plan: string): number {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\tif (!planDef) return PLANS.hobby.storageLimitMb * 1024 * 1024;\n\tif (planDef.storageLimitMb < 0) return Number.POSITIVE_INFINITY;\n\treturn planDef.storageLimitMb * 1024 * 1024;\n}\n\n/** Hobby has a hard cap (no overage billing); paid tiers bill $2/GB over allowance. */\nexport function hasStorageOverage(plan: string): boolean {\n\treturn plan !== \"hobby\" && plan !== \"enterprise\";\n}\n\nexport function getBasePriceCents(plan: string): number {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\treturn planDef?.monthlyPriceCents ?? 0;\n}\n\nexport function getPlanDisplayName(plan: string): string {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\treturn planDef?.displayName ?? plan.charAt(0).toUpperCase() + plan.slice(1);\n}\n\n// Re-export bytes-per-GB constant for callers that compute display values.\nexport { BYTES_PER_GB };\n"
5
+ "/**\n * Single source of truth for plan tiers — capacity, price, display copy,\n * Stripe binding, and container allocations.\n *\n * Consumed by:\n * - Provisioner (`packages/provisioner/src/plans.ts` re-exports)\n * - API (`/api/accounts/usage` for allowance math + display)\n * - Web app (`/billing` page renders plan cards from this)\n *\n * Adding a tier? Add an entry to PLANS. Removing one? Drop here, run\n * the Stripe-side cleanup (archive lookup_key), and update env vars.\n */\n\nconst BYTES_PER_GB: number = 1024 ** 3;\n\nexport type AccountPlanId = \"none\" | PlanId;\nexport type PlanId = \"launch\" | \"scale\" | \"enterprise\";\n\nexport interface ContainerAlloc {\n\tmemoryMb: number;\n\tcpus: number;\n}\n\nexport interface Plan {\n\tid: PlanId;\n\tdisplayName: string;\n\t/** Monthly subscription price in cents. null = custom (Enterprise). */\n\tmonthlyPriceCents: number | null;\n\t/** Annual subscription price in cents. null = no self-serve annual price. */\n\tannualPriceCents: number | null;\n\ttotalCpus: number;\n\ttotalMemoryMb: number;\n\t/** Hard cap. -1 = unlimited (Enterprise). Storage overage bills past this. */\n\tstorageLimitMb: number;\n\tcontainers: {\n\t\tpostgres: ContainerAlloc;\n\t\tapi: ContainerAlloc;\n\t\tprocessor: ContainerAlloc;\n\t};\n\t/** Display-only. Marketing/short pitch. */\n\ttagline: string;\n\t/** Display-only. Bullet list on the plan card. */\n\tfeatures: string[];\n\t/** Stripe `lookup_key` for monthly recurring tier price. null for enterprise. */\n\tstripeLookupKey: string | null;\n\t/** Stripe `lookup_key` for annual recurring tier price. null for enterprise. */\n\tstripeAnnualLookupKey: string | null;\n}\n\n// ── Allocation helpers ──────────────────────────────────────────────\n//\n// Allocation within a plan (3 containers per tenant):\n// Default split (paid tiers) — PG 50% / proc 30% / api 20%\n// Sub-1GB total — PG 60% / proc 25% / api 15%\n//\n// Docker memory limit is a hard cap (OOM kill on overage). CPU is a soft\n// cap via `--cpus` (throttling, not killing). Storage is monitored\n// separately and billed as overage — PG crashes if we hard-cap it.\n\nfunction alloc(totalMb: number, totalCpus: number): Plan[\"containers\"] {\n\treturn {\n\t\tpostgres: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.5),\n\t\t\tcpus: round2(totalCpus * 0.5),\n\t\t},\n\t\tprocessor: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.3),\n\t\t\tcpus: round2(totalCpus * 0.3),\n\t\t},\n\t\tapi: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.2),\n\t\t\tcpus: round2(totalCpus * 0.2),\n\t\t},\n\t};\n}\n\nfunction allocTight(totalMb: number, totalCpus: number): Plan[\"containers\"] {\n\treturn {\n\t\tpostgres: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.6),\n\t\t\tcpus: round2(totalCpus * 0.6),\n\t\t},\n\t\tprocessor: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.25),\n\t\t\tcpus: round2(totalCpus * 0.25),\n\t\t},\n\t\tapi: {\n\t\t\tmemoryMb: Math.floor(totalMb * 0.15),\n\t\t\tcpus: round2(totalCpus * 0.15),\n\t\t},\n\t};\n}\n\nfunction round2(n: number): number {\n\treturn Math.round(n * 100) / 100;\n}\n\n/**\n * Split a compute envelope across (postgres, processor, api) containers.\n * Auto-biases PG-heavy (60/25/15) for sub-1GB totals.\n */\nexport function allocForTotals(\n\ttotalMemoryMb: number,\n\ttotalCpus: number,\n): Plan[\"containers\"] {\n\treturn totalMemoryMb < 1024\n\t\t? allocTight(totalMemoryMb, totalCpus)\n\t\t: alloc(totalMemoryMb, totalCpus);\n}\n\n// ── Canonical plan data ─────────────────────────────────────────────\n\nexport const PLANS: Record<PlanId, Plan> = {\n\tlaunch: {\n\t\tid: \"launch\",\n\t\tdisplayName: \"Launch\",\n\t\tmonthlyPriceCents: 9_900, // $99\n\t\tannualPriceCents: 99_000, // 2 months free\n\t\ttotalCpus: 2,\n\t\ttotalMemoryMb: 6_144,\n\t\tstorageLimitMb: 102_400, // 100 GB\n\t\tcontainers: alloc(6_144, 2),\n\t\ttagline: \"Real product\",\n\t\tfeatures: [\n\t\t\t\"2 vCPU · 6 GB RAM\",\n\t\t\t\"100 GB storage · always-on\",\n\t\t\t\"3-5 contracts\",\n\t\t\t\"Production reindex windows\",\n\t\t\t\"Spend caps + alerts\",\n\t\t\t\"Email support\",\n\t\t],\n\t\tstripeLookupKey: \"secondlayer_launch_monthly\",\n\t\tstripeAnnualLookupKey: \"secondlayer_launch_yearly\",\n\t},\n\tscale: {\n\t\tid: \"scale\",\n\t\tdisplayName: \"Scale\",\n\t\tmonthlyPriceCents: 29_900, // $299\n\t\tannualPriceCents: 299_000, // 2 months free\n\t\ttotalCpus: 8,\n\t\ttotalMemoryMb: 24_576,\n\t\tstorageLimitMb: 512_000, // 500 GB\n\t\tcontainers: alloc(24_576, 8),\n\t\ttagline: \"Full indexing\",\n\t\tfeatures: [\n\t\t\t\"8 vCPU · 24 GB RAM\",\n\t\t\t\"500 GB storage · always-on\",\n\t\t\t\"Heavy history + replay\",\n\t\t\t\"24h SLA · priority support\",\n\t\t],\n\t\tstripeLookupKey: \"secondlayer_scale_monthly\",\n\t\tstripeAnnualLookupKey: \"secondlayer_scale_yearly\",\n\t},\n\tenterprise: {\n\t\tid: \"enterprise\",\n\t\tdisplayName: \"Enterprise\",\n\t\tmonthlyPriceCents: null,\n\t\tannualPriceCents: null,\n\t\ttotalCpus: 16,\n\t\ttotalMemoryMb: 65_536,\n\t\tstorageLimitMb: -1,\n\t\tcontainers: alloc(65_536, 16),\n\t\ttagline: \"Whatever needed\",\n\t\tfeatures: [\n\t\t\t\"Custom compute + storage\",\n\t\t\t\"SLAs · regions · SSO\",\n\t\t\t\"Dedicated success engineer\",\n\t\t],\n\t\tstripeLookupKey: null,\n\t\tstripeAnnualLookupKey: null,\n\t},\n};\n\nexport const PLAN_IDS: readonly PlanId[] = [\"launch\", \"scale\", \"enterprise\"];\n\nexport function getPlan(id: string): Plan {\n\tconst plan = (PLANS as Record<string, Plan | undefined>)[id];\n\tif (!plan) throw new Error(`Unknown plan: ${id}`);\n\treturn plan;\n}\n\nexport function isValidPlanId(id: string): id is PlanId {\n\treturn id in PLANS;\n}\n\n// ── Allowance helpers (used by /api/accounts/usage display) ─────────\n//\n// Compute is hard-capped by Docker `--cpus`, so there's no compute\n// overage billing. The function below returns ∞ for paid plans (display-\n// only — no metering).\n//\n// Storage IS metered and billed past the plan's allowance via the\n// `storage_gb_months` Stripe meter at $2/GB-mo.\n\nexport function getComputeAllowanceHours(_plan: string): number {\n\t// Compute overage was killed when we removed the `compute_hours` meter.\n\t// All plans are now hard-capped by Docker `--cpus`. ∞ here means \"no\n\t// overage tracked\" for display purposes.\n\treturn Number.POSITIVE_INFINITY;\n}\n\nexport function getStorageAllowanceBytes(plan: string): number {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\tif (!planDef) return 0;\n\tif (planDef.storageLimitMb < 0) return Number.POSITIVE_INFINITY;\n\treturn planDef.storageLimitMb * 1024 * 1024;\n}\n\n/** Paid tiers bill $2/GB over allowance. Accounts with no plan do not accrue overage. */\nexport function hasStorageOverage(plan: string): boolean {\n\treturn plan !== \"none\" && plan !== \"enterprise\";\n}\n\nexport function getBasePriceCents(plan: string): number {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\treturn planDef?.monthlyPriceCents ?? 0;\n}\n\nexport function getPlanDisplayName(plan: string): string {\n\tconst planDef = (PLANS as Record<string, Plan | undefined>)[plan];\n\treturn planDef?.displayName ?? plan.charAt(0).toUpperCase() + plan.slice(1);\n}\n\n// Re-export bytes-per-GB constant for callers that compute display values.\nexport { BYTES_PER_GB };\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "176BF41B28FD6A0464756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "D52F48DDE4C1BD2764756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -182,9 +182,9 @@ interface SubgraphSyncInfo {
182
182
  integrity: "complete" | "gaps_detected";
183
183
  }
184
184
  interface SubgraphResourceWarning {
185
- code: "HOBBY_LARGE_REINDEX";
185
+ code: string;
186
186
  message: string;
187
- plan: "hobby";
187
+ plan?: string;
188
188
  blockRange: number;
189
189
  processorMemoryMb: number;
190
190
  recommendedPlan: "launch";