@secondlayer/shared 7.11.2 → 9.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/archive/range-digest.d.ts +16 -169
- package/dist/src/archive/semantic-digest-builder.d.ts +16 -169
- package/dist/src/archive/status.js +11 -3
- package/dist/src/archive/status.js.map +3 -3
- package/dist/src/coverage/index.d.ts +15 -169
- package/dist/src/crypto/secrets.js +22 -3
- package/dist/src/crypto/secrets.js.map +3 -3
- package/dist/src/db/index.d.ts +18 -211
- package/dist/src/db/index.js +23 -16
- package/dist/src/db/index.js.map +4 -4
- package/dist/src/db/queries/chain-reorgs.d.ts +16 -169
- package/dist/src/db/queries/chain-reorgs.js +23 -16
- package/dist/src/db/queries/chain-reorgs.js.map +4 -4
- package/dist/src/db/queries/contracts.d.ts +16 -169
- package/dist/src/db/queries/instance.d.ts +16 -169
- package/dist/src/db/queries/integrity.d.ts +16 -169
- package/dist/src/db/queries/subgraph-gaps.d.ts +16 -169
- package/dist/src/db/queries/subgraph-operations.d.ts +17 -170
- package/dist/src/db/queries/subgraph-operations.js +1 -15
- package/dist/src/db/queries/subgraph-operations.js.map +3 -3
- package/dist/src/db/queries/subgraphs.d.ts +17 -191
- package/dist/src/db/queries/subgraphs.js +30 -1068
- package/dist/src/db/queries/subgraphs.js.map +5 -12
- package/dist/src/db/queries/subscriptions.d.ts +16 -169
- package/dist/src/db/queries/subscriptions.js +22 -3
- package/dist/src/db/queries/subscriptions.js.map +3 -3
- package/dist/src/db/queries/sync-scope.d.ts +1006 -0
- package/dist/src/db/queries/sync-scope.js +116 -0
- package/dist/src/db/queries/sync-scope.js.map +10 -0
- package/dist/src/db/schema.d.ts +17 -198
- package/dist/src/errors.d.ts +1 -22
- package/dist/src/errors.js +2 -4
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +22 -256
- package/dist/src/index.js +24 -20
- package/dist/src/index.js.map +6 -6
- package/dist/src/mode.d.ts +16 -2
- package/dist/src/mode.js +25 -4
- package/dist/src/mode.js.map +3 -3
- package/dist/src/node/local-client.d.ts +16 -169
- package/dist/src/runtime/index.d.ts +137 -18
- package/dist/src/runtime/index.js +261 -41
- package/dist/src/runtime/index.js.map +9 -7
- package/dist/src/schemas/index.d.ts +5 -25
- package/dist/src/schemas/index.js +1 -2
- package/dist/src/schemas/index.js.map +3 -3
- package/dist/src/schemas/subgraphs.d.ts +5 -25
- package/dist/src/schemas/subgraphs.js +1 -2
- package/dist/src/schemas/subgraphs.js.map +3 -3
- package/dist/src/streams-rows/index.d.ts +399 -0
- package/dist/src/streams-rows/index.js +336 -0
- package/dist/src/streams-rows/index.js.map +17 -0
- package/dist/src/subgraphs/spec.d.ts +0 -9
- package/migrations/0118_drop_retired_control_tables.ts +53 -0
- package/migrations/0119_drop_accounts_plan.ts +29 -0
- package/migrations/0120_sync_scopes.ts +42 -0
- package/package.json +10 -6
- package/dist/src/x402.d.ts +0 -47
- package/dist/src/x402.js +0 -79
- package/dist/src/x402.js.map +0 -10
|
@@ -541,11 +541,9 @@ interface AccountsTable {
|
|
|
541
541
|
ghost: Generated<boolean>;
|
|
542
542
|
/** Stacks principal owning a wallet-ghost account (x402-paid deploys). */
|
|
543
543
|
wallet_principal: string | null;
|
|
544
|
-
plan: Generated<string>;
|
|
545
544
|
display_name: string | null;
|
|
546
545
|
bio: string | null;
|
|
547
546
|
avatar_url: string | null;
|
|
548
|
-
slug: string | null;
|
|
549
547
|
stripe_customer_id: string | null;
|
|
550
548
|
/** Opt-out toggle for the subgraph reindex-completion email. Defaults true. */
|
|
551
549
|
notify_reindex_complete: Generated<boolean>;
|
|
@@ -562,19 +560,6 @@ interface SessionsTable {
|
|
|
562
560
|
last_used_at: Date | null;
|
|
563
561
|
created_at: Generated<Date>;
|
|
564
562
|
}
|
|
565
|
-
/**
|
|
566
|
-
* One-time claim tokens for ghost accounts. The raw token only ever appears in
|
|
567
|
-
* the claim URL returned at mint time; we store its sha256. A used token marks
|
|
568
|
-
* the account as claimed (or merged into an existing account).
|
|
569
|
-
*/
|
|
570
|
-
interface ClaimTokensTable {
|
|
571
|
-
id: Generated<string>;
|
|
572
|
-
account_id: string;
|
|
573
|
-
token_hash: string;
|
|
574
|
-
created_at: Generated<Date>;
|
|
575
|
-
expires_at: Date;
|
|
576
|
-
used_at: Date | null;
|
|
577
|
-
}
|
|
578
563
|
interface MagicLinksTable {
|
|
579
564
|
id: Generated<string>;
|
|
580
565
|
email: string;
|
|
@@ -585,47 +570,6 @@ interface MagicLinksTable {
|
|
|
585
570
|
failed_attempts: Generated<number>;
|
|
586
571
|
created_at: Generated<Date>;
|
|
587
572
|
}
|
|
588
|
-
interface UsageDailyTable {
|
|
589
|
-
account_id: string;
|
|
590
|
-
tenant_id: string | null;
|
|
591
|
-
date: string;
|
|
592
|
-
api_requests: Generated<number>;
|
|
593
|
-
deliveries: Generated<number>;
|
|
594
|
-
streams_events_returned: Generated<number>;
|
|
595
|
-
index_decoded_events_returned: Generated<number>;
|
|
596
|
-
}
|
|
597
|
-
interface UsageSnapshotsTable {
|
|
598
|
-
id: Generated<string>;
|
|
599
|
-
account_id: string;
|
|
600
|
-
measured_at: Generated<Date>;
|
|
601
|
-
storage_bytes: Generated<number>;
|
|
602
|
-
}
|
|
603
|
-
interface AccountInsightsTable {
|
|
604
|
-
id: Generated<string>;
|
|
605
|
-
account_id: string;
|
|
606
|
-
category: string;
|
|
607
|
-
insight_type: string;
|
|
608
|
-
resource_id: string | null;
|
|
609
|
-
severity: string;
|
|
610
|
-
title: string;
|
|
611
|
-
body: string;
|
|
612
|
-
data: unknown;
|
|
613
|
-
dismissed_at: Date | null;
|
|
614
|
-
expires_at: Date | null;
|
|
615
|
-
created_at: Generated<Date>;
|
|
616
|
-
}
|
|
617
|
-
interface AccountAgentRunsTable {
|
|
618
|
-
id: Generated<string>;
|
|
619
|
-
account_id: string;
|
|
620
|
-
started_at: Generated<Date>;
|
|
621
|
-
completed_at: Date | null;
|
|
622
|
-
status: Generated<string>;
|
|
623
|
-
input_tokens: Generated<number>;
|
|
624
|
-
output_tokens: Generated<number>;
|
|
625
|
-
cost_usd: Generated<number>;
|
|
626
|
-
insights_created: Generated<number>;
|
|
627
|
-
error: string | null;
|
|
628
|
-
}
|
|
629
573
|
interface SubgraphProcessingStatsTable {
|
|
630
574
|
id: Generated<string>;
|
|
631
575
|
subgraph_name: string;
|
|
@@ -658,41 +602,6 @@ interface SubgraphHealthSnapshotsTable {
|
|
|
658
602
|
last_processed_block: number | null;
|
|
659
603
|
captured_at: Generated<Date>;
|
|
660
604
|
}
|
|
661
|
-
interface SubgraphUsageDailyTable {
|
|
662
|
-
subgraph_id: string;
|
|
663
|
-
date: string;
|
|
664
|
-
query_count: Generated<number>;
|
|
665
|
-
}
|
|
666
|
-
interface ProjectsTable {
|
|
667
|
-
id: Generated<string>;
|
|
668
|
-
name: string;
|
|
669
|
-
slug: string;
|
|
670
|
-
account_id: string;
|
|
671
|
-
settings: Generated<Record<string, unknown>>;
|
|
672
|
-
network: Generated<string>;
|
|
673
|
-
node_rpc: string | null;
|
|
674
|
-
created_at: Generated<Date>;
|
|
675
|
-
updated_at: Generated<Date>;
|
|
676
|
-
}
|
|
677
|
-
interface TeamMembersTable {
|
|
678
|
-
id: Generated<string>;
|
|
679
|
-
project_id: string;
|
|
680
|
-
account_id: string;
|
|
681
|
-
role: Generated<string>;
|
|
682
|
-
invited_by: string | null;
|
|
683
|
-
created_at: Generated<Date>;
|
|
684
|
-
}
|
|
685
|
-
interface TeamInvitationsTable {
|
|
686
|
-
id: Generated<string>;
|
|
687
|
-
project_id: string;
|
|
688
|
-
email: string;
|
|
689
|
-
role: Generated<string>;
|
|
690
|
-
token: string;
|
|
691
|
-
invited_by: string | null;
|
|
692
|
-
expires_at: Date;
|
|
693
|
-
accepted_at: Date | null;
|
|
694
|
-
created_at: Generated<Date>;
|
|
695
|
-
}
|
|
696
605
|
interface ProcessedStripeEventsTable {
|
|
697
606
|
event_id: string;
|
|
698
607
|
event_type: string;
|
|
@@ -1129,6 +1038,20 @@ interface StageFailuresTable {
|
|
|
1129
1038
|
resolved_at: Date | null;
|
|
1130
1039
|
retain_until: Generated<Date>;
|
|
1131
1040
|
}
|
|
1041
|
+
/**
|
|
1042
|
+
* The instance's declared scope — one row per network. `start_height` is what
|
|
1043
|
+
* makes missing history below it deliberate rather than a gap.
|
|
1044
|
+
*/
|
|
1045
|
+
interface SyncScopesTable {
|
|
1046
|
+
network: string;
|
|
1047
|
+
start_height: number;
|
|
1048
|
+
target_height: number | null;
|
|
1049
|
+
bootstrap_source: BootstrapSource;
|
|
1050
|
+
bootstrap_manifest_digest: string | null;
|
|
1051
|
+
genesis_hash: string | null;
|
|
1052
|
+
created_at: Generated<Date>;
|
|
1053
|
+
updated_at: Generated<Date>;
|
|
1054
|
+
}
|
|
1132
1055
|
interface Database {
|
|
1133
1056
|
blocks: BlocksTable;
|
|
1134
1057
|
transactions: TransactionsTable;
|
|
@@ -1145,27 +1068,14 @@ interface Database {
|
|
|
1145
1068
|
accounts: AccountsTable;
|
|
1146
1069
|
sessions: SessionsTable;
|
|
1147
1070
|
magic_links: MagicLinksTable;
|
|
1148
|
-
claim_tokens: ClaimTokensTable;
|
|
1149
|
-
usage_daily: UsageDailyTable;
|
|
1150
|
-
usage_snapshots: UsageSnapshotsTable;
|
|
1151
|
-
account_insights: AccountInsightsTable;
|
|
1152
|
-
account_agent_runs: AccountAgentRunsTable;
|
|
1153
1071
|
subgraph_health_snapshots: SubgraphHealthSnapshotsTable;
|
|
1154
1072
|
subgraph_processing_stats: SubgraphProcessingStatsTable;
|
|
1155
1073
|
subgraph_table_snapshots: SubgraphTableSnapshotsTable;
|
|
1156
1074
|
subgraph_gaps: SubgraphGapsTable;
|
|
1157
1075
|
subgraph_operations: SubgraphOperationsTable;
|
|
1158
|
-
subgraph_usage_daily: SubgraphUsageDailyTable;
|
|
1159
|
-
projects: ProjectsTable;
|
|
1160
|
-
team_members: TeamMembersTable;
|
|
1161
|
-
team_invitations: TeamInvitationsTable;
|
|
1162
1076
|
processed_stripe_events: ProcessedStripeEventsTable;
|
|
1163
|
-
tenants: TenantsTable;
|
|
1164
|
-
tenant_usage_monthly: TenantUsageMonthlyTable;
|
|
1165
|
-
tenant_compute_addons: TenantComputeAddonsTable;
|
|
1166
1077
|
account_spend_caps: AccountSpendCapsTable;
|
|
1167
1078
|
account_credits: AccountCreditsTable;
|
|
1168
|
-
provisioning_audit_log: ProvisioningAuditLogTable;
|
|
1169
1079
|
subscriptions: SubscriptionsTable;
|
|
1170
1080
|
subscription_outbox: SubscriptionOutboxTable;
|
|
1171
1081
|
subscription_deliveries: SubscriptionDeliveriesTable;
|
|
@@ -1194,6 +1104,7 @@ interface Database {
|
|
|
1194
1104
|
chain_read_cache: ChainReadCacheTable;
|
|
1195
1105
|
pending_fork_blocks: PendingForkBlocksTable;
|
|
1196
1106
|
observer_journal: ObserverJournalTable;
|
|
1107
|
+
sync_scopes: SyncScopesTable;
|
|
1197
1108
|
stage_registry: StageRegistryTable;
|
|
1198
1109
|
stage_runs: StageRunsTable;
|
|
1199
1110
|
stage_block_receipts: StageBlockReceiptsTable;
|
|
@@ -1240,57 +1151,6 @@ interface X402PaymentsTable {
|
|
|
1240
1151
|
* reconciler's heal path can never double-credit them. */
|
|
1241
1152
|
credited_at: Generated<Date> | null;
|
|
1242
1153
|
}
|
|
1243
|
-
type TenantStatus = "provisioning" | "active" | "limit_warning" | "paused_limit" | "suspended" | "error" | "deleted";
|
|
1244
|
-
interface TenantsTable {
|
|
1245
|
-
id: Generated<string>;
|
|
1246
|
-
account_id: string;
|
|
1247
|
-
slug: string;
|
|
1248
|
-
status: ColumnType<TenantStatus, TenantStatus | undefined, TenantStatus>;
|
|
1249
|
-
plan: string;
|
|
1250
|
-
cpus: ColumnType<number, number | string, number | string>;
|
|
1251
|
-
memory_mb: number;
|
|
1252
|
-
storage_limit_mb: number;
|
|
1253
|
-
storage_used_mb: number | null;
|
|
1254
|
-
pg_container_id: string | null;
|
|
1255
|
-
api_container_id: string | null;
|
|
1256
|
-
processor_container_id: string | null;
|
|
1257
|
-
target_database_url_enc: Buffer;
|
|
1258
|
-
tenant_jwt_secret_enc: Buffer;
|
|
1259
|
-
anon_key_enc: Buffer;
|
|
1260
|
-
service_key_enc: Buffer;
|
|
1261
|
-
api_url_internal: string;
|
|
1262
|
-
api_url_public: string;
|
|
1263
|
-
suspended_at: Date | null;
|
|
1264
|
-
last_health_check_at: Date | null;
|
|
1265
|
-
last_active_at: Generated<Date>;
|
|
1266
|
-
service_gen: Generated<number>;
|
|
1267
|
-
anon_gen: Generated<number>;
|
|
1268
|
-
project_id: string | null;
|
|
1269
|
-
created_at: Generated<Date>;
|
|
1270
|
-
updated_at: Generated<Date>;
|
|
1271
|
-
}
|
|
1272
|
-
interface TenantUsageMonthlyTable {
|
|
1273
|
-
id: Generated<string>;
|
|
1274
|
-
tenant_id: string;
|
|
1275
|
-
period_month: Date;
|
|
1276
|
-
storage_peak_mb: Generated<number>;
|
|
1277
|
-
storage_avg_mb: Generated<number>;
|
|
1278
|
-
storage_last_mb: Generated<number>;
|
|
1279
|
-
measurements: Generated<number>;
|
|
1280
|
-
first_at: Generated<Date>;
|
|
1281
|
-
last_at: Generated<Date>;
|
|
1282
|
-
}
|
|
1283
|
-
interface TenantComputeAddonsTable {
|
|
1284
|
-
id: Generated<string>;
|
|
1285
|
-
tenant_id: string;
|
|
1286
|
-
memory_mb_delta: Generated<number>;
|
|
1287
|
-
cpu_delta: Generated<number | string>;
|
|
1288
|
-
storage_mb_delta: Generated<number>;
|
|
1289
|
-
effective_from: Generated<Date>;
|
|
1290
|
-
effective_until: Date | null;
|
|
1291
|
-
stripe_subscription_item_id: string | null;
|
|
1292
|
-
created_at: Generated<Date>;
|
|
1293
|
-
}
|
|
1294
1154
|
interface AccountSpendCapsTable {
|
|
1295
1155
|
account_id: string;
|
|
1296
1156
|
monthly_cap_cents: number | null;
|
|
@@ -1314,20 +1174,6 @@ interface AccountCreditsTable {
|
|
|
1314
1174
|
refill_last_at: Date | null;
|
|
1315
1175
|
updated_at: Generated<Date>;
|
|
1316
1176
|
}
|
|
1317
|
-
type ProvisioningAuditEvent = "provision.start" | "provision.success" | "provision.failure" | "suspend" | "resume" | "resize" | "keys.rotate" | "bastion.key.upload" | "bastion.key.revoke" | "teardown";
|
|
1318
|
-
type ProvisioningAuditStatus = "ok" | "error";
|
|
1319
|
-
interface ProvisioningAuditLogTable {
|
|
1320
|
-
id: Generated<string>;
|
|
1321
|
-
tenant_id: string | null;
|
|
1322
|
-
tenant_slug: string | null;
|
|
1323
|
-
account_id: string | null;
|
|
1324
|
-
actor: string;
|
|
1325
|
-
event: ProvisioningAuditEvent;
|
|
1326
|
-
status: ProvisioningAuditStatus;
|
|
1327
|
-
detail: unknown | null;
|
|
1328
|
-
error: string | null;
|
|
1329
|
-
created_at: Generated<Date>;
|
|
1330
|
-
}
|
|
1331
1177
|
type SubscriptionStatus = "active" | "paused" | "error";
|
|
1332
1178
|
/** Polymorphic subscription mode: `subgraph` reacts to processed table rows;
|
|
1333
1179
|
* `chain` reacts to raw chain events matched directly off the Index/Streams
|
|
@@ -15,14 +15,33 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/mode.ts
|
|
18
|
-
var VALID_MODES = [
|
|
19
|
-
|
|
18
|
+
var VALID_MODES = [
|
|
19
|
+
"oss",
|
|
20
|
+
"platform",
|
|
21
|
+
"archive"
|
|
22
|
+
];
|
|
23
|
+
function declaredMode() {
|
|
20
24
|
const raw = process.env.INSTANCE_MODE?.trim().toLowerCase();
|
|
21
25
|
if (raw && VALID_MODES.includes(raw)) {
|
|
22
26
|
return raw;
|
|
23
27
|
}
|
|
24
28
|
return "oss";
|
|
25
29
|
}
|
|
30
|
+
function getInstanceMode() {
|
|
31
|
+
const declared = declaredMode();
|
|
32
|
+
return declared === "archive" ? "platform" : declared;
|
|
33
|
+
}
|
|
34
|
+
function getDeclaredInstanceMode() {
|
|
35
|
+
return declaredMode();
|
|
36
|
+
}
|
|
37
|
+
function isMeteredReads() {
|
|
38
|
+
const override = process.env.METERED_READS?.trim().toLowerCase();
|
|
39
|
+
if (override === "true")
|
|
40
|
+
return true;
|
|
41
|
+
if (override === "false")
|
|
42
|
+
return false;
|
|
43
|
+
return getInstanceMode() === "platform";
|
|
44
|
+
}
|
|
26
45
|
function isPlatformMode() {
|
|
27
46
|
return getInstanceMode() === "platform";
|
|
28
47
|
}
|
|
@@ -160,5 +179,5 @@ export {
|
|
|
160
179
|
decryptSecret
|
|
161
180
|
};
|
|
162
181
|
|
|
163
|
-
//# debugId=
|
|
182
|
+
//# debugId=8E49B42A8C43DF1864756E2164756E21
|
|
164
183
|
//# sourceMappingURL=secrets.js.map
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/mode.ts", "../src/crypto/secrets.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Instance modes for the Secondlayer platform.\n *\n * - `oss`: self-hosted, single-tenant. No auth middleware, no platform routes\n * (projects, admin). Everything runs against a single `DATABASE_URL`.\n * Intended for `docker compose up`.\n *\n * - `platform`: control-plane mode. Magic-link auth, API keys, projects,\n * admin. Serves the dashboard + CLI against a single shared DB. Post\n * 2026-05-14 shared-rip this also serves subgraphs + subscriptions.\n */\n\nexport type InstanceMode = \"oss\" | \"platform\";\n\nconst VALID_MODES: readonly
|
|
5
|
+
"/**\n * Instance modes for the Secondlayer platform.\n *\n * - `oss`: self-hosted, single-tenant. No auth middleware, no platform routes\n * (projects, admin). Everything runs against a single `DATABASE_URL`.\n * Intended for `docker compose up`.\n *\n * - `platform`: control-plane mode. Magic-link auth, API keys, projects,\n * admin. Serves the dashboard + CLI against a single shared DB. Post\n * 2026-05-14 shared-rip this also serves subgraphs + subscriptions.\n */\n\nexport type InstanceMode = \"oss\" | \"platform\";\n\n/** What the operator wrote in the env — `archive` is the honest name for the\n * metered public-archive deployment and aliases `platform` behaviorally. */\nexport type DeclaredInstanceMode = InstanceMode | \"archive\";\n\nconst VALID_MODES: readonly DeclaredInstanceMode[] = [\n\t\"oss\",\n\t\"platform\",\n\t\"archive\",\n];\n\nfunction declaredMode(): DeclaredInstanceMode {\n\tconst raw = process.env.INSTANCE_MODE?.trim().toLowerCase();\n\tif (raw && (VALID_MODES as readonly string[]).includes(raw)) {\n\t\treturn raw as DeclaredInstanceMode;\n\t}\n\treturn \"oss\";\n}\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. `archive` normalizes to `platform` here so\n * every behavioral branch stays a two-way choice; use\n * `getDeclaredInstanceMode()` for display surfaces.\n */\nexport function getInstanceMode(): InstanceMode {\n\tconst declared = declaredMode();\n\treturn declared === \"archive\" ? \"platform\" : declared;\n}\n\n/** The mode as declared in the env, for status/identity surfaces. */\nexport function getDeclaredInstanceMode(): DeclaredInstanceMode {\n\treturn declaredMode();\n}\n\n/**\n * True when public reads are credit-metered (the free window, retention, and\n * credits gates arm). Platform/archive deployments meter by default;\n * self-host never does unless the operator explicitly sets\n * `METERED_READS=true`. `METERED_READS=false` disarms a metered deployment.\n */\nexport function isMeteredReads(): boolean {\n\tconst override = process.env.METERED_READS?.trim().toLowerCase();\n\tif (override === \"true\") return true;\n\tif (override === \"false\") return false;\n\treturn getInstanceMode() === \"platform\";\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",
|
|
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\n/**\n * Non-throwing check for whether a secrets key is currently resolvable —\n * same resolution order as {@link loadKey} (env var, then the OSS\n * `.env.local` fallback), but never bootstraps a new key (that would\n * generate+persist a key as a side effect of a mere existence check), never\n * throws, and never returns or logs the key itself. Used by callers that\n * need to fail loud on a missing key BEFORE attempting a decrypt (f072).\n */\nexport function secretsKeyAvailable(): boolean {\n\tif (process.env[KEY_ENV]) return true;\n\tif (getInstanceMode() === \"oss\") {\n\t\tconst envPath = resolve(process.cwd(), \".env.local\");\n\t\treturn readExistingKey(envPath) !== null;\n\t}\n\treturn false;\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
7
|
],
|
|
8
|
-
"mappings": ";;;;;;;;;;;;;;;;;
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "8E49B42A8C43DF1864756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|