@secondlayer/shared 1.1.0 → 2.0.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 +57 -6
- package/dist/src/db/index.js +53 -29
- package/dist/src/db/index.js.map +4 -4
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/accounts.d.ts +31 -2
- package/dist/src/db/queries/integrity.d.ts +31 -2
- package/dist/src/db/queries/marketplace.d.ts +31 -2
- package/dist/src/db/queries/marketplace.js +6 -9
- package/dist/src/db/queries/marketplace.js.map +3 -3
- package/dist/src/db/queries/projects.d.ts +31 -2
- package/dist/src/db/queries/projects.js.map +2 -2
- package/dist/src/db/queries/subgraph-gaps.d.ts +31 -2
- package/dist/src/db/queries/subgraphs.d.ts +35 -5
- package/dist/src/db/queries/subgraphs.js +3 -9
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/tenants.d.ts +493 -0
- package/dist/src/db/queries/tenants.js +194 -0
- package/dist/src/db/queries/tenants.js.map +11 -0
- package/dist/src/db/queries/usage.d.ts +31 -2
- package/dist/src/db/queries/usage.js +3 -3
- package/dist/src/db/queries/usage.js.map +3 -3
- package/dist/src/db/queries/workflows.d.ts +31 -2
- package/dist/src/db/queries/workflows.js +31 -3
- package/dist/src/db/queries/workflows.js.map +4 -4
- package/dist/src/db/schema.d.ts +35 -3
- package/dist/src/env.d.ts +10 -0
- package/dist/src/env.js +3 -1
- package/dist/src/env.js.map +3 -3
- package/dist/src/errors.d.ts +17 -3
- package/dist/src/errors.js +34 -3
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +83 -8
- package/dist/src/index.js +88 -31
- package/dist/src/index.js.map +6 -6
- package/dist/src/logger.js +3 -1
- package/dist/src/logger.js.map +3 -3
- package/dist/src/mode.d.ts +30 -0
- package/dist/src/mode.js +43 -0
- package/dist/src/mode.js.map +10 -0
- package/dist/src/node/archive-client.js +3 -1
- package/dist/src/node/archive-client.js.map +3 -3
- package/dist/src/node/hiro-client.js +3 -1
- package/dist/src/node/hiro-client.js.map +3 -3
- package/dist/src/node/local-client.d.ts +31 -2
- package/dist/src/queue/listener.d.ts +11 -2
- package/dist/src/queue/listener.js +11 -12
- package/dist/src/queue/listener.js.map +3 -3
- package/dist/src/types.d.ts +10 -0
- package/migrations/0037_nullable_api_key.ts +35 -0
- package/migrations/0038_drop_workflow_tables.ts +46 -0
- package/migrations/0039_tenants.ts +66 -0
- package/migrations/0040_tenant_key_generations.ts +29 -0
- package/migrations/0041_subgraphs_drop_api_key_id.ts +49 -0
- package/migrations/0042_tenant_project_id.ts +25 -0
- package/package.json +9 -1
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import { ColumnType, Generated, Selectable } from "kysely";
|
|
3
|
+
interface BlocksTable {
|
|
4
|
+
height: number;
|
|
5
|
+
hash: string;
|
|
6
|
+
parent_hash: string;
|
|
7
|
+
burn_block_height: number;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
canonical: Generated<boolean>;
|
|
10
|
+
created_at: Generated<Date>;
|
|
11
|
+
}
|
|
12
|
+
interface TransactionsTable {
|
|
13
|
+
tx_id: string;
|
|
14
|
+
block_height: number;
|
|
15
|
+
tx_index: Generated<number>;
|
|
16
|
+
type: string;
|
|
17
|
+
sender: string;
|
|
18
|
+
status: string;
|
|
19
|
+
contract_id: string | null;
|
|
20
|
+
function_name: string | null;
|
|
21
|
+
function_args: Generated<unknown | null>;
|
|
22
|
+
raw_result: Generated<string | null>;
|
|
23
|
+
raw_tx: string;
|
|
24
|
+
created_at: Generated<Date>;
|
|
25
|
+
}
|
|
26
|
+
interface EventsTable {
|
|
27
|
+
id: Generated<string>;
|
|
28
|
+
tx_id: string;
|
|
29
|
+
block_height: number;
|
|
30
|
+
event_index: number;
|
|
31
|
+
type: string;
|
|
32
|
+
data: unknown;
|
|
33
|
+
created_at: Generated<Date>;
|
|
34
|
+
}
|
|
35
|
+
interface IndexProgressTable {
|
|
36
|
+
network: string;
|
|
37
|
+
last_indexed_block: Generated<number>;
|
|
38
|
+
last_contiguous_block: Generated<number>;
|
|
39
|
+
highest_seen_block: Generated<number>;
|
|
40
|
+
updated_at: Generated<Date>;
|
|
41
|
+
}
|
|
42
|
+
interface SubgraphsTable {
|
|
43
|
+
id: Generated<string>;
|
|
44
|
+
name: string;
|
|
45
|
+
version: Generated<string>;
|
|
46
|
+
status: Generated<string>;
|
|
47
|
+
definition: Record<string, unknown>;
|
|
48
|
+
schema_hash: string;
|
|
49
|
+
handler_path: string;
|
|
50
|
+
schema_name: string | null;
|
|
51
|
+
start_block: Generated<number>;
|
|
52
|
+
last_processed_block: Generated<number>;
|
|
53
|
+
reindex_from_block: number | null;
|
|
54
|
+
reindex_to_block: number | null;
|
|
55
|
+
last_error: string | null;
|
|
56
|
+
last_error_at: Date | null;
|
|
57
|
+
total_processed: Generated<number>;
|
|
58
|
+
total_errors: Generated<number>;
|
|
59
|
+
account_id: string;
|
|
60
|
+
handler_code: string | null;
|
|
61
|
+
source_code: string | null;
|
|
62
|
+
project_id: string | null;
|
|
63
|
+
is_public: Generated<boolean>;
|
|
64
|
+
tags: Generated<string[]>;
|
|
65
|
+
description: string | null;
|
|
66
|
+
forked_from_id: string | null;
|
|
67
|
+
created_at: Generated<Date>;
|
|
68
|
+
updated_at: Generated<Date>;
|
|
69
|
+
}
|
|
70
|
+
interface SubgraphGapsTable {
|
|
71
|
+
id: Generated<string>;
|
|
72
|
+
subgraph_id: string;
|
|
73
|
+
subgraph_name: string;
|
|
74
|
+
gap_start: number;
|
|
75
|
+
gap_end: number;
|
|
76
|
+
reason: string;
|
|
77
|
+
detected_at: Generated<Date>;
|
|
78
|
+
resolved_at: Date | null;
|
|
79
|
+
}
|
|
80
|
+
interface ApiKeysTable {
|
|
81
|
+
id: Generated<string>;
|
|
82
|
+
key_hash: string;
|
|
83
|
+
key_prefix: string;
|
|
84
|
+
name: string | null;
|
|
85
|
+
status: Generated<string>;
|
|
86
|
+
rate_limit: Generated<number>;
|
|
87
|
+
ip_address: string;
|
|
88
|
+
account_id: string;
|
|
89
|
+
last_used_at: Date | null;
|
|
90
|
+
revoked_at: Date | null;
|
|
91
|
+
created_at: Generated<Date>;
|
|
92
|
+
}
|
|
93
|
+
interface AccountsTable {
|
|
94
|
+
id: Generated<string>;
|
|
95
|
+
email: string;
|
|
96
|
+
plan: Generated<string>;
|
|
97
|
+
display_name: string | null;
|
|
98
|
+
bio: string | null;
|
|
99
|
+
avatar_url: string | null;
|
|
100
|
+
slug: string | null;
|
|
101
|
+
created_at: Generated<Date>;
|
|
102
|
+
}
|
|
103
|
+
interface SessionsTable {
|
|
104
|
+
id: Generated<string>;
|
|
105
|
+
token_hash: string;
|
|
106
|
+
token_prefix: string;
|
|
107
|
+
account_id: string;
|
|
108
|
+
ip_address: string;
|
|
109
|
+
expires_at: Generated<Date>;
|
|
110
|
+
revoked_at: Date | null;
|
|
111
|
+
last_used_at: Date | null;
|
|
112
|
+
created_at: Generated<Date>;
|
|
113
|
+
}
|
|
114
|
+
interface MagicLinksTable {
|
|
115
|
+
id: Generated<string>;
|
|
116
|
+
email: string;
|
|
117
|
+
token: string;
|
|
118
|
+
code: string | null;
|
|
119
|
+
expires_at: Date;
|
|
120
|
+
used_at: Date | null;
|
|
121
|
+
failed_attempts: Generated<number>;
|
|
122
|
+
created_at: Generated<Date>;
|
|
123
|
+
}
|
|
124
|
+
interface UsageDailyTable {
|
|
125
|
+
account_id: string;
|
|
126
|
+
date: string;
|
|
127
|
+
api_requests: Generated<number>;
|
|
128
|
+
deliveries: Generated<number>;
|
|
129
|
+
}
|
|
130
|
+
interface UsageSnapshotsTable {
|
|
131
|
+
id: Generated<string>;
|
|
132
|
+
account_id: string;
|
|
133
|
+
measured_at: Generated<Date>;
|
|
134
|
+
storage_bytes: Generated<number>;
|
|
135
|
+
}
|
|
136
|
+
interface WaitlistTable {
|
|
137
|
+
id: Generated<string>;
|
|
138
|
+
email: string;
|
|
139
|
+
source: Generated<string>;
|
|
140
|
+
status: Generated<string>;
|
|
141
|
+
created_at: Generated<Date>;
|
|
142
|
+
}
|
|
143
|
+
interface AccountInsightsTable {
|
|
144
|
+
id: Generated<string>;
|
|
145
|
+
account_id: string;
|
|
146
|
+
category: string;
|
|
147
|
+
insight_type: string;
|
|
148
|
+
resource_id: string | null;
|
|
149
|
+
severity: string;
|
|
150
|
+
title: string;
|
|
151
|
+
body: string;
|
|
152
|
+
data: unknown;
|
|
153
|
+
dismissed_at: Date | null;
|
|
154
|
+
expires_at: Date | null;
|
|
155
|
+
created_at: Generated<Date>;
|
|
156
|
+
}
|
|
157
|
+
interface AccountAgentRunsTable {
|
|
158
|
+
id: Generated<string>;
|
|
159
|
+
account_id: string;
|
|
160
|
+
started_at: Generated<Date>;
|
|
161
|
+
completed_at: Date | null;
|
|
162
|
+
status: Generated<string>;
|
|
163
|
+
input_tokens: Generated<number>;
|
|
164
|
+
output_tokens: Generated<number>;
|
|
165
|
+
cost_usd: Generated<number>;
|
|
166
|
+
insights_created: Generated<number>;
|
|
167
|
+
error: string | null;
|
|
168
|
+
}
|
|
169
|
+
interface SubgraphProcessingStatsTable {
|
|
170
|
+
id: Generated<string>;
|
|
171
|
+
subgraph_name: string;
|
|
172
|
+
api_key_id: string | null;
|
|
173
|
+
bucket_start: Date | null;
|
|
174
|
+
bucket_end: Date | null;
|
|
175
|
+
blocks_processed: number | null;
|
|
176
|
+
total_time_ms: number | null;
|
|
177
|
+
handler_time_ms: number | null;
|
|
178
|
+
flush_time_ms: number | null;
|
|
179
|
+
max_block_time_ms: number | null;
|
|
180
|
+
max_handler_time_ms: number | null;
|
|
181
|
+
avg_ops_per_block: number | null;
|
|
182
|
+
is_catchup: Generated<boolean>;
|
|
183
|
+
created_at: Generated<Date>;
|
|
184
|
+
}
|
|
185
|
+
interface SubgraphTableSnapshotsTable {
|
|
186
|
+
id: Generated<string>;
|
|
187
|
+
subgraph_name: string;
|
|
188
|
+
api_key_id: string | null;
|
|
189
|
+
table_name: string;
|
|
190
|
+
row_count: number | null;
|
|
191
|
+
created_at: Generated<Date>;
|
|
192
|
+
}
|
|
193
|
+
interface SubgraphHealthSnapshotsTable {
|
|
194
|
+
id: Generated<string>;
|
|
195
|
+
subgraph_id: string;
|
|
196
|
+
total_processed: number;
|
|
197
|
+
total_errors: number;
|
|
198
|
+
last_processed_block: number | null;
|
|
199
|
+
captured_at: Generated<Date>;
|
|
200
|
+
}
|
|
201
|
+
interface SubgraphUsageDailyTable {
|
|
202
|
+
subgraph_id: string;
|
|
203
|
+
date: string;
|
|
204
|
+
query_count: Generated<number>;
|
|
205
|
+
}
|
|
206
|
+
interface ProjectsTable {
|
|
207
|
+
id: Generated<string>;
|
|
208
|
+
name: string;
|
|
209
|
+
slug: string;
|
|
210
|
+
account_id: string;
|
|
211
|
+
settings: Generated<Record<string, unknown>>;
|
|
212
|
+
network: Generated<string>;
|
|
213
|
+
node_rpc: string | null;
|
|
214
|
+
created_at: Generated<Date>;
|
|
215
|
+
updated_at: Generated<Date>;
|
|
216
|
+
}
|
|
217
|
+
interface TeamMembersTable {
|
|
218
|
+
id: Generated<string>;
|
|
219
|
+
project_id: string;
|
|
220
|
+
account_id: string;
|
|
221
|
+
role: Generated<string>;
|
|
222
|
+
invited_by: string | null;
|
|
223
|
+
created_at: Generated<Date>;
|
|
224
|
+
}
|
|
225
|
+
interface TeamInvitationsTable {
|
|
226
|
+
id: Generated<string>;
|
|
227
|
+
project_id: string;
|
|
228
|
+
email: string;
|
|
229
|
+
role: Generated<string>;
|
|
230
|
+
token: string;
|
|
231
|
+
invited_by: string | null;
|
|
232
|
+
expires_at: Date;
|
|
233
|
+
accepted_at: Date | null;
|
|
234
|
+
created_at: Generated<Date>;
|
|
235
|
+
}
|
|
236
|
+
interface ChatSessionsTable {
|
|
237
|
+
id: Generated<string>;
|
|
238
|
+
account_id: string;
|
|
239
|
+
title: string | null;
|
|
240
|
+
summary: unknown | null;
|
|
241
|
+
created_at: Generated<Date>;
|
|
242
|
+
updated_at: Generated<Date>;
|
|
243
|
+
}
|
|
244
|
+
interface ChatMessagesTable {
|
|
245
|
+
id: Generated<string>;
|
|
246
|
+
chat_session_id: string;
|
|
247
|
+
role: string;
|
|
248
|
+
parts: unknown;
|
|
249
|
+
metadata: unknown | null;
|
|
250
|
+
created_at: Generated<Date>;
|
|
251
|
+
}
|
|
252
|
+
interface WorkflowDefinitionsTable {
|
|
253
|
+
id: Generated<string>;
|
|
254
|
+
name: string;
|
|
255
|
+
version: Generated<string>;
|
|
256
|
+
status: Generated<string>;
|
|
257
|
+
trigger_type: string;
|
|
258
|
+
trigger_config: unknown;
|
|
259
|
+
handler_path: string;
|
|
260
|
+
source_code: string | null;
|
|
261
|
+
retries_config: unknown | null;
|
|
262
|
+
timeout_ms: number | null;
|
|
263
|
+
api_key_id: string;
|
|
264
|
+
project_id: string | null;
|
|
265
|
+
created_at: Generated<Date>;
|
|
266
|
+
updated_at: Generated<Date>;
|
|
267
|
+
}
|
|
268
|
+
interface WorkflowRunsTable {
|
|
269
|
+
id: Generated<string>;
|
|
270
|
+
definition_id: string;
|
|
271
|
+
status: Generated<string>;
|
|
272
|
+
trigger_type: string;
|
|
273
|
+
trigger_data: unknown | null;
|
|
274
|
+
dedup_key: string | null;
|
|
275
|
+
error: string | null;
|
|
276
|
+
started_at: Date | null;
|
|
277
|
+
completed_at: Date | null;
|
|
278
|
+
duration_ms: number | null;
|
|
279
|
+
total_ai_tokens: Generated<number>;
|
|
280
|
+
created_at: Generated<Date>;
|
|
281
|
+
}
|
|
282
|
+
interface WorkflowStepsTable {
|
|
283
|
+
id: Generated<string>;
|
|
284
|
+
run_id: string;
|
|
285
|
+
step_index: number;
|
|
286
|
+
step_id: string;
|
|
287
|
+
step_type: string;
|
|
288
|
+
status: Generated<string>;
|
|
289
|
+
input: unknown | null;
|
|
290
|
+
output: unknown | null;
|
|
291
|
+
error: string | null;
|
|
292
|
+
retry_count: Generated<number>;
|
|
293
|
+
ai_tokens_used: Generated<number>;
|
|
294
|
+
started_at: Date | null;
|
|
295
|
+
completed_at: Date | null;
|
|
296
|
+
duration_ms: number | null;
|
|
297
|
+
memo_key: string | null;
|
|
298
|
+
parent_step_id: string | null;
|
|
299
|
+
created_at: Generated<Date>;
|
|
300
|
+
}
|
|
301
|
+
interface WorkflowQueueTable {
|
|
302
|
+
id: Generated<string>;
|
|
303
|
+
run_id: string;
|
|
304
|
+
status: Generated<string>;
|
|
305
|
+
attempts: Generated<number>;
|
|
306
|
+
max_attempts: Generated<number>;
|
|
307
|
+
scheduled_for: Generated<Date>;
|
|
308
|
+
locked_at: Date | null;
|
|
309
|
+
locked_by: string | null;
|
|
310
|
+
error: string | null;
|
|
311
|
+
created_at: Generated<Date>;
|
|
312
|
+
completed_at: Date | null;
|
|
313
|
+
}
|
|
314
|
+
interface WorkflowSchedulesTable {
|
|
315
|
+
id: Generated<string>;
|
|
316
|
+
definition_id: string;
|
|
317
|
+
cron_expr: string;
|
|
318
|
+
timezone: Generated<string>;
|
|
319
|
+
next_run_at: Date;
|
|
320
|
+
last_run_at: Date | null;
|
|
321
|
+
enabled: Generated<boolean>;
|
|
322
|
+
created_at: Generated<Date>;
|
|
323
|
+
}
|
|
324
|
+
interface WorkflowCursorsTable {
|
|
325
|
+
name: string;
|
|
326
|
+
block_height: Generated<number>;
|
|
327
|
+
updated_at: Generated<Date>;
|
|
328
|
+
}
|
|
329
|
+
interface Database {
|
|
330
|
+
blocks: BlocksTable;
|
|
331
|
+
transactions: TransactionsTable;
|
|
332
|
+
events: EventsTable;
|
|
333
|
+
index_progress: IndexProgressTable;
|
|
334
|
+
subgraphs: SubgraphsTable;
|
|
335
|
+
api_keys: ApiKeysTable;
|
|
336
|
+
accounts: AccountsTable;
|
|
337
|
+
sessions: SessionsTable;
|
|
338
|
+
magic_links: MagicLinksTable;
|
|
339
|
+
usage_daily: UsageDailyTable;
|
|
340
|
+
usage_snapshots: UsageSnapshotsTable;
|
|
341
|
+
waitlist: WaitlistTable;
|
|
342
|
+
account_insights: AccountInsightsTable;
|
|
343
|
+
account_agent_runs: AccountAgentRunsTable;
|
|
344
|
+
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
345
|
+
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
346
|
+
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
347
|
+
subgraph_gaps: SubgraphGapsTable;
|
|
348
|
+
subgraph_usage_daily: SubgraphUsageDailyTable;
|
|
349
|
+
projects: ProjectsTable;
|
|
350
|
+
team_members: TeamMembersTable;
|
|
351
|
+
team_invitations: TeamInvitationsTable;
|
|
352
|
+
chat_sessions: ChatSessionsTable;
|
|
353
|
+
chat_messages: ChatMessagesTable;
|
|
354
|
+
workflow_definitions: WorkflowDefinitionsTable;
|
|
355
|
+
workflow_runs: WorkflowRunsTable;
|
|
356
|
+
workflow_steps: WorkflowStepsTable;
|
|
357
|
+
workflow_queue: WorkflowQueueTable;
|
|
358
|
+
workflow_schedules: WorkflowSchedulesTable;
|
|
359
|
+
workflow_cursors: WorkflowCursorsTable;
|
|
360
|
+
workflow_signer_secrets: WorkflowSignerSecretsTable;
|
|
361
|
+
workflow_budgets: WorkflowBudgetsTable;
|
|
362
|
+
tenants: TenantsTable;
|
|
363
|
+
}
|
|
364
|
+
type TenantStatus = "provisioning" | "active" | "suspended" | "error" | "deleted";
|
|
365
|
+
interface TenantsTable {
|
|
366
|
+
id: Generated<string>;
|
|
367
|
+
account_id: string;
|
|
368
|
+
slug: string;
|
|
369
|
+
status: ColumnType<TenantStatus, TenantStatus | undefined, TenantStatus>;
|
|
370
|
+
plan: string;
|
|
371
|
+
cpus: ColumnType<number, number | string, number | string>;
|
|
372
|
+
memory_mb: number;
|
|
373
|
+
storage_limit_mb: number;
|
|
374
|
+
storage_used_mb: number | null;
|
|
375
|
+
pg_container_id: string | null;
|
|
376
|
+
api_container_id: string | null;
|
|
377
|
+
processor_container_id: string | null;
|
|
378
|
+
target_database_url_enc: Buffer;
|
|
379
|
+
tenant_jwt_secret_enc: Buffer;
|
|
380
|
+
anon_key_enc: Buffer;
|
|
381
|
+
service_key_enc: Buffer;
|
|
382
|
+
api_url_internal: string;
|
|
383
|
+
api_url_public: string;
|
|
384
|
+
trial_ends_at: Date;
|
|
385
|
+
suspended_at: Date | null;
|
|
386
|
+
last_health_check_at: Date | null;
|
|
387
|
+
service_gen: Generated<number>;
|
|
388
|
+
anon_gen: Generated<number>;
|
|
389
|
+
project_id: string | null;
|
|
390
|
+
created_at: Generated<Date>;
|
|
391
|
+
updated_at: Generated<Date>;
|
|
392
|
+
}
|
|
393
|
+
type Tenant = Selectable<TenantsTable>;
|
|
394
|
+
interface WorkflowBudgetsTable {
|
|
395
|
+
id: Generated<string>;
|
|
396
|
+
workflow_definition_id: string;
|
|
397
|
+
/** Period key: "daily:YYYY-MM-DD" | "weekly:YYYY-Www" | "per-run:<uuid>". */
|
|
398
|
+
period: string;
|
|
399
|
+
ai_usd_used: Generated<string>;
|
|
400
|
+
ai_tokens_used: Generated<string>;
|
|
401
|
+
chain_microstx_used: Generated<string>;
|
|
402
|
+
chain_tx_count: Generated<number>;
|
|
403
|
+
run_count: Generated<number>;
|
|
404
|
+
step_count: Generated<number>;
|
|
405
|
+
reset_at: Date;
|
|
406
|
+
created_at: Generated<Date>;
|
|
407
|
+
updated_at: Generated<Date>;
|
|
408
|
+
}
|
|
409
|
+
interface WorkflowSignerSecretsTable {
|
|
410
|
+
id: Generated<string>;
|
|
411
|
+
account_id: string;
|
|
412
|
+
name: string;
|
|
413
|
+
/** AES-GCM ciphertext bytes produced by the runner's KMS on write. */
|
|
414
|
+
encrypted_value: Buffer;
|
|
415
|
+
created_at: Generated<Date>;
|
|
416
|
+
updated_at: Generated<Date>;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Tenant registry queries. Encrypted columns are stored as `bytea` and
|
|
420
|
+
* transparently encrypted/decrypted via `encryptSecret`/`decryptSecret`.
|
|
421
|
+
*
|
|
422
|
+
* Never return decrypted values from listTenants — only `getTenantCredentials`
|
|
423
|
+
* surfaces plaintext, and only when explicitly called by a caller that
|
|
424
|
+
* needs to hand creds to a CLI or dashboard session.
|
|
425
|
+
*/
|
|
426
|
+
interface NewTenantInput {
|
|
427
|
+
accountId: string;
|
|
428
|
+
slug: string;
|
|
429
|
+
plan: string;
|
|
430
|
+
cpus: number;
|
|
431
|
+
memoryMb: number;
|
|
432
|
+
storageLimitMb: number;
|
|
433
|
+
pgContainerId: string;
|
|
434
|
+
apiContainerId: string;
|
|
435
|
+
processorContainerId: string;
|
|
436
|
+
targetDatabaseUrl: string;
|
|
437
|
+
tenantJwtSecret: string;
|
|
438
|
+
anonKey: string;
|
|
439
|
+
serviceKey: string;
|
|
440
|
+
apiUrlInternal: string;
|
|
441
|
+
apiUrlPublic: string;
|
|
442
|
+
trialEndsAt: Date;
|
|
443
|
+
projectId?: string;
|
|
444
|
+
}
|
|
445
|
+
declare function insertTenant(db: Kysely<Database>, input: NewTenantInput): Promise<Tenant>;
|
|
446
|
+
declare function getTenantByAccount(db: Kysely<Database>, accountId: string): Promise<Tenant | null>;
|
|
447
|
+
declare function getTenantBySlug(db: Kysely<Database>, slug: string): Promise<Tenant | null>;
|
|
448
|
+
declare function listTenantsByStatus(db: Kysely<Database>, status: TenantStatus): Promise<Tenant[]>;
|
|
449
|
+
declare function listExpiredTrials(db: Kysely<Database>, now?: Date): Promise<Tenant[]>;
|
|
450
|
+
declare function listSuspendedOlderThan(db: Kysely<Database>, olderThan: Date): Promise<Tenant[]>;
|
|
451
|
+
declare function setTenantStatus(db: Kysely<Database>, slug: string, status: TenantStatus): Promise<void>;
|
|
452
|
+
declare function recordHealthCheck(db: Kysely<Database>, slug: string, storageUsedMb: number | null): Promise<void>;
|
|
453
|
+
declare function updateTenantPlan(db: Kysely<Database>, slug: string, plan: string, cpus: number, memoryMb: number, storageLimitMb: number): Promise<void>;
|
|
454
|
+
type RotateType = "service" | "anon" | "both";
|
|
455
|
+
/**
|
|
456
|
+
* Bump the selected gen counter(s) by 1 and return the new values.
|
|
457
|
+
* Used by the key-rotate endpoint to force the tenant API to reject
|
|
458
|
+
* previously-issued tokens of the rotated role(s).
|
|
459
|
+
*/
|
|
460
|
+
declare function bumpTenantKeyGen(db: Kysely<Database>, slug: string, type: RotateType): Promise<{
|
|
461
|
+
serviceGen: number
|
|
462
|
+
anonGen: number
|
|
463
|
+
}>;
|
|
464
|
+
/**
|
|
465
|
+
* Replace the encrypted key columns after a successful rotate. Only the
|
|
466
|
+
* rotated column(s) are written — the other stays untouched.
|
|
467
|
+
*/
|
|
468
|
+
declare function updateTenantKeys(db: Kysely<Database>, slug: string, keys: {
|
|
469
|
+
serviceKey?: string
|
|
470
|
+
anonKey?: string
|
|
471
|
+
}): Promise<void>;
|
|
472
|
+
/**
|
|
473
|
+
* Hard-delete a tenant row. Call only AFTER the provisioner has torn down
|
|
474
|
+
* containers + volume; otherwise orphaned resources linger. Returns whether
|
|
475
|
+
* a row was actually deleted.
|
|
476
|
+
*/
|
|
477
|
+
declare function deleteTenant(db: Kysely<Database>, slug: string): Promise<boolean>;
|
|
478
|
+
interface TenantCredentials {
|
|
479
|
+
slug: string;
|
|
480
|
+
targetDatabaseUrl: string;
|
|
481
|
+
tenantJwtSecret: string;
|
|
482
|
+
anonKey: string;
|
|
483
|
+
serviceKey: string;
|
|
484
|
+
apiUrlInternal: string;
|
|
485
|
+
apiUrlPublic: string;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Decrypts the four encrypted columns and returns them plaintext. Call
|
|
489
|
+
* this only when surfacing credentials to an authorized caller (dashboard,
|
|
490
|
+
* CLI). Never log the returned object.
|
|
491
|
+
*/
|
|
492
|
+
declare function getTenantCredentials(db: Kysely<Database>, slug: string): Promise<TenantCredentials | null>;
|
|
493
|
+
export { updateTenantPlan, updateTenantKeys, setTenantStatus, recordHealthCheck, listTenantsByStatus, listSuspendedOlderThan, listExpiredTrials, insertTenant, getTenantCredentials, getTenantBySlug, getTenantByAccount, deleteTenant, bumpTenantKeyGen, TenantCredentials, RotateType, NewTenantInput };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/crypto/secrets.ts
|
|
18
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
19
|
+
var KEY_ENV = "SECONDLAYER_SECRETS_KEY";
|
|
20
|
+
var IV_LEN = 12;
|
|
21
|
+
var TAG_LEN = 16;
|
|
22
|
+
function loadKey() {
|
|
23
|
+
const hex = process.env[KEY_ENV];
|
|
24
|
+
if (!hex) {
|
|
25
|
+
throw new Error(`${KEY_ENV} not set. Generate one with: openssl rand -hex 32`);
|
|
26
|
+
}
|
|
27
|
+
const key = Buffer.from(hex, "hex");
|
|
28
|
+
if (key.length !== 32) {
|
|
29
|
+
throw new Error(`${KEY_ENV} must be 32 bytes hex (got ${key.length})`);
|
|
30
|
+
}
|
|
31
|
+
return key;
|
|
32
|
+
}
|
|
33
|
+
var _cachedKey = null;
|
|
34
|
+
function getKey() {
|
|
35
|
+
if (!_cachedKey)
|
|
36
|
+
_cachedKey = loadKey();
|
|
37
|
+
return _cachedKey;
|
|
38
|
+
}
|
|
39
|
+
function encryptSecret(plaintext) {
|
|
40
|
+
const key = getKey();
|
|
41
|
+
const iv = randomBytes(IV_LEN);
|
|
42
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
43
|
+
const ciphertext = Buffer.concat([
|
|
44
|
+
cipher.update(plaintext, "utf8"),
|
|
45
|
+
cipher.final()
|
|
46
|
+
]);
|
|
47
|
+
const tag = cipher.getAuthTag();
|
|
48
|
+
return Buffer.concat([iv, tag, ciphertext]);
|
|
49
|
+
}
|
|
50
|
+
function decryptSecret(envelope) {
|
|
51
|
+
const key = getKey();
|
|
52
|
+
const iv = envelope.subarray(0, IV_LEN);
|
|
53
|
+
const tag = envelope.subarray(IV_LEN, IV_LEN + TAG_LEN);
|
|
54
|
+
const ciphertext = envelope.subarray(IV_LEN + TAG_LEN);
|
|
55
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
56
|
+
decipher.setAuthTag(tag);
|
|
57
|
+
return decipher.update(ciphertext).toString("utf8") + decipher.final("utf8");
|
|
58
|
+
}
|
|
59
|
+
function generateSecretsKey() {
|
|
60
|
+
return randomBytes(32).toString("hex");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/db/queries/tenants.ts
|
|
64
|
+
async function insertTenant(db, input) {
|
|
65
|
+
const row = {
|
|
66
|
+
account_id: input.accountId,
|
|
67
|
+
slug: input.slug,
|
|
68
|
+
status: "active",
|
|
69
|
+
plan: input.plan,
|
|
70
|
+
cpus: input.cpus,
|
|
71
|
+
memory_mb: input.memoryMb,
|
|
72
|
+
storage_limit_mb: input.storageLimitMb,
|
|
73
|
+
pg_container_id: input.pgContainerId,
|
|
74
|
+
api_container_id: input.apiContainerId,
|
|
75
|
+
processor_container_id: input.processorContainerId,
|
|
76
|
+
target_database_url_enc: encryptSecret(input.targetDatabaseUrl),
|
|
77
|
+
tenant_jwt_secret_enc: encryptSecret(input.tenantJwtSecret),
|
|
78
|
+
anon_key_enc: encryptSecret(input.anonKey),
|
|
79
|
+
service_key_enc: encryptSecret(input.serviceKey),
|
|
80
|
+
api_url_internal: input.apiUrlInternal,
|
|
81
|
+
api_url_public: input.apiUrlPublic,
|
|
82
|
+
trial_ends_at: input.trialEndsAt,
|
|
83
|
+
project_id: input.projectId ?? null
|
|
84
|
+
};
|
|
85
|
+
return db.insertInto("tenants").values(row).returningAll().executeTakeFirstOrThrow();
|
|
86
|
+
}
|
|
87
|
+
async function getTenantByAccount(db, accountId) {
|
|
88
|
+
const row = await db.selectFrom("tenants").selectAll().where("account_id", "=", accountId).where("status", "<>", "deleted").orderBy("created_at", "desc").executeTakeFirst();
|
|
89
|
+
return row ?? null;
|
|
90
|
+
}
|
|
91
|
+
async function getTenantBySlug(db, slug) {
|
|
92
|
+
const row = await db.selectFrom("tenants").selectAll().where("slug", "=", slug).executeTakeFirst();
|
|
93
|
+
return row ?? null;
|
|
94
|
+
}
|
|
95
|
+
async function listTenantsByStatus(db, status) {
|
|
96
|
+
return db.selectFrom("tenants").selectAll().where("status", "=", status).execute();
|
|
97
|
+
}
|
|
98
|
+
async function listExpiredTrials(db, now = new Date) {
|
|
99
|
+
return db.selectFrom("tenants").selectAll().where("status", "in", ["provisioning", "active"]).where("trial_ends_at", "<", now).execute();
|
|
100
|
+
}
|
|
101
|
+
async function listSuspendedOlderThan(db, olderThan) {
|
|
102
|
+
return db.selectFrom("tenants").selectAll().where("status", "=", "suspended").where("suspended_at", "<", olderThan).execute();
|
|
103
|
+
}
|
|
104
|
+
async function setTenantStatus(db, slug, status) {
|
|
105
|
+
const patch = {
|
|
106
|
+
status,
|
|
107
|
+
updated_at: new Date
|
|
108
|
+
};
|
|
109
|
+
if (status === "suspended")
|
|
110
|
+
patch.suspended_at = new Date;
|
|
111
|
+
if (status === "active")
|
|
112
|
+
patch.suspended_at = null;
|
|
113
|
+
await db.updateTable("tenants").set(patch).where("slug", "=", slug).execute();
|
|
114
|
+
}
|
|
115
|
+
async function recordHealthCheck(db, slug, storageUsedMb) {
|
|
116
|
+
await db.updateTable("tenants").set({
|
|
117
|
+
last_health_check_at: new Date,
|
|
118
|
+
storage_used_mb: storageUsedMb,
|
|
119
|
+
updated_at: new Date
|
|
120
|
+
}).where("slug", "=", slug).execute();
|
|
121
|
+
}
|
|
122
|
+
async function updateTenantPlan(db, slug, plan, cpus, memoryMb, storageLimitMb) {
|
|
123
|
+
await db.updateTable("tenants").set({
|
|
124
|
+
plan,
|
|
125
|
+
cpus,
|
|
126
|
+
memory_mb: memoryMb,
|
|
127
|
+
storage_limit_mb: storageLimitMb,
|
|
128
|
+
updated_at: new Date
|
|
129
|
+
}).where("slug", "=", slug).execute();
|
|
130
|
+
}
|
|
131
|
+
async function bumpTenantKeyGen(db, slug, type) {
|
|
132
|
+
const bumpService = type === "service" || type === "both";
|
|
133
|
+
const bumpAnon = type === "anon" || type === "both";
|
|
134
|
+
const row = await db.updateTable("tenants").set((eb) => ({
|
|
135
|
+
service_gen: bumpService ? eb("service_gen", "+", 1) : eb.ref("service_gen"),
|
|
136
|
+
anon_gen: bumpAnon ? eb("anon_gen", "+", 1) : eb.ref("anon_gen"),
|
|
137
|
+
updated_at: new Date
|
|
138
|
+
})).where("slug", "=", slug).returning(["service_gen", "anon_gen"]).executeTakeFirstOrThrow();
|
|
139
|
+
return { serviceGen: row.service_gen, anonGen: row.anon_gen };
|
|
140
|
+
}
|
|
141
|
+
async function updateTenantKeys(db, slug, keys) {
|
|
142
|
+
const patch = { updated_at: new Date };
|
|
143
|
+
if (keys.serviceKey)
|
|
144
|
+
patch.service_key_enc = encryptSecret(keys.serviceKey);
|
|
145
|
+
if (keys.anonKey)
|
|
146
|
+
patch.anon_key_enc = encryptSecret(keys.anonKey);
|
|
147
|
+
if (Object.keys(patch).length === 1)
|
|
148
|
+
return;
|
|
149
|
+
await db.updateTable("tenants").set(patch).where("slug", "=", slug).execute();
|
|
150
|
+
}
|
|
151
|
+
async function deleteTenant(db, slug) {
|
|
152
|
+
const res = await db.deleteFrom("tenants").where("slug", "=", slug).executeTakeFirst();
|
|
153
|
+
return (res.numDeletedRows ?? 0n) > 0n;
|
|
154
|
+
}
|
|
155
|
+
async function getTenantCredentials(db, slug) {
|
|
156
|
+
const row = await db.selectFrom("tenants").select([
|
|
157
|
+
"slug",
|
|
158
|
+
"target_database_url_enc",
|
|
159
|
+
"tenant_jwt_secret_enc",
|
|
160
|
+
"anon_key_enc",
|
|
161
|
+
"service_key_enc",
|
|
162
|
+
"api_url_internal",
|
|
163
|
+
"api_url_public"
|
|
164
|
+
]).where("slug", "=", slug).executeTakeFirst();
|
|
165
|
+
if (!row)
|
|
166
|
+
return null;
|
|
167
|
+
return {
|
|
168
|
+
slug: row.slug,
|
|
169
|
+
targetDatabaseUrl: decryptSecret(row.target_database_url_enc),
|
|
170
|
+
tenantJwtSecret: decryptSecret(row.tenant_jwt_secret_enc),
|
|
171
|
+
anonKey: decryptSecret(row.anon_key_enc),
|
|
172
|
+
serviceKey: decryptSecret(row.service_key_enc),
|
|
173
|
+
apiUrlInternal: row.api_url_internal,
|
|
174
|
+
apiUrlPublic: row.api_url_public
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export {
|
|
178
|
+
updateTenantPlan,
|
|
179
|
+
updateTenantKeys,
|
|
180
|
+
setTenantStatus,
|
|
181
|
+
recordHealthCheck,
|
|
182
|
+
listTenantsByStatus,
|
|
183
|
+
listSuspendedOlderThan,
|
|
184
|
+
listExpiredTrials,
|
|
185
|
+
insertTenant,
|
|
186
|
+
getTenantCredentials,
|
|
187
|
+
getTenantBySlug,
|
|
188
|
+
getTenantByAccount,
|
|
189
|
+
deleteTenant,
|
|
190
|
+
bumpTenantKeyGen
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
//# debugId=1D0DA0A21FCE7D7664756E2164756E21
|
|
194
|
+
//# sourceMappingURL=tenants.js.map
|