@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.
- package/README.md +2 -2
- package/dist/src/crypto/secrets.js +47 -3
- package/dist/src/crypto/secrets.js.map +5 -4
- package/dist/src/db/index.d.ts +112 -137
- package/dist/src/db/index.js.map +2 -2
- package/dist/src/db/jsonb.d.ts +5 -1
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/account-spend-caps.d.ts +444 -0
- package/dist/src/db/queries/account-spend-caps.js +60 -0
- package/dist/src/db/queries/account-spend-caps.js.map +10 -0
- package/dist/src/db/queries/account-usage.d.ts +468 -0
- package/dist/src/db/queries/account-usage.js +222 -0
- package/dist/src/db/queries/account-usage.js.map +11 -0
- package/dist/src/db/queries/accounts.d.ts +100 -109
- package/dist/src/db/queries/accounts.js +15 -1
- package/dist/src/db/queries/accounts.js.map +3 -3
- package/dist/src/db/queries/integrity.d.ts +85 -107
- package/dist/src/db/queries/projects.d.ts +87 -109
- package/dist/src/db/queries/provisioning-audit.d.ts +85 -107
- package/dist/src/db/queries/subgraph-gaps.d.ts +85 -107
- package/dist/src/db/queries/subgraphs.d.ts +86 -109
- package/dist/src/db/queries/subgraphs.js +2 -3
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/{workflows.d.ts → tenant-compute-addons.d.ts} +108 -142
- package/dist/src/db/queries/tenant-compute-addons.js +47 -0
- package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
- package/dist/src/db/queries/tenants.d.ts +98 -110
- package/dist/src/db/queries/tenants.js +55 -8
- package/dist/src/db/queries/tenants.js.map +6 -5
- package/dist/src/db/queries/usage.d.ts +86 -132
- package/dist/src/db/queries/usage.js +5 -64
- package/dist/src/db/queries/usage.js.map +4 -5
- package/dist/src/db/schema.d.ts +107 -136
- package/dist/src/errors.d.ts +8 -7
- package/dist/src/errors.js +11 -12
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +119 -143
- package/dist/src/index.js +11 -12
- package/dist/src/index.js.map +4 -4
- package/dist/src/node/local-client.d.ts +85 -107
- package/dist/src/pricing.d.ts +20 -1
- package/dist/src/pricing.js +58 -1
- package/dist/src/pricing.js.map +3 -3
- package/migrations/0045_drop_marketplace_columns.ts +47 -0
- package/migrations/0046_tenant_activity_signal.ts +47 -0
- package/migrations/0047_usage_daily_tenant_id.ts +73 -0
- package/migrations/0048_tenant_compute_addons.ts +49 -0
- package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
- package/migrations/0050_account_spend_caps.ts +45 -0
- package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
- package/migrations/0052_sentries.ts +61 -0
- package/migrations/0053_workflow_runtime.ts +88 -0
- package/migrations/0054_accounts_plan_hobby.ts +32 -0
- package/migrations/0055_ai_usage_account_scope.ts +108 -0
- package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
- package/migrations/0057_subscriptions.ts +137 -0
- package/package.json +26 -14
- package/dist/src/db/queries/workflows.js +0 -260
- package/dist/src/db/queries/workflows.js.map +0 -12
- package/dist/src/lib/plans.d.ts +0 -9
- package/dist/src/lib/plans.js +0 -37
- package/dist/src/lib/plans.js.map +0 -10
- package/dist/src/schemas/workflows.d.ts +0 -70
- package/dist/src/schemas/workflows.js +0 -43
- 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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
388
|
+
type OutboxStatus = "pending" | "delivered" | "dead";
|
|
389
|
+
interface SubscriptionOutboxTable {
|
|
443
390
|
id: Generated<string>;
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
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,
|
|
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)
|
|
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
|
-
|
|
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=
|
|
75
|
+
//# debugId=93865CC393EF182964756E2164756E21
|
|
135
76
|
//# sourceMappingURL=usage.js.map
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/
|
|
3
|
+
"sources": ["../src/db/queries/usage.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"
|
|
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": ";;;;;;;;;;;;;;;;;
|
|
9
|
-
"debugId": "
|
|
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
|
}
|