@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.
Files changed (55) hide show
  1. package/dist/src/db/index.d.ts +57 -6
  2. package/dist/src/db/index.js +53 -29
  3. package/dist/src/db/index.js.map +4 -4
  4. package/dist/src/db/jsonb.js.map +2 -2
  5. package/dist/src/db/queries/accounts.d.ts +31 -2
  6. package/dist/src/db/queries/integrity.d.ts +31 -2
  7. package/dist/src/db/queries/marketplace.d.ts +31 -2
  8. package/dist/src/db/queries/marketplace.js +6 -9
  9. package/dist/src/db/queries/marketplace.js.map +3 -3
  10. package/dist/src/db/queries/projects.d.ts +31 -2
  11. package/dist/src/db/queries/projects.js.map +2 -2
  12. package/dist/src/db/queries/subgraph-gaps.d.ts +31 -2
  13. package/dist/src/db/queries/subgraphs.d.ts +35 -5
  14. package/dist/src/db/queries/subgraphs.js +3 -9
  15. package/dist/src/db/queries/subgraphs.js.map +4 -4
  16. package/dist/src/db/queries/tenants.d.ts +493 -0
  17. package/dist/src/db/queries/tenants.js +194 -0
  18. package/dist/src/db/queries/tenants.js.map +11 -0
  19. package/dist/src/db/queries/usage.d.ts +31 -2
  20. package/dist/src/db/queries/usage.js +3 -3
  21. package/dist/src/db/queries/usage.js.map +3 -3
  22. package/dist/src/db/queries/workflows.d.ts +31 -2
  23. package/dist/src/db/queries/workflows.js +31 -3
  24. package/dist/src/db/queries/workflows.js.map +4 -4
  25. package/dist/src/db/schema.d.ts +35 -3
  26. package/dist/src/env.d.ts +10 -0
  27. package/dist/src/env.js +3 -1
  28. package/dist/src/env.js.map +3 -3
  29. package/dist/src/errors.d.ts +17 -3
  30. package/dist/src/errors.js +34 -3
  31. package/dist/src/errors.js.map +3 -3
  32. package/dist/src/index.d.ts +83 -8
  33. package/dist/src/index.js +88 -31
  34. package/dist/src/index.js.map +6 -6
  35. package/dist/src/logger.js +3 -1
  36. package/dist/src/logger.js.map +3 -3
  37. package/dist/src/mode.d.ts +30 -0
  38. package/dist/src/mode.js +43 -0
  39. package/dist/src/mode.js.map +10 -0
  40. package/dist/src/node/archive-client.js +3 -1
  41. package/dist/src/node/archive-client.js.map +3 -3
  42. package/dist/src/node/hiro-client.js +3 -1
  43. package/dist/src/node/hiro-client.js.map +3 -3
  44. package/dist/src/node/local-client.d.ts +31 -2
  45. package/dist/src/queue/listener.d.ts +11 -2
  46. package/dist/src/queue/listener.js +11 -12
  47. package/dist/src/queue/listener.js.map +3 -3
  48. package/dist/src/types.d.ts +10 -0
  49. package/migrations/0037_nullable_api_key.ts +35 -0
  50. package/migrations/0038_drop_workflow_tables.ts +46 -0
  51. package/migrations/0039_tenants.ts +66 -0
  52. package/migrations/0040_tenant_key_generations.ts +29 -0
  53. package/migrations/0041_subgraphs_drop_api_key_id.ts +49 -0
  54. package/migrations/0042_tenant_project_id.ts +25 -0
  55. package/package.json +9 -1
