@secondlayer/shared 2.1.0 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +2 -2
  2. package/dist/src/crypto/secrets.js +47 -3
  3. package/dist/src/crypto/secrets.js.map +5 -4
  4. package/dist/src/db/index.d.ts +112 -137
  5. package/dist/src/db/index.js.map +2 -2
  6. package/dist/src/db/jsonb.d.ts +5 -1
  7. package/dist/src/db/jsonb.js.map +2 -2
  8. package/dist/src/db/queries/account-spend-caps.d.ts +444 -0
  9. package/dist/src/db/queries/account-spend-caps.js +60 -0
  10. package/dist/src/db/queries/account-spend-caps.js.map +10 -0
  11. package/dist/src/db/queries/account-usage.d.ts +468 -0
  12. package/dist/src/db/queries/account-usage.js +222 -0
  13. package/dist/src/db/queries/account-usage.js.map +11 -0
  14. package/dist/src/db/queries/accounts.d.ts +100 -109
  15. package/dist/src/db/queries/accounts.js +15 -1
  16. package/dist/src/db/queries/accounts.js.map +3 -3
  17. package/dist/src/db/queries/integrity.d.ts +85 -107
  18. package/dist/src/db/queries/projects.d.ts +87 -109
  19. package/dist/src/db/queries/provisioning-audit.d.ts +85 -107
  20. package/dist/src/db/queries/subgraph-gaps.d.ts +85 -107
  21. package/dist/src/db/queries/subgraphs.d.ts +86 -109
  22. package/dist/src/db/queries/subgraphs.js +2 -3
  23. package/dist/src/db/queries/subgraphs.js.map +4 -4
  24. package/dist/src/db/queries/{workflows.d.ts → tenant-compute-addons.d.ts} +108 -142
  25. package/dist/src/db/queries/tenant-compute-addons.js +47 -0
  26. package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
  27. package/dist/src/db/queries/tenants.d.ts +98 -110
  28. package/dist/src/db/queries/tenants.js +55 -8
  29. package/dist/src/db/queries/tenants.js.map +6 -5
  30. package/dist/src/db/queries/usage.d.ts +86 -132
  31. package/dist/src/db/queries/usage.js +5 -64
  32. package/dist/src/db/queries/usage.js.map +4 -5
  33. package/dist/src/db/schema.d.ts +107 -136
  34. package/dist/src/errors.d.ts +8 -7
  35. package/dist/src/errors.js +11 -12
  36. package/dist/src/errors.js.map +3 -3
  37. package/dist/src/index.d.ts +119 -143
  38. package/dist/src/index.js +11 -12
  39. package/dist/src/index.js.map +4 -4
  40. package/dist/src/node/local-client.d.ts +85 -107
  41. package/dist/src/pricing.d.ts +20 -1
  42. package/dist/src/pricing.js +58 -1
  43. package/dist/src/pricing.js.map +3 -3
  44. package/migrations/0045_drop_marketplace_columns.ts +47 -0
  45. package/migrations/0046_tenant_activity_signal.ts +47 -0
  46. package/migrations/0047_usage_daily_tenant_id.ts +73 -0
  47. package/migrations/0048_tenant_compute_addons.ts +49 -0
  48. package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
  49. package/migrations/0050_account_spend_caps.ts +45 -0
  50. package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
  51. package/migrations/0052_sentries.ts +61 -0
  52. package/migrations/0053_workflow_runtime.ts +88 -0
  53. package/migrations/0054_accounts_plan_hobby.ts +32 -0
  54. package/migrations/0055_ai_usage_account_scope.ts +108 -0
  55. package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
  56. package/migrations/0057_subscriptions.ts +137 -0
  57. package/package.json +26 -14
  58. package/dist/src/db/queries/workflows.js +0 -260
  59. package/dist/src/db/queries/workflows.js.map +0 -12
  60. package/dist/src/lib/plans.d.ts +0 -9
  61. package/dist/src/lib/plans.js +0 -37
  62. package/dist/src/lib/plans.js.map +0 -10
  63. package/dist/src/schemas/workflows.d.ts +0 -70
  64. package/dist/src/schemas/workflows.js +0 -43
  65. package/dist/src/schemas/workflows.js.map +0 -10
@@ -1,10 +1,3 @@
1
- interface PlanLimits {
2
- subgraphs: number;
3
- apiRequestsPerDay: number;
4
- deliveriesPerMonth: number;
5
- storageBytes: number;
6
- }
7
- declare function getPlanLimits(plan: string): PlanLimits;
8
1
  import { Kysely } from "kysely";
9
2
  import { ColumnType, Generated } from "kysely";
10
3
  interface BlocksTable {
@@ -67,10 +60,6 @@ interface SubgraphsTable {
67
60
  handler_code: string | null;
68
61
  source_code: string | null;
69
62
  project_id: string | null;
70
- is_public: Generated<boolean>;
71
- tags: Generated<string[]>;
72
- description: string | null;
73
- forked_from_id: string | null;
74
63
  created_at: Generated<Date>;
75
64
  updated_at: Generated<Date>;
76
65
  }
@@ -105,6 +94,7 @@ interface AccountsTable {
105
94
  bio: string | null;
106
95
  avatar_url: string | null;
107
96
  slug: string | null;
97
+ stripe_customer_id: string | null;
108
98
  created_at: Generated<Date>;
109
99
  }
110
100
  interface SessionsTable {
@@ -130,6 +120,7 @@ interface MagicLinksTable {
130
120
  }
131
121
  interface UsageDailyTable {
132
122
  account_id: string;
123
+ tenant_id: string | null;
133
124
  date: string;
134
125
  api_requests: Generated<number>;
135
126
  deliveries: Generated<number>;
@@ -256,83 +247,6 @@ interface ChatMessagesTable {
256
247
  metadata: unknown | null;
257
248
  created_at: Generated<Date>;
258
249
  }
259
- interface WorkflowDefinitionsTable {
260
- id: Generated<string>;
261
- name: string;
262
- version: Generated<string>;
263
- status: Generated<string>;
264
- trigger_type: string;
265
- trigger_config: unknown;
266
- handler_path: string;
267
- source_code: string | null;
268
- retries_config: unknown | null;
269
- timeout_ms: number | null;
270
- api_key_id: string;
271
- project_id: string | null;
272
- created_at: Generated<Date>;
273
- updated_at: Generated<Date>;
274
- }
275
- interface WorkflowRunsTable {
276
- id: Generated<string>;
277
- definition_id: string;
278
- status: Generated<string>;
279
- trigger_type: string;
280
- trigger_data: unknown | null;
281
- dedup_key: string | null;
282
- error: string | null;
283
- started_at: Date | null;
284
- completed_at: Date | null;
285
- duration_ms: number | null;
286
- total_ai_tokens: Generated<number>;
287
- created_at: Generated<Date>;
288
- }
289
- interface WorkflowStepsTable {
290
- id: Generated<string>;
291
- run_id: string;
292
- step_index: number;
293
- step_id: string;
294
- step_type: string;
295
- status: Generated<string>;
296
- input: unknown | null;
297
- output: unknown | null;
298
- error: string | null;
299
- retry_count: Generated<number>;
300
- ai_tokens_used: Generated<number>;
301
- started_at: Date | null;
302
- completed_at: Date | null;
303
- duration_ms: number | null;
304
- memo_key: string | null;
305
- parent_step_id: string | null;
306
- created_at: Generated<Date>;
307
- }
308
- interface WorkflowQueueTable {
309
- id: Generated<string>;
310
- run_id: string;
311
- status: Generated<string>;
312
- attempts: Generated<number>;
313
- max_attempts: Generated<number>;
314
- scheduled_for: Generated<Date>;
315
- locked_at: Date | null;
316
- locked_by: string | null;
317
- error: string | null;
318
- created_at: Generated<Date>;
319
- completed_at: Date | null;
320
- }
321
- interface WorkflowSchedulesTable {
322
- id: Generated<string>;
323
- definition_id: string;
324
- cron_expr: string;
325
- timezone: Generated<string>;
326
- next_run_at: Date;
327
- last_run_at: Date | null;
328
- enabled: Generated<boolean>;
329
- created_at: Generated<Date>;
330
- }
331
- interface WorkflowCursorsTable {
332
- name: string;
333
- block_height: Generated<number>;
334
- updated_at: Generated<Date>;
335
- }
336
250
  interface Database {
337
251
  blocks: BlocksTable;
338
252
  transactions: TransactionsTable;
@@ -358,17 +272,14 @@ interface Database {
358
272
  team_invitations: TeamInvitationsTable;
359
273
  chat_sessions: ChatSessionsTable;
360
274
  chat_messages: ChatMessagesTable;
361
- workflow_definitions: WorkflowDefinitionsTable;
362
- workflow_runs: WorkflowRunsTable;
363
- workflow_steps: WorkflowStepsTable;
364
- workflow_queue: WorkflowQueueTable;
365
- workflow_schedules: WorkflowSchedulesTable;
366
- workflow_cursors: WorkflowCursorsTable;
367
- workflow_signer_secrets: WorkflowSignerSecretsTable;
368
- workflow_budgets: WorkflowBudgetsTable;
369
275
  tenants: TenantsTable;
370
276
  tenant_usage_monthly: TenantUsageMonthlyTable;
277
+ tenant_compute_addons: TenantComputeAddonsTable;
278
+ account_spend_caps: AccountSpendCapsTable;
371
279
  provisioning_audit_log: ProvisioningAuditLogTable;
280
+ subscriptions: SubscriptionsTable;
281
+ subscription_outbox: SubscriptionOutboxTable;
282
+ subscription_deliveries: SubscriptionDeliveriesTable;
372
283
  }
373
284
  type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
374
285
  interface TenantsTable {
@@ -390,9 +301,9 @@ interface TenantsTable {
390
301
  service_key_enc: Buffer;
391
302
  api_url_internal: string;
392
303
  api_url_public: string;
393
- trial_ends_at: Date;
394
304
  suspended_at: Date | null;
395
305
  last_health_check_at: Date | null;
306
+ last_active_at: Generated<Date>;
396
307
  service_gen: Generated<number>;
397
308
  anon_gen: Generated<number>;
398
309
  project_id: string | null;
@@ -410,6 +321,28 @@ interface TenantUsageMonthlyTable {
410
321
  first_at: Generated<Date>;
411
322
  last_at: Generated<Date>;
412
323
  }
324
+ interface TenantComputeAddonsTable {
325
+ id: Generated<string>;
326
+ tenant_id: string;
327
+ memory_mb_delta: Generated<number>;
328
+ cpu_delta: Generated<number | string>;
329
+ storage_mb_delta: Generated<number>;
330
+ effective_from: Generated<Date>;
331
+ effective_until: Date | null;
332
+ stripe_subscription_item_id: string | null;
333
+ created_at: Generated<Date>;
334
+ }
335
+ interface AccountSpendCapsTable {
336
+ account_id: string;
337
+ monthly_cap_cents: number | null;
338
+ compute_cap_cents: number | null;
339
+ storage_cap_cents: number | null;
340
+ ai_cap_cents: number | null;
341
+ alert_threshold_pct: Generated<number>;
342
+ alert_sent_at: Date | null;
343
+ frozen_at: Date | null;
344
+ updated_at: Generated<Date>;
345
+ }
413
346
  type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
414
347
  type ProvisioningAuditStatus = "ok" | "error";
415
348
  interface ProvisioningAuditLogTable {
@@ -424,29 +357,67 @@ interface ProvisioningAuditLogTable {
424
357
  error: string | null;
425
358
  created_at: Generated<Date>;
426
359
  }
427
- interface WorkflowBudgetsTable {
360
+ type SubscriptionStatus = "active" | "paused" | "error";
361
+ type SubscriptionFormat = "standard-webhooks" | "inngest" | "trigger" | "cloudflare" | "cloudevents" | "raw";
362
+ type SubscriptionRuntime = "inngest" | "trigger" | "cloudflare" | "node";
363
+ interface SubscriptionsTable {
428
364
  id: Generated<string>;
429
- workflow_definition_id: string;
430
- /** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
431
- period: string;
432
- ai_usd_used: Generated<string>;
433
- ai_tokens_used: Generated<string>;
434
- chain_microstx_used: Generated<string>;
435
- chain_tx_count: Generated<number>;
436
- run_count: Generated<number>;
437
- step_count: Generated<number>;
438
- reset_at: Date;
365
+ account_id: string;
366
+ project_id: string | null;
367
+ name: string;
368
+ status: ColumnType<SubscriptionStatus, SubscriptionStatus | undefined, SubscriptionStatus>;
369
+ subgraph_name: string;
370
+ table_name: string;
371
+ filter: Generated<unknown>;
372
+ format: ColumnType<SubscriptionFormat, SubscriptionFormat | undefined, SubscriptionFormat>;
373
+ runtime: SubscriptionRuntime | null;
374
+ url: string;
375
+ signing_secret_enc: Buffer;
376
+ auth_config: Generated<unknown>;
377
+ max_retries: Generated<number>;
378
+ timeout_ms: Generated<number>;
379
+ concurrency: Generated<number>;
380
+ circuit_failures: Generated<number>;
381
+ circuit_opened_at: Date | null;
382
+ last_delivery_at: Date | null;
383
+ last_success_at: Date | null;
384
+ last_error: string | null;
439
385
  created_at: Generated<Date>;
440
386
  updated_at: Generated<Date>;
441
387
  }
442
- interface WorkflowSignerSecretsTable {
388
+ type OutboxStatus = "pending" | "delivered" | "dead";
389
+ interface SubscriptionOutboxTable {
443
390
  id: Generated<string>;
444
- account_id: string;
445
- name: string;
446
- /** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
447
- encrypted_value: Buffer;
391
+ subscription_id: string;
392
+ subgraph_name: string;
393
+ table_name: string;
394
+ block_height: number | bigint;
395
+ tx_id: string | null;
396
+ row_pk: unknown;
397
+ event_type: string;
398
+ payload: unknown;
399
+ dedup_key: string;
400
+ attempt: Generated<number>;
401
+ next_attempt_at: Generated<Date>;
402
+ status: ColumnType<OutboxStatus, OutboxStatus | undefined, OutboxStatus>;
403
+ is_replay: Generated<boolean>;
404
+ delivered_at: Date | null;
405
+ failed_at: Date | null;
406
+ locked_by: string | null;
407
+ locked_until: Date | null;
448
408
  created_at: Generated<Date>;
449
- updated_at: Generated<Date>;
409
+ }
410
+ interface SubscriptionDeliveriesTable {
411
+ id: Generated<string>;
412
+ outbox_id: string;
413
+ subscription_id: string;
414
+ attempt: number;
415
+ status_code: number | null;
416
+ response_headers: unknown | null;
417
+ response_body: string | null;
418
+ error_message: string | null;
419
+ duration_ms: number | null;
420
+ dispatched_at: Generated<Date>;
450
421
  }
451
422
  /** Increment API request counter for today. Fire-and-forget safe. */
452
423
  declare function incrementApiRequests(db: Kysely<Database>, accountId: string): Promise<void>;
@@ -457,26 +428,9 @@ interface UsageSummary {
457
428
  }
458
429
  /** Get current usage for an account. */
459
430
  declare function getUsage(db: Kysely<Database>, accountId: string): Promise<UsageSummary>;
460
- interface DailyUsage {
461
- date: string;
462
- apiRequests: number;
463
- deliveries: number;
464
- }
465
- /** Get last 7 days of daily usage, filling missing days with 0. */
466
- declare function getDailyUsage(db: Kysely<Database>, accountId: string): Promise<DailyUsage[]>;
467
- interface LimitCheck {
468
- allowed: boolean;
469
- limits: ReturnType<typeof getPlanLimits>;
470
- current: UsageSummary & {
471
- subgraphs: number
472
- };
473
- exceeded?: string;
474
- }
475
- /** Check if an account is within plan limits. */
476
- declare function checkLimits(db: Kysely<Database>, accountId: string, plan: string): Promise<LimitCheck>;
477
431
  /**
478
432
  * Measure storage for all accounts by querying pg_total_relation_size
479
433
  * for each tenant's subgraph schemas.
480
434
  */
481
435
  declare function measureStorage(db: Kysely<Database>): Promise<void>;
482
- export { measureStorage, incrementApiRequests, getUsage, getDailyUsage, checkLimits, UsageSummary, LimitCheck, DailyUsage };
436
+ export { measureStorage, incrementApiRequests, getUsage, UsageSummary };
@@ -14,21 +14,6 @@ var __export = (target, all) => {
14
14
  });
15
15
  };
16
16
 
17
- // src/lib/plans.ts
18
- var FREE_PLAN = {
19
- subgraphs: 2,
20
- apiRequestsPerDay: 1000,
21
- deliveriesPerMonth: 5000,
22
- storageBytes: 100 * 1024 * 1024
23
- };
24
- function getPlanLimits(plan) {
25
- switch (plan) {
26
- case "free":
27
- default:
28
- return FREE_PLAN;
29
- }
30
- }
31
-
32
17
  // src/db/queries/usage.ts
33
18
  import { sql } from "kysely";
34
19
  async function incrementApiRequests(db, accountId) {
@@ -44,7 +29,7 @@ async function incrementApiRequests(db, accountId) {
44
29
  }
45
30
  async function getUsage(db, accountId) {
46
31
  const today = new Date().toISOString().slice(0, 10);
47
- const monthStart = today.slice(0, 7) + "-01";
32
+ const monthStart = `${today.slice(0, 7)}-01`;
48
33
  const dailyRow = await db.selectFrom("usage_daily").select("api_requests").where("account_id", "=", accountId).where("date", "=", today).executeTakeFirst();
49
34
  const monthlyRow = await db.selectFrom("usage_daily").select(sql`COALESCE(SUM(deliveries), 0)`.as("total")).where("account_id", "=", accountId).where("date", ">=", monthStart).executeTakeFirst();
50
35
  const storageRow = await db.selectFrom("usage_snapshots").select("storage_bytes").where("account_id", "=", accountId).orderBy("measured_at", "desc").limit(1).executeTakeFirst();
@@ -54,49 +39,6 @@ async function getUsage(db, accountId) {
54
39
  storageBytes: Number(storageRow?.storage_bytes ?? 0)
55
40
  };
56
41
  }
57
- async function getDailyUsage(db, accountId) {
58
- const rows = await db.selectFrom("usage_daily").select(["date", "api_requests", "deliveries"]).where("account_id", "=", accountId).where("date", ">=", sql`NOW()::date - 6`).orderBy("date", "asc").execute();
59
- const byDate = new Map(rows.map((r) => {
60
- const d = r.date;
61
- const key = d instanceof Date ? d.toISOString().slice(0, 10) : String(d).slice(0, 10);
62
- return [key, r];
63
- }));
64
- const result = [];
65
- for (let i = 6;i >= 0; i--) {
66
- const d = new Date;
67
- d.setDate(d.getDate() - i);
68
- const dateStr = d.toISOString().slice(0, 10);
69
- const row = byDate.get(dateStr);
70
- result.push({
71
- date: dateStr,
72
- apiRequests: row?.api_requests ?? 0,
73
- deliveries: row?.deliveries ?? 0
74
- });
75
- }
76
- return result;
77
- }
78
- async function checkLimits(db, accountId, plan) {
79
- const limits = getPlanLimits(plan);
80
- const usage = await getUsage(db, accountId);
81
- const subgraphCount = await db.selectFrom("subgraphs").select(sql`count(*)`.as("count")).where("account_id", "=", accountId).executeTakeFirst();
82
- const current = {
83
- ...usage,
84
- subgraphs: Number(subgraphCount?.count ?? 0)
85
- };
86
- if (current.subgraphs >= limits.subgraphs) {
87
- return { allowed: false, limits, current, exceeded: "subgraphs" };
88
- }
89
- if (current.apiRequestsToday >= limits.apiRequestsPerDay) {
90
- return { allowed: false, limits, current, exceeded: "api_requests" };
91
- }
92
- if (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {
93
- return { allowed: false, limits, current, exceeded: "deliveries" };
94
- }
95
- if (current.storageBytes >= limits.storageBytes) {
96
- return { allowed: false, limits, current, exceeded: "storage" };
97
- }
98
- return { allowed: true, limits, current };
99
- }
100
42
  async function measureStorage(db) {
101
43
  const accountSubgraphs = await db.selectFrom("subgraphs").select(["account_id", "schema_name"]).where("schema_name", "is not", null).execute();
102
44
  const byAccount = new Map;
@@ -114,7 +56,8 @@ async function measureStorage(db) {
114
56
  SELECT COALESCE(SUM(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))), 0)::text as size
115
57
  FROM pg_tables WHERE schemaname = ${schema}
116
58
  `.execute(db);
117
- totalBytes += Number(result.rows[0]?.size ?? 0);
59
+ const row = result.rows[0];
60
+ totalBytes += Number(row?.size ?? 0);
118
61
  } catch {}
119
62
  }
120
63
  await db.insertInto("usage_snapshots").values({
@@ -126,10 +69,8 @@ async function measureStorage(db) {
126
69
  export {
127
70
  measureStorage,
128
71
  incrementApiRequests,
129
- getUsage,
130
- getDailyUsage,
131
- checkLimits
72
+ getUsage
132
73
  };
133
74
 
134
- //# debugId=7A7AEE74ECF0C63664756E2164756E21
75
+ //# debugId=93865CC393EF182964756E2164756E21
135
76
  //# sourceMappingURL=usage.js.map
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/lib/plans.ts", "../src/db/queries/usage.ts"],
3
+ "sources": ["../src/db/queries/usage.ts"],
4
4
  "sourcesContent": [
5
- "export interface PlanLimits {\n\tsubgraphs: number;\n\tapiRequestsPerDay: number;\n\tdeliveriesPerMonth: number;\n\tstorageBytes: number;\n}\n\nexport const FREE_PLAN: PlanLimits = {\n\tsubgraphs: 2,\n\tapiRequestsPerDay: 1_000,\n\tdeliveriesPerMonth: 5_000,\n\tstorageBytes: 100 * 1024 * 1024,\n};\n\nexport function getPlanLimits(plan: string): PlanLimits {\n\tswitch (plan) {\n\t\tcase \"free\":\n\t\tdefault:\n\t\t\treturn FREE_PLAN;\n\t}\n}\n",
6
- "import { type Kysely, sql } from \"kysely\";\nimport { getPlanLimits } from \"../../lib/plans.ts\";\nimport type { Database } from \"../types.ts\";\n\n/** Increment API request counter for today. Fire-and-forget safe. */\nexport async function incrementApiRequests(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<void> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tawait db\n\t\t.insertInto(\"usage_daily\")\n\t\t.values({\n\t\t\taccount_id: accountId,\n\t\t\tdate: today,\n\t\t\tapi_requests: 1,\n\t\t\tdeliveries: 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"account_id\", \"date\"]).doUpdateSet({\n\t\t\t\tapi_requests: sql`usage_daily.api_requests + 1`,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\nexport interface UsageSummary {\n\tapiRequestsToday: number;\n\tdeliveriesThisMonth: number;\n\tstorageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<UsageSummary> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tconst monthStart = today.slice(0, 7) + \"-01\"; // YYYY-MM-01\n\n\t// Today's API requests\n\tconst dailyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(\"api_requests\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \"=\", today)\n\t\t.executeTakeFirst();\n\n\t// This month's deliveries\n\tconst monthlyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", monthStart)\n\t\t.executeTakeFirst();\n\n\t// Latest storage snapshot\n\tconst storageRow = await db\n\t\t.selectFrom(\"usage_snapshots\")\n\t\t.select(\"storage_bytes\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.orderBy(\"measured_at\", \"desc\")\n\t\t.limit(1)\n\t\t.executeTakeFirst();\n\n\treturn {\n\t\tapiRequestsToday: dailyRow?.api_requests ?? 0,\n\t\tdeliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n\t\tstorageBytes: Number(storageRow?.storage_bytes ?? 0),\n\t};\n}\n\nexport interface DailyUsage {\n\tdate: string;\n\tapiRequests: number;\n\tdeliveries: number;\n}\n\n/** Get last 7 days of daily usage, filling missing days with 0. */\nexport async function getDailyUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<DailyUsage[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select([\"date\", \"api_requests\", \"deliveries\"])\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", sql<string>`NOW()::date - 6`)\n\t\t.orderBy(\"date\", \"asc\")\n\t\t.execute();\n\n\t// Fill missing days with 0 (normalize date to YYYY-MM-DD string)\n\tconst byDate = new Map(\n\t\trows.map((r) => {\n\t\t\tconst d = r.date as unknown;\n\t\t\tconst key =\n\t\t\t\td instanceof Date\n\t\t\t\t\t? d.toISOString().slice(0, 10)\n\t\t\t\t\t: String(d).slice(0, 10);\n\t\t\treturn [key, r];\n\t\t}),\n\t);\n\tconst result: DailyUsage[] = [];\n\tfor (let i = 6; i >= 0; i--) {\n\t\tconst d = new Date();\n\t\td.setDate(d.getDate() - i);\n\t\tconst dateStr = d.toISOString().slice(0, 10);\n\t\tconst row = byDate.get(dateStr);\n\t\tresult.push({\n\t\t\tdate: dateStr,\n\t\t\tapiRequests: row?.api_requests ?? 0,\n\t\t\tdeliveries: row?.deliveries ?? 0,\n\t\t});\n\t}\n\treturn result;\n}\n\nexport interface LimitCheck {\n\tallowed: boolean;\n\tlimits: ReturnType<typeof getPlanLimits>;\n\tcurrent: UsageSummary & { subgraphs: number };\n\texceeded?: string;\n}\n\n/** Check if an account is within plan limits. */\nexport async function checkLimits(\n\tdb: Kysely<Database>,\n\taccountId: string,\n\tplan: string,\n): Promise<LimitCheck> {\n\tconst limits = getPlanLimits(plan);\n\tconst usage = await getUsage(db, accountId);\n\n\tconst subgraphCount = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.select(sql<number>`count(*)`.as(\"count\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.executeTakeFirst();\n\n\tconst current = {\n\t\t...usage,\n\t\tsubgraphs: Number(subgraphCount?.count ?? 0),\n\t};\n\n\tif (current.subgraphs >= limits.subgraphs) {\n\t\treturn { allowed: false, limits, current, exceeded: \"subgraphs\" };\n\t}\n\tif (current.apiRequestsToday >= limits.apiRequestsPerDay) {\n\t\treturn { allowed: false, limits, current, exceeded: \"api_requests\" };\n\t}\n\tif (current.deliveriesThisMonth >= limits.deliveriesPerMonth) {\n\t\treturn { allowed: false, limits, current, exceeded: \"deliveries\" };\n\t}\n\tif (current.storageBytes >= limits.storageBytes) {\n\t\treturn { allowed: false, limits, current, exceeded: \"storage\" };\n\t}\n\n\treturn { allowed: true, limits, current };\n}\n\n/**\n * Measure storage for all accounts by querying pg_total_relation_size\n * for each tenant's subgraph schemas.\n */\nexport async function measureStorage(db: Kysely<Database>): Promise<void> {\n\t// Get all accounts with subgraphs\n\tconst accountSubgraphs = await db\n\t\t.selectFrom(\"subgraphs\")\n\t\t.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\ttotalBytes += Number((result.rows[0] as any)?.size ?? 0);\n\t\t\t} catch {\n\t\t\t\t// Schema may not exist\n\t\t\t}\n\t\t}\n\n\t\tawait db\n\t\t\t.insertInto(\"usage_snapshots\")\n\t\t\t.values({\n\t\t\t\taccount_id: accountId,\n\t\t\t\tstorage_bytes: totalBytes,\n\t\t\t})\n\t\t\t.execute();\n\t}\n}\n"
5
+ "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 db\n\t\t.insertInto(\"usage_daily\")\n\t\t.values({\n\t\t\taccount_id: accountId,\n\t\t\tdate: today,\n\t\t\tapi_requests: 1,\n\t\t\tdeliveries: 0,\n\t\t})\n\t\t.onConflict((oc) =>\n\t\t\toc.columns([\"account_id\", \"date\"]).doUpdateSet({\n\t\t\t\tapi_requests: sql`usage_daily.api_requests + 1`,\n\t\t\t}),\n\t\t)\n\t\t.execute();\n}\n\nexport interface UsageSummary {\n\tapiRequestsToday: number;\n\tdeliveriesThisMonth: number;\n\tstorageBytes: number;\n}\n\n/** Get current usage for an account. */\nexport async function getUsage(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<UsageSummary> {\n\tconst today = new Date().toISOString().slice(0, 10);\n\tconst monthStart = `${today.slice(0, 7)}-01`; // YYYY-MM-01\n\n\t// Today's API requests\n\tconst dailyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(\"api_requests\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \"=\", today)\n\t\t.executeTakeFirst();\n\n\t// This month's deliveries\n\tconst monthlyRow = await db\n\t\t.selectFrom(\"usage_daily\")\n\t\t.select(sql<number>`COALESCE(SUM(deliveries), 0)`.as(\"total\"))\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"date\", \">=\", monthStart)\n\t\t.executeTakeFirst();\n\n\t// Latest storage snapshot\n\tconst storageRow = await db\n\t\t.selectFrom(\"usage_snapshots\")\n\t\t.select(\"storage_bytes\")\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.orderBy(\"measured_at\", \"desc\")\n\t\t.limit(1)\n\t\t.executeTakeFirst();\n\n\treturn {\n\t\tapiRequestsToday: dailyRow?.api_requests ?? 0,\n\t\tdeliveriesThisMonth: Number(monthlyRow?.total ?? 0),\n\t\tstorageBytes: Number(storageRow?.storage_bytes ?? 0),\n\t};\n}\n\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"
7
6
  ],
8
- "mappings": "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",
9
- "debugId": "7A7AEE74ECF0C63664756E2164756E21",
7
+ "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAIA,eAAsB,oBAAoB,CACzC,IACA,WACgB;AAAA,EAChB,MAAM,QAAQ,IAAI,KAAK,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EAClD,MAAM,GACJ,WAAW,aAAa,EACxB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,cAAc;AAAA,IACd,YAAY;AAAA,EACb,CAAC,EACA,WAAW,CAAC,OACZ,GAAG,QAAQ,CAAC,cAAc,MAAM,CAAC,EAAE,YAAY;AAAA,IAC9C,cAAc;AAAA,EACf,CAAC,CACF,EACC,QAAQ;AAAA;AAUX,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": "93865CC393EF182964756E2164756E21",
10
9
  "names": []
11
10
  }