@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.
- package/dist/src/db/index.d.ts +45 -1
- package/dist/src/db/queries/account-spend-caps.d.ts +44 -0
- package/dist/src/db/queries/account-usage.d.ts +44 -0
- package/dist/src/db/queries/account-usage.js +4 -29
- package/dist/src/db/queries/account-usage.js.map +3 -3
- package/dist/src/db/queries/accounts.d.ts +44 -0
- package/dist/src/db/queries/chain-reorgs.d.ts +532 -0
- package/dist/src/db/queries/chain-reorgs.js +322 -0
- package/dist/src/db/queries/chain-reorgs.js.map +14 -0
- package/dist/src/db/queries/integrity.d.ts +44 -0
- package/dist/src/db/queries/projects.d.ts +44 -0
- package/dist/src/db/queries/provisioning-audit.d.ts +44 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +44 -0
- package/dist/src/db/queries/subgraph-operations.d.ts +44 -0
- package/dist/src/db/queries/subgraphs.d.ts +44 -0
- package/dist/src/db/queries/subscriptions.d.ts +44 -0
- package/dist/src/db/queries/tenant-compute-addons.d.ts +44 -0
- package/dist/src/db/queries/tenants.d.ts +45 -7
- package/dist/src/db/queries/tenants.js +1 -5
- package/dist/src/db/queries/tenants.js.map +3 -3
- package/dist/src/db/queries/usage.d.ts +47 -1
- package/dist/src/db/queries/usage.js +20 -1
- package/dist/src/db/queries/usage.js.map +3 -3
- package/dist/src/db/schema.d.ts +45 -1
- package/dist/src/index.d.ts +47 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/node/local-client.d.ts +44 -0
- package/dist/src/pricing.d.ts +6 -5
- package/dist/src/pricing.js +4 -29
- package/dist/src/pricing.js.map +3 -3
- package/dist/src/schemas/index.d.ts +2 -2
- package/dist/src/schemas/index.js.map +1 -1
- package/dist/src/schemas/subgraphs.d.ts +2 -2
- package/dist/src/schemas/subgraphs.js.map +1 -1
- package/dist/src/subgraphs/spec.d.ts +2 -2
- package/dist/src/types.d.ts +2 -1
- package/migrations/0064_remove_hobby_plan.ts +28 -0
- package/migrations/0065_l2_decoded_events.ts +50 -0
- package/migrations/0066_public_l2_decoded_events.ts +83 -0
- package/migrations/0067_product_usage_counters.ts +18 -0
- package/migrations/0068_chain_reorgs_and_burn_block_hash.ts +48 -0
- package/package.json +5 -1
|
@@ -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 {
|
|
@@ -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 {
|
|
@@ -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 {
|
|
@@ -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 {
|
|
@@ -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 {
|
|
@@ -478,12 +522,6 @@ declare function getTenantByAccount(db: Kysely<Database>, accountId: string): Pr
|
|
|
478
522
|
declare function getTenantBySlug(db: Kysely<Database>, slug: string): Promise<Tenant | null>;
|
|
479
523
|
declare function listTenantsByStatus(db: Kysely<Database>, status: TenantStatus): Promise<Tenant[]>;
|
|
480
524
|
/**
|
|
481
|
-
* Tenants considered "idle" for auto-pause on the Hobby tier. Active =
|
|
482
|
-
* any successful tenant-API request bumped `last_active_at` within the
|
|
483
|
-
* threshold.
|
|
484
|
-
*/
|
|
485
|
-
declare function listIdleHobbyTenants(db: Kysely<Database>, idleSince: Date): Promise<Tenant[]>;
|
|
486
|
-
/**
|
|
487
525
|
* Bump `last_active_at` for a tenant. Callers are expected to throttle
|
|
488
526
|
* (don't hammer on every request) — the tenant-API activity middleware
|
|
489
527
|
* enforces a 60s per-tenant min between writes.
|
|
@@ -539,4 +577,4 @@ interface TenantCredentials {
|
|
|
539
577
|
* CLI). Never log the returned object.
|
|
540
578
|
*/
|
|
541
579
|
declare function getTenantCredentials(db: Kysely<Database>, slug: string): Promise<TenantCredentials | null>;
|
|
542
|
-
export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordMonthlyUsage, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan,
|
|
580
|
+
export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordMonthlyUsage, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan, insertTenant, getTenantCredentials, getTenantBySlug, getTenantByAccount, deleteTenant, bumpTenantKeyGen, bumpTenantActivity, TenantCredentials, RotateType, NewTenantInput };
|
|
@@ -183,9 +183,6 @@ async function getTenantBySlug(db, slug) {
|
|
|
183
183
|
async function listTenantsByStatus(db, status) {
|
|
184
184
|
return db.selectFrom("tenants").selectAll().where("status", "=", status).execute();
|
|
185
185
|
}
|
|
186
|
-
async function listIdleHobbyTenants(db, idleSince) {
|
|
187
|
-
return db.selectFrom("tenants").selectAll().where("status", "in", ["active", "limit_warning"]).where("plan", "=", "hobby").where("last_active_at", "<", idleSince).execute();
|
|
188
|
-
}
|
|
189
186
|
async function bumpTenantActivity(db, slug) {
|
|
190
187
|
await db.updateTable("tenants").set({ last_active_at: new Date }).where("slug", "=", slug).execute();
|
|
191
188
|
}
|
|
@@ -298,7 +295,6 @@ export {
|
|
|
298
295
|
recordHealthCheck,
|
|
299
296
|
listTenantsByStatus,
|
|
300
297
|
listSuspendedOlderThan,
|
|
301
|
-
listIdleHobbyTenants,
|
|
302
298
|
insertTenant,
|
|
303
299
|
getTenantCredentials,
|
|
304
300
|
getTenantBySlug,
|
|
@@ -308,5 +304,5 @@ export {
|
|
|
308
304
|
bumpTenantActivity
|
|
309
305
|
};
|
|
310
306
|
|
|
311
|
-
//# debugId=
|
|
307
|
+
//# debugId=D41D174F77530ADC64756E2164756E21
|
|
312
308
|
//# sourceMappingURL=tenants.js.map
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Instance modes for the Secondlayer platform.\n *\n * - `oss`: self-hosted, single-tenant. No auth middleware, no platform routes\n * (projects, admin, tenants). Everything runs against a single\n * `DATABASE_URL`. Intended for `docker compose up`.\n *\n * - `dedicated`: per-customer managed instance. JWT-based auth (anon =\n * read-only, service = full). Dual-DB mode — shared source indexer DB for\n * block reads, per-tenant target DB for subgraph data. No platform-wide\n * routes mounted (no cross-tenant accounts).\n *\n * - `platform`: control-plane mode. Magic-link auth, API keys, projects,\n * tenants, admin. Serves the dashboard + CLI against a single shared DB.\n */\n\nexport type InstanceMode = \"oss\" | \"dedicated\" | \"platform\";\n\nconst VALID_MODES: readonly InstanceMode[] = [\"oss\", \"dedicated\", \"platform\"];\n\n/**\n * Resolve the active instance mode from `process.env.INSTANCE_MODE`.\n * Defaults to `\"oss\"` — the safest default for self-hosters who deploy\n * without setting the variable.\n */\nexport function getInstanceMode(): InstanceMode {\n\tconst raw = process.env.INSTANCE_MODE?.trim().toLowerCase();\n\tif (raw && (VALID_MODES as readonly string[]).includes(raw)) {\n\t\treturn raw as InstanceMode;\n\t}\n\treturn \"oss\";\n}\n\n/** True when the active mode is `\"platform\"` (shared multi-tenant). */\nexport function isPlatformMode(): boolean {\n\treturn getInstanceMode() === \"platform\";\n}\n\n/** True when the active mode is `\"oss\"` (self-hosted). */\nexport function isOssMode(): boolean {\n\treturn getInstanceMode() === \"oss\";\n}\n\n/** True when the active mode is `\"dedicated\"` (per-tenant managed). */\nexport function isDedicatedMode(): boolean {\n\treturn getInstanceMode() === \"dedicated\";\n}\n",
|
|
6
6
|
"import { createCipheriv, createDecipheriv, randomBytes } from \"node:crypto\";\nimport {\n\tappendFileSync,\n\tcloseSync,\n\texistsSync,\n\topenSync,\n\treadFileSync,\n\tunlinkSync,\n} from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { getInstanceMode } from \"../mode.ts\";\n\n/**\n * AES-256-GCM symmetric envelope for encrypted secrets at rest (tenant keys,\n * subscription signing secrets, etc.).\n *\n * Ciphertext layout: `iv (12 bytes) || authTag (16 bytes) || ciphertext`\n *\n * The key comes from `SECONDLAYER_SECRETS_KEY` — 32 bytes hex. In OSS mode,\n * if the env var is unset on first use we autogenerate a key and persist it\n * to `.env.local` in the current working directory so subsequent restarts\n * pick it up without user intervention. Dedicated/platform modes throw —\n * those runtimes must provision the key explicitly.\n *\n * Rotation strategy: re-encrypt all rows with the new key and swap the env\n * var. Not zero-downtime, but acceptable at v2 scale. For real KMS (AWS\n * KMS, Vault, GCP KMS), wrap the same byte layout behind an\n * `EncryptSecret`/`DecryptSecret` interface and swap at startup.\n */\n\nconst KEY_ENV = \"SECONDLAYER_SECRETS_KEY\";\nconst IV_LEN = 12;\nconst TAG_LEN = 16;\n\nfunction readExistingKey(envPath: string): string | null {\n\tif (!existsSync(envPath)) return null;\n\tconst contents = readFileSync(envPath, \"utf8\");\n\tconst match = contents.match(/^SECONDLAYER_SECRETS_KEY=([a-fA-F0-9]{64})/m);\n\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\treturn match ? match[1]! : null;\n}\n\n/**\n * Atomic file lock via `openSync(..., \"wx\")` — O_CREAT | O_EXCL. If two\n * processes race on cold-compose start, exactly one creates the lock\n * file; the loser polls until the winner finishes writing `.env.local`,\n * then reads the winner's key. Stale locks (process crashed mid-write)\n * are cleaned after `STALE_LOCK_MS`.\n */\nconst STALE_LOCK_MS = 10_000;\nconst POLL_MS = 25;\n\nfunction bootstrapOssKey(): string {\n\tconst envPath = resolve(process.cwd(), \".env.local\");\n\n\t// Fast path — key already on disk from a prior run.\n\tconst existing = readExistingKey(envPath);\n\tif (existing) {\n\t\tprocess.env[KEY_ENV] = existing;\n\t\treturn existing;\n\t}\n\n\tconst lockPath = `${envPath}.secret-bootstrap.lock`;\n\tlet lockFd: number | null = null;\n\ttry {\n\t\tlockFd = openSync(lockPath, \"wx\", 0o600);\n\t} catch (err) {\n\t\tconst e = err as NodeJS.ErrnoException;\n\t\tif (e.code !== \"EEXIST\") throw err;\n\t}\n\n\tif (lockFd === null) {\n\t\t// Another process is bootstrapping. Poll for its result.\n\t\tconst deadline = Date.now() + STALE_LOCK_MS;\n\t\twhile (Date.now() < deadline) {\n\t\t\tconst key = readExistingKey(envPath);\n\t\t\tif (key) {\n\t\t\t\tprocess.env[KEY_ENV] = key;\n\t\t\t\treturn key;\n\t\t\t}\n\t\t\tBun.sleepSync(POLL_MS);\n\t\t}\n\t\t// Lock holder died mid-write — force-clean and retry once.\n\t\ttry {\n\t\t\tunlinkSync(lockPath);\n\t\t} catch {}\n\t\treturn bootstrapOssKey();\n\t}\n\n\ttry {\n\t\tconst hex = randomBytes(32).toString(\"hex\");\n\t\tconst line = `${existsSync(envPath) ? \"\\n\" : \"\"}${KEY_ENV}=${hex}\\n`;\n\t\tappendFileSync(envPath, line, { mode: 0o600 });\n\t\tprocess.env[KEY_ENV] = hex;\n\t\tconsole.log(\n\t\t\t`[secondlayer] generated ${KEY_ENV}; saved to ${envPath} (mode 0600)`,\n\t\t);\n\t\treturn hex;\n\t} finally {\n\t\tcloseSync(lockFd);\n\t\ttry {\n\t\t\tunlinkSync(lockPath);\n\t\t} catch {}\n\t}\n}\n\nfunction loadKey(): Buffer {\n\tlet hex = process.env[KEY_ENV];\n\tif (!hex) {\n\t\tif (getInstanceMode() === \"oss\") {\n\t\t\thex = bootstrapOssKey();\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`,\n\t\t\t);\n\t\t}\n\t}\n\tconst key = Buffer.from(hex, \"hex\");\n\tif (key.length !== 32) {\n\t\tthrow new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);\n\t}\n\treturn key;\n}\n\nlet _cachedKey: Buffer | null = null;\nfunction getKey(): Buffer {\n\tif (!_cachedKey) _cachedKey = loadKey();\n\treturn _cachedKey;\n}\n\nexport function encryptSecret(plaintext: string): Buffer {\n\tconst key = getKey();\n\tconst iv = randomBytes(IV_LEN);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(plaintext, \"utf8\"),\n\t\tcipher.final(),\n\t]);\n\tconst tag = cipher.getAuthTag();\n\treturn Buffer.concat([iv, tag, ciphertext]);\n}\n\nexport function decryptSecret(envelope: Buffer): string {\n\tconst key = getKey();\n\tconst iv = envelope.subarray(0, IV_LEN);\n\tconst tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);\n\tconst ciphertext = envelope.subarray(IV_LEN + TAG_LEN);\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, iv);\n\tdecipher.setAuthTag(tag);\n\treturn decipher.update(ciphertext).toString(\"utf8\") + decipher.final(\"utf8\");\n}\n\n/** Generate a fresh 32-byte hex key suitable for `SECONDLAYER_SECRETS_KEY`. */\nexport function generateSecretsKey(): string {\n\treturn randomBytes(32).toString(\"hex\");\n}\n",
|
|
7
|
-
"import { type Kysely, sql } from \"kysely\";\nimport { decryptSecret, encryptSecret } from \"../../crypto/secrets.ts\";\nimport type { Database, InsertTenant, Tenant, TenantStatus } from \"../types.ts\";\n\n/**\n * Tenant registry queries. Encrypted columns are stored as `bytea` and\n * transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.\n *\n * Never return decrypted values from listTenants — only `getTenantCredentials`\n * surfaces plaintext, and only when explicitly called by a caller that\n * needs to hand creds to a CLI or dashboard session.\n */\n\nexport interface NewTenantInput {\n\taccountId: string;\n\tslug: string;\n\tplan: string;\n\tcpus: number;\n\tmemoryMb: number;\n\tstorageLimitMb: number;\n\tpgContainerId: string;\n\tapiContainerId: string;\n\tprocessorContainerId: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n\tprojectId?: string;\n}\n\nexport async function insertTenant(\n\tdb: Kysely<Database>,\n\tinput: NewTenantInput,\n): Promise<Tenant> {\n\tconst row: InsertTenant = {\n\t\taccount_id: input.accountId,\n\t\tslug: input.slug,\n\t\tstatus: \"active\",\n\t\tplan: input.plan,\n\t\tcpus: input.cpus,\n\t\tmemory_mb: input.memoryMb,\n\t\tstorage_limit_mb: input.storageLimitMb,\n\t\tpg_container_id: input.pgContainerId,\n\t\tapi_container_id: input.apiContainerId,\n\t\tprocessor_container_id: input.processorContainerId,\n\t\ttarget_database_url_enc: encryptSecret(input.targetDatabaseUrl),\n\t\ttenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),\n\t\tanon_key_enc: encryptSecret(input.anonKey),\n\t\tservice_key_enc: encryptSecret(input.serviceKey),\n\t\tapi_url_internal: input.apiUrlInternal,\n\t\tapi_url_public: input.apiUrlPublic,\n\t\tproject_id: input.projectId ?? null,\n\t};\n\treturn db\n\t\t.insertInto(\"tenants\")\n\t\t.values(row)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getTenantByAccount(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"<>\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function getTenantBySlug(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function listTenantsByStatus(\n\tdb: Kysely<Database>,\n\tstatus: TenantStatus,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", status)\n\t\t.execute();\n}\n\n/**\n * Tenants considered \"idle\" for auto-pause on the Hobby tier. Active =\n * any successful tenant-API request bumped `last_active_at` within the\n * threshold.\n */\nexport async function listIdleHobbyTenants(\n\tdb: Kysely<Database>,\n\tidleSince: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"in\", [\"active\", \"limit_warning\"])\n\t\t.where(\"plan\", \"=\", \"hobby\")\n\t\t.where(\"last_active_at\", \"<\", idleSince)\n\t\t.execute();\n}\n\n/**\n * Bump `last_active_at` for a tenant. Callers are expected to throttle\n * (don't hammer on every request) — the tenant-API activity middleware\n * enforces a 60s per-tenant min between writes.\n */\nexport async function bumpTenantActivity(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({ last_active_at: new Date() })\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport async function listSuspendedOlderThan(\n\tdb: Kysely<Database>,\n\tolderThan: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"suspended\")\n\t\t.where(\"suspended_at\", \"<\", olderThan)\n\t\t.execute();\n}\n\nexport async function setTenantStatus(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstatus: TenantStatus,\n): Promise<void> {\n\tconst patch: Record<string, unknown> = {\n\t\tstatus,\n\t\tupdated_at: new Date(),\n\t};\n\tif (status === \"suspended\" || status === \"paused_limit\") {\n\t\tpatch.suspended_at = new Date();\n\t}\n\tif (status === \"active\") patch.suspended_at = null;\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\nexport async function recordHealthCheck(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstorageUsedMb: number | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tlast_health_check_at: new Date(),\n\t\t\tstorage_used_mb: storageUsedMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\n/**\n * Record a storage measurement into the current calendar month's bucket.\n * Maintains peak, running average, and the most recent value in a single\n * upsert. Billing will consume this later; for now the table just gives\n * us evidence of usage over time.\n */\nexport async function recordMonthlyUsage(\n\tdb: Kysely<Database>,\n\ttenantId: string,\n\tstorageMb: number,\n): Promise<void> {\n\t// Bucket is the first day of the current month (UTC), so the unique\n\t// (tenant_id, period_month) constraint groups all samples cleanly.\n\tconst now = new Date();\n\tconst periodMonth = new Date(\n\t\tDate.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n\t);\n\n\t// Running mean: avg_new = (avg_old * n + x) / (n + 1). Doing it in SQL\n\t// keeps the write atomic — no read-modify-write race between ticks.\n\tawait sql`\n\t\tINSERT INTO tenant_usage_monthly (\n\t\t\ttenant_id, period_month,\n\t\t\tstorage_peak_mb, storage_avg_mb, storage_last_mb,\n\t\t\tmeasurements, first_at, last_at\n\t\t) VALUES (\n\t\t\t${tenantId}, ${periodMonth},\n\t\t\t${storageMb}, ${storageMb}, ${storageMb},\n\t\t\t1, now(), now()\n\t\t)\n\t\tON CONFLICT (tenant_id, period_month) DO UPDATE SET\n\t\t\tstorage_peak_mb = GREATEST(tenant_usage_monthly.storage_peak_mb, EXCLUDED.storage_last_mb),\n\t\t\tstorage_avg_mb = (\n\t\t\t\t(tenant_usage_monthly.storage_avg_mb * tenant_usage_monthly.measurements + EXCLUDED.storage_last_mb)\n\t\t\t\t/ (tenant_usage_monthly.measurements + 1)\n\t\t\t),\n\t\t\tstorage_last_mb = EXCLUDED.storage_last_mb,\n\t\t\tmeasurements = tenant_usage_monthly.measurements + 1,\n\t\t\tlast_at = now()\n\t`.execute(db);\n}\n\nexport async function updateTenantPlan(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tplan: string,\n\tcpus: number,\n\tmemoryMb: number,\n\tstorageLimitMb: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tplan,\n\t\t\tcpus,\n\t\t\tmemory_mb: memoryMb,\n\t\t\tstorage_limit_mb: storageLimitMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport type RotateType = \"service\" | \"anon\" | \"both\";\n\n/**\n * Bump the selected gen counter(s) by 1 and return the new values.\n * Used by the key-rotate endpoint to force the tenant API to reject\n * previously-issued tokens of the rotated role(s).\n */\nexport async function bumpTenantKeyGen(\n\tdb: Kysely<Database>,\n\tslug: string,\n\ttype: RotateType,\n): Promise<{ serviceGen: number; anonGen: number }> {\n\tconst bumpService = type === \"service\" || type === \"both\";\n\tconst bumpAnon = type === \"anon\" || type === \"both\";\n\tconst row = await db\n\t\t.updateTable(\"tenants\")\n\t\t.set((eb) => ({\n\t\t\tservice_gen: bumpService\n\t\t\t\t? eb(\"service_gen\", \"+\", 1)\n\t\t\t\t: eb.ref(\"service_gen\"),\n\t\t\tanon_gen: bumpAnon ? eb(\"anon_gen\", \"+\", 1) : eb.ref(\"anon_gen\"),\n\t\t\tupdated_at: new Date(),\n\t\t}))\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.returning([\"service_gen\", \"anon_gen\"])\n\t\t.executeTakeFirstOrThrow();\n\treturn { serviceGen: row.service_gen, anonGen: row.anon_gen };\n}\n\n/**\n * Replace the encrypted key columns after a successful rotate. Only the\n * rotated column(s) are written — the other stays untouched.\n */\nexport async function updateTenantKeys(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tkeys: { serviceKey?: string; anonKey?: string },\n): Promise<void> {\n\tconst patch: Record<string, unknown> = { updated_at: new Date() };\n\tif (keys.serviceKey) patch.service_key_enc = encryptSecret(keys.serviceKey);\n\tif (keys.anonKey) patch.anon_key_enc = encryptSecret(keys.anonKey);\n\tif (Object.keys(patch).length === 1) return; // only updated_at — nothing to write\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\n/**\n * Hard-delete a tenant row. Call only AFTER the provisioner has torn down\n * containers + volume; otherwise orphaned resources linger. Returns whether\n * a row was actually deleted.\n */\nexport async function deleteTenant(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<boolean> {\n\tconst res = await db\n\t\t.deleteFrom(\"tenants\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn (res.numDeletedRows ?? 0n) > 0n;\n}\n\nexport interface TenantCredentials {\n\tslug: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n}\n\n/**\n * Decrypts the four encrypted columns and returns them plaintext. Call\n * this only when surfacing credentials to an authorized caller (dashboard,\n * CLI). Never log the returned object.\n */\nexport async function getTenantCredentials(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<TenantCredentials | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\n\t\t\t\"slug\",\n\t\t\t\"target_database_url_enc\",\n\t\t\t\"tenant_jwt_secret_enc\",\n\t\t\t\"anon_key_enc\",\n\t\t\t\"service_key_enc\",\n\t\t\t\"api_url_internal\",\n\t\t\t\"api_url_public\",\n\t\t])\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\tif (!row) return null;\n\treturn {\n\t\tslug: row.slug,\n\t\ttargetDatabaseUrl: decryptSecret(row.target_database_url_enc),\n\t\ttenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),\n\t\tanonKey: decryptSecret(row.anon_key_enc),\n\t\tserviceKey: decryptSecret(row.service_key_enc),\n\t\tapiUrlInternal: row.api_url_internal,\n\t\tapiUrlPublic: row.api_url_public,\n\t};\n}\n"
|
|
7
|
+
"import { type Kysely, sql } from \"kysely\";\nimport { decryptSecret, encryptSecret } from \"../../crypto/secrets.ts\";\nimport type { Database, InsertTenant, Tenant, TenantStatus } from \"../types.ts\";\n\n/**\n * Tenant registry queries. Encrypted columns are stored as `bytea` and\n * transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.\n *\n * Never return decrypted values from listTenants — only `getTenantCredentials`\n * surfaces plaintext, and only when explicitly called by a caller that\n * needs to hand creds to a CLI or dashboard session.\n */\n\nexport interface NewTenantInput {\n\taccountId: string;\n\tslug: string;\n\tplan: string;\n\tcpus: number;\n\tmemoryMb: number;\n\tstorageLimitMb: number;\n\tpgContainerId: string;\n\tapiContainerId: string;\n\tprocessorContainerId: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n\tprojectId?: string;\n}\n\nexport async function insertTenant(\n\tdb: Kysely<Database>,\n\tinput: NewTenantInput,\n): Promise<Tenant> {\n\tconst row: InsertTenant = {\n\t\taccount_id: input.accountId,\n\t\tslug: input.slug,\n\t\tstatus: \"active\",\n\t\tplan: input.plan,\n\t\tcpus: input.cpus,\n\t\tmemory_mb: input.memoryMb,\n\t\tstorage_limit_mb: input.storageLimitMb,\n\t\tpg_container_id: input.pgContainerId,\n\t\tapi_container_id: input.apiContainerId,\n\t\tprocessor_container_id: input.processorContainerId,\n\t\ttarget_database_url_enc: encryptSecret(input.targetDatabaseUrl),\n\t\ttenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),\n\t\tanon_key_enc: encryptSecret(input.anonKey),\n\t\tservice_key_enc: encryptSecret(input.serviceKey),\n\t\tapi_url_internal: input.apiUrlInternal,\n\t\tapi_url_public: input.apiUrlPublic,\n\t\tproject_id: input.projectId ?? null,\n\t};\n\treturn db\n\t\t.insertInto(\"tenants\")\n\t\t.values(row)\n\t\t.returningAll()\n\t\t.executeTakeFirstOrThrow();\n}\n\nexport async function getTenantByAccount(\n\tdb: Kysely<Database>,\n\taccountId: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"account_id\", \"=\", accountId)\n\t\t.where(\"status\", \"<>\", \"deleted\")\n\t\t.orderBy(\"created_at\", \"desc\")\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function getTenantBySlug(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<Tenant | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nexport async function listTenantsByStatus(\n\tdb: Kysely<Database>,\n\tstatus: TenantStatus,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", status)\n\t\t.execute();\n}\n\n/**\n * Bump `last_active_at` for a tenant. Callers are expected to throttle\n * (don't hammer on every request) — the tenant-API activity middleware\n * enforces a 60s per-tenant min between writes.\n */\nexport async function bumpTenantActivity(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({ last_active_at: new Date() })\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport async function listSuspendedOlderThan(\n\tdb: Kysely<Database>,\n\tolderThan: Date,\n): Promise<Tenant[]> {\n\treturn db\n\t\t.selectFrom(\"tenants\")\n\t\t.selectAll()\n\t\t.where(\"status\", \"=\", \"suspended\")\n\t\t.where(\"suspended_at\", \"<\", olderThan)\n\t\t.execute();\n}\n\nexport async function setTenantStatus(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstatus: TenantStatus,\n): Promise<void> {\n\tconst patch: Record<string, unknown> = {\n\t\tstatus,\n\t\tupdated_at: new Date(),\n\t};\n\tif (status === \"suspended\" || status === \"paused_limit\") {\n\t\tpatch.suspended_at = new Date();\n\t}\n\tif (status === \"active\") patch.suspended_at = null;\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\nexport async function recordHealthCheck(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tstorageUsedMb: number | null,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tlast_health_check_at: new Date(),\n\t\t\tstorage_used_mb: storageUsedMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\n/**\n * Record a storage measurement into the current calendar month's bucket.\n * Maintains peak, running average, and the most recent value in a single\n * upsert. Billing will consume this later; for now the table just gives\n * us evidence of usage over time.\n */\nexport async function recordMonthlyUsage(\n\tdb: Kysely<Database>,\n\ttenantId: string,\n\tstorageMb: number,\n): Promise<void> {\n\t// Bucket is the first day of the current month (UTC), so the unique\n\t// (tenant_id, period_month) constraint groups all samples cleanly.\n\tconst now = new Date();\n\tconst periodMonth = new Date(\n\t\tDate.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1),\n\t);\n\n\t// Running mean: avg_new = (avg_old * n + x) / (n + 1). Doing it in SQL\n\t// keeps the write atomic — no read-modify-write race between ticks.\n\tawait sql`\n\t\tINSERT INTO tenant_usage_monthly (\n\t\t\ttenant_id, period_month,\n\t\t\tstorage_peak_mb, storage_avg_mb, storage_last_mb,\n\t\t\tmeasurements, first_at, last_at\n\t\t) VALUES (\n\t\t\t${tenantId}, ${periodMonth},\n\t\t\t${storageMb}, ${storageMb}, ${storageMb},\n\t\t\t1, now(), now()\n\t\t)\n\t\tON CONFLICT (tenant_id, period_month) DO UPDATE SET\n\t\t\tstorage_peak_mb = GREATEST(tenant_usage_monthly.storage_peak_mb, EXCLUDED.storage_last_mb),\n\t\t\tstorage_avg_mb = (\n\t\t\t\t(tenant_usage_monthly.storage_avg_mb * tenant_usage_monthly.measurements + EXCLUDED.storage_last_mb)\n\t\t\t\t/ (tenant_usage_monthly.measurements + 1)\n\t\t\t),\n\t\t\tstorage_last_mb = EXCLUDED.storage_last_mb,\n\t\t\tmeasurements = tenant_usage_monthly.measurements + 1,\n\t\t\tlast_at = now()\n\t`.execute(db);\n}\n\nexport async function updateTenantPlan(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tplan: string,\n\tcpus: number,\n\tmemoryMb: number,\n\tstorageLimitMb: number,\n): Promise<void> {\n\tawait db\n\t\t.updateTable(\"tenants\")\n\t\t.set({\n\t\t\tplan,\n\t\t\tcpus,\n\t\t\tmemory_mb: memoryMb,\n\t\t\tstorage_limit_mb: storageLimitMb,\n\t\t\tupdated_at: new Date(),\n\t\t})\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.execute();\n}\n\nexport type RotateType = \"service\" | \"anon\" | \"both\";\n\n/**\n * Bump the selected gen counter(s) by 1 and return the new values.\n * Used by the key-rotate endpoint to force the tenant API to reject\n * previously-issued tokens of the rotated role(s).\n */\nexport async function bumpTenantKeyGen(\n\tdb: Kysely<Database>,\n\tslug: string,\n\ttype: RotateType,\n): Promise<{ serviceGen: number; anonGen: number }> {\n\tconst bumpService = type === \"service\" || type === \"both\";\n\tconst bumpAnon = type === \"anon\" || type === \"both\";\n\tconst row = await db\n\t\t.updateTable(\"tenants\")\n\t\t.set((eb) => ({\n\t\t\tservice_gen: bumpService\n\t\t\t\t? eb(\"service_gen\", \"+\", 1)\n\t\t\t\t: eb.ref(\"service_gen\"),\n\t\t\tanon_gen: bumpAnon ? eb(\"anon_gen\", \"+\", 1) : eb.ref(\"anon_gen\"),\n\t\t\tupdated_at: new Date(),\n\t\t}))\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.returning([\"service_gen\", \"anon_gen\"])\n\t\t.executeTakeFirstOrThrow();\n\treturn { serviceGen: row.service_gen, anonGen: row.anon_gen };\n}\n\n/**\n * Replace the encrypted key columns after a successful rotate. Only the\n * rotated column(s) are written — the other stays untouched.\n */\nexport async function updateTenantKeys(\n\tdb: Kysely<Database>,\n\tslug: string,\n\tkeys: { serviceKey?: string; anonKey?: string },\n): Promise<void> {\n\tconst patch: Record<string, unknown> = { updated_at: new Date() };\n\tif (keys.serviceKey) patch.service_key_enc = encryptSecret(keys.serviceKey);\n\tif (keys.anonKey) patch.anon_key_enc = encryptSecret(keys.anonKey);\n\tif (Object.keys(patch).length === 1) return; // only updated_at — nothing to write\n\tawait db.updateTable(\"tenants\").set(patch).where(\"slug\", \"=\", slug).execute();\n}\n\n/**\n * Hard-delete a tenant row. Call only AFTER the provisioner has torn down\n * containers + volume; otherwise orphaned resources linger. Returns whether\n * a row was actually deleted.\n */\nexport async function deleteTenant(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<boolean> {\n\tconst res = await db\n\t\t.deleteFrom(\"tenants\")\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\treturn (res.numDeletedRows ?? 0n) > 0n;\n}\n\nexport interface TenantCredentials {\n\tslug: string;\n\ttargetDatabaseUrl: string;\n\ttenantJwtSecret: string;\n\tanonKey: string;\n\tserviceKey: string;\n\tapiUrlInternal: string;\n\tapiUrlPublic: string;\n}\n\n/**\n * Decrypts the four encrypted columns and returns them plaintext. Call\n * this only when surfacing credentials to an authorized caller (dashboard,\n * CLI). Never log the returned object.\n */\nexport async function getTenantCredentials(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<TenantCredentials | null> {\n\tconst row = await db\n\t\t.selectFrom(\"tenants\")\n\t\t.select([\n\t\t\t\"slug\",\n\t\t\t\"target_database_url_enc\",\n\t\t\t\"tenant_jwt_secret_enc\",\n\t\t\t\"anon_key_enc\",\n\t\t\t\"service_key_enc\",\n\t\t\t\"api_url_internal\",\n\t\t\t\"api_url_public\",\n\t\t])\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\tif (!row) return null;\n\treturn {\n\t\tslug: row.slug,\n\t\ttargetDatabaseUrl: decryptSecret(row.target_database_url_enc),\n\t\ttenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),\n\t\tanonKey: decryptSecret(row.anon_key_enc),\n\t\tserviceKey: decryptSecret(row.service_key_enc),\n\t\tapiUrlInternal: row.api_url_internal,\n\t\tapiUrlPublic: row.api_url_public,\n\t};\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "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
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "D41D174F77530ADC64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
|
@@ -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 {
|
|
@@ -448,6 +492,8 @@ interface SubscriptionDeliveriesTable {
|
|
|
448
492
|
}
|
|
449
493
|
/** Increment API request counter for today. Fire-and-forget safe. */
|
|
450
494
|
declare function incrementApiRequests(db: Kysely<Database>, accountId: string): Promise<void>;
|
|
495
|
+
declare function incrementStreamsEventsReturned(db: Kysely<Database>, accountId: string, quantity: number): Promise<void>;
|
|
496
|
+
declare function incrementIndexDecodedEventsReturned(db: Kysely<Database>, accountId: string, quantity: number): Promise<void>;
|
|
451
497
|
interface UsageSummary {
|
|
452
498
|
apiRequestsToday: number;
|
|
453
499
|
deliveriesThisMonth: number;
|
|
@@ -460,4 +506,4 @@ declare function getUsage(db: Kysely<Database>, accountId: string): Promise<Usag
|
|
|
460
506
|
* for each tenant's subgraph schemas.
|
|
461
507
|
*/
|
|
462
508
|
declare function measureStorage(db: Kysely<Database>): Promise<void>;
|
|
463
|
-
export { measureStorage, incrementApiRequests, getUsage, UsageSummary };
|
|
509
|
+
export { measureStorage, incrementStreamsEventsReturned, incrementIndexDecodedEventsReturned, incrementApiRequests, getUsage, UsageSummary };
|
|
@@ -25,6 +25,23 @@ async function incrementApiRequests(db, accountId) {
|
|
|
25
25
|
DO UPDATE SET api_requests = usage_daily.api_requests + 1
|
|
26
26
|
`.execute(db);
|
|
27
27
|
}
|
|
28
|
+
async function incrementAccountDailyCounter(db, accountId, column, quantity) {
|
|
29
|
+
if (quantity <= 0)
|
|
30
|
+
return;
|
|
31
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
32
|
+
await sql`
|
|
33
|
+
INSERT INTO usage_daily (account_id, tenant_id, date, api_requests, deliveries, ${sql.raw(column)})
|
|
34
|
+
VALUES (${accountId}, NULL, ${today}, 0, 0, ${quantity})
|
|
35
|
+
ON CONFLICT (account_id, date) WHERE tenant_id IS NULL
|
|
36
|
+
DO UPDATE SET ${sql.raw(column)} = usage_daily.${sql.raw(column)} + ${quantity}
|
|
37
|
+
`.execute(db);
|
|
38
|
+
}
|
|
39
|
+
async function incrementStreamsEventsReturned(db, accountId, quantity) {
|
|
40
|
+
await incrementAccountDailyCounter(db, accountId, "streams_events_returned", quantity);
|
|
41
|
+
}
|
|
42
|
+
async function incrementIndexDecodedEventsReturned(db, accountId, quantity) {
|
|
43
|
+
await incrementAccountDailyCounter(db, accountId, "index_decoded_events_returned", quantity);
|
|
44
|
+
}
|
|
28
45
|
async function getUsage(db, accountId) {
|
|
29
46
|
const today = new Date().toISOString().slice(0, 10);
|
|
30
47
|
const monthStart = `${today.slice(0, 7)}-01`;
|
|
@@ -66,9 +83,11 @@ async function measureStorage(db) {
|
|
|
66
83
|
}
|
|
67
84
|
export {
|
|
68
85
|
measureStorage,
|
|
86
|
+
incrementStreamsEventsReturned,
|
|
87
|
+
incrementIndexDecodedEventsReturned,
|
|
69
88
|
incrementApiRequests,
|
|
70
89
|
getUsage
|
|
71
90
|
};
|
|
72
91
|
|
|
73
|
-
//# debugId=
|
|
92
|
+
//# debugId=0A5108029A948ED964756E2164756E21
|
|
74
93
|
//# sourceMappingURL=usage.js.map
|