@@ -0,0 +1,66 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ /**
4
+ * Dedicated-hosting tenant registry.
5
+ *
6
+ * One row per customer instance. Provisioner is stateless — it does Docker
7
+ * ops + returns values; control plane (this table) owns the persistent
8
+ * mapping between accounts and their per-tenant stack.
9
+ *
10
+ * Encrypted fields use `packages/shared/src/crypto/secrets.ts` (AES-GCM
11
+ * envelope keyed by `SECONDLAYER_SECRETS_KEY`). Never log them in plaintext.
12
+ *
13
+ * Storage is soft-enforced: `storage_used_mb` is updated by the health
14
+ * cron; alerts + overage billing live in the control plane, not the DB.
15
+ */
16
+ export async function up(db: Kysely<unknown>): Promise<void> {
17
+ await sql`SET lock_timeout = '30s'`.execute(db);
18
+
19
+ await sql`
20
+ CREATE TABLE tenants (
21
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
22
+ account_id uuid NOT NULL REFERENCES accounts(id) ON DELETE RESTRICT,
23
+ slug text NOT NULL UNIQUE,
24
+ status text NOT NULL DEFAULT 'provisioning',
25
+
26
+ plan text NOT NULL,
27
+ cpus numeric(4,2) NOT NULL,
28
+ memory_mb integer NOT NULL,
29
+ storage_limit_mb integer NOT NULL,
30
+ storage_used_mb integer,
31
+
32
+ pg_container_id text,
33
+ api_container_id text,
34
+ processor_container_id text,
35
+
36
+ target_database_url_enc bytea NOT NULL,
37
+ tenant_jwt_secret_enc bytea NOT NULL,
38
+ anon_key_enc bytea NOT NULL,
39
+ service_key_enc bytea NOT NULL,
40
+
41
+ api_url_internal text NOT NULL,
42
+ api_url_public text NOT NULL,
43
+
44
+ trial_ends_at timestamptz NOT NULL,
45
+ suspended_at timestamptz,
46
+ last_health_check_at timestamptz,
47
+
48
+ created_at timestamptz NOT NULL DEFAULT now(),
49
+ updated_at timestamptz NOT NULL DEFAULT now()
50
+ )
51
+ `.execute(db);
52
+
53
+ await sql`CREATE INDEX tenants_account_idx ON tenants (account_id)`.execute(
54
+ db,
55
+ );
56
+ await sql`CREATE INDEX tenants_status_idx ON tenants (status) WHERE status <> 'deleted'`.execute(
57
+ db,
58
+ );
59
+ await sql`CREATE INDEX tenants_trial_ends_idx ON tenants (trial_ends_at) WHERE status IN ('provisioning', 'active')`.execute(
60
+ db,
61
+ );
62
+ }
63
+
64
+ export async function down(db: Kysely<unknown>): Promise<void> {
65
+ await sql`DROP TABLE IF EXISTS tenants`.execute(db);
66
+ }
@@ -0,0 +1,29 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ /**
4
+ * Per-tenant key generation counters. Each JWT carries a `gen` claim; the
5
+ * tenant API rejects tokens whose `gen` doesn't match the current counter
6
+ * for that role. Bumping a counter invalidates all JWTs of that role
7
+ * immediately, without rotating the signing secret (which would force
8
+ * both keys to rotate together).
9
+ *
10
+ * UX: user can rotate service alone (leaked server-side key) OR anon alone
11
+ * (client-side embedding exposed) OR both together (offboarding panic).
12
+ */
13
+ export async function up(db: Kysely<unknown>): Promise<void> {
14
+ await sql`SET lock_timeout = '30s'`.execute(db);
15
+
16
+ await sql`
17
+ ALTER TABLE tenants
18
+ ADD COLUMN service_gen integer NOT NULL DEFAULT 1,
19
+ ADD COLUMN anon_gen integer NOT NULL DEFAULT 1
20
+ `.execute(db);
21
+ }
22
+
23
+ export async function down(db: Kysely<unknown>): Promise<void> {
24
+ await sql`
25
+ ALTER TABLE tenants
26
+ DROP COLUMN IF EXISTS service_gen,
27
+ DROP COLUMN IF EXISTS anon_gen
28
+ `.execute(db);
29
+ }
@@ -0,0 +1,49 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ /**
4
+ * Post-cutover cleanup: drop `subgraphs.api_key_id` and the partial unique
5
+ * index that went with nullable-api-key-id handling. Every subgraph lives in
6
+ * a tenant DB now; tenants authenticate via JWT (TENANT_JWT_SECRET), not
7
+ * per-account API keys — so this column has been dead weight since migration
8
+ * 0037 made it nullable.
9
+ *
10
+ * Runs against BOTH the platform DB and every tenant DB (migrations share
11
+ * the same list). The platform DB's `subgraphs` table is manually dropped
12
+ * as a one-off operation AFTER this migration (see Phase 2 cutover notes).
13
+ *
14
+ * Restores the simple `UNIQUE (name)` constraint the table started with —
15
+ * tenant subgraphs were always name-unique within a tenant.
16
+ */
17
+ export async function up(db: Kysely<unknown>): Promise<void> {
18
+ await sql`SET lock_timeout = '30s'`.execute(db);
19
+
20
+ await sql`DROP INDEX IF EXISTS subgraphs_name_unique_no_key`.execute(db);
21
+ await sql`ALTER TABLE subgraphs DROP COLUMN IF EXISTS api_key_id`.execute(db);
22
+ // Re-add the simple name-unique constraint if it's not already there
23
+ // (noop if it was never dropped in a prior migration).
24
+ await sql`
25
+ DO $$
26
+ BEGIN
27
+ IF NOT EXISTS (
28
+ SELECT 1 FROM pg_constraint
29
+ WHERE conname = 'subgraphs_name_unique'
30
+ ) THEN
31
+ ALTER TABLE subgraphs ADD CONSTRAINT subgraphs_name_unique UNIQUE (name);
32
+ END IF;
33
+ END$$
34
+ `.execute(db);
35
+ }
36
+
37
+ export async function down(db: Kysely<unknown>): Promise<void> {
38
+ await sql`ALTER TABLE subgraphs DROP CONSTRAINT IF EXISTS subgraphs_name_unique`.execute(
39
+ db,
40
+ );
41
+ await sql`ALTER TABLE subgraphs ADD COLUMN IF NOT EXISTS api_key_id text`.execute(
42
+ db,
43
+ );
44
+ await sql`
45
+ CREATE UNIQUE INDEX IF NOT EXISTS subgraphs_name_unique_no_key
46
+ ON subgraphs (name)
47
+ WHERE api_key_id IS NULL
48
+ `.execute(db);
49
+ }
@@ -0,0 +1,25 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ /**
4
+ * Link tenants to projects (1:1 enforced at application layer today;
5
+ * schema supports 1:N for future branching).
6
+ *
7
+ * `ON DELETE SET NULL` so a project delete doesn't cascade into tenant
8
+ * teardown — the tenant row stays (with project_id = NULL) until explicit
9
+ * teardown via the provisioner.
10
+ */
11
+ export async function up(db: Kysely<unknown>): Promise<void> {
12
+ await sql`SET lock_timeout = '30s'`.execute(db);
13
+ await sql`
14
+ ALTER TABLE tenants
15
+ ADD COLUMN project_id uuid REFERENCES projects(id) ON DELETE SET NULL
16
+ `.execute(db);
17
+ await sql`CREATE INDEX IF NOT EXISTS tenants_project_idx ON tenants (project_id)`.execute(
18
+ db,
19
+ );
20
+ }
21
+
22
+ export async function down(db: Kysely<unknown>): Promise<void> {
23
+ await sql`DROP INDEX IF EXISTS tenants_project_idx`.execute(db);
24
+ await sql`ALTER TABLE tenants DROP COLUMN IF EXISTS project_id`.execute(db);
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/shared",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/src/index.js",
6
6
  "types": "./dist/src/index.d.ts",
@@ -73,6 +73,10 @@
73
73
  "types": "./dist/src/db/queries/workflows.d.ts",
74
74
  "import": "./dist/src/db/queries/workflows.js"
75
75
  },
76
+ "./db/queries/tenants": {
77
+ "types": "./dist/src/db/queries/tenants.d.ts",
78
+ "import": "./dist/src/db/queries/tenants.js"
79
+ },
76
80
  "./db/queries/marketplace": {
77
81
  "types": "./dist/src/db/queries/marketplace.d.ts",
78
82
  "import": "./dist/src/db/queries/marketplace.js"
@@ -89,6 +93,10 @@
89
93
  "types": "./dist/src/env.d.ts",
90
94
  "import": "./dist/src/env.js"
91
95
  },
96
+ "./mode": {
97
+ "types": "./dist/src/mode.d.ts",
98
+ "import": "./dist/src/mode.js"
99
+ },
92
100
  "./logger": {
93
101
  "types": "./dist/src/logger.d.ts",
94
102
  "import": "./dist/src/logger.js"