@secondlayer/shared 2.0.0 → 3.0.0-alpha.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/README.md +2 -2
- package/dist/src/db/index.d.ts +64 -130
- package/dist/src/db/index.js.map +2 -2
- package/dist/src/db/jsonb.d.ts +5 -1
- package/dist/src/db/jsonb.js.map +2 -2
- package/dist/src/db/queries/account-spend-caps.d.ts +379 -0
- package/dist/src/db/queries/account-spend-caps.js +60 -0
- package/dist/src/db/queries/account-spend-caps.js.map +10 -0
- package/dist/src/db/queries/account-usage.d.ts +403 -0
- package/dist/src/db/queries/account-usage.js +222 -0
- package/dist/src/db/queries/account-usage.js.map +11 -0
- package/dist/src/db/queries/accounts.d.ts +61 -108
- package/dist/src/db/queries/accounts.js +15 -1
- package/dist/src/db/queries/accounts.js.map +3 -3
- package/dist/src/db/queries/integrity.d.ts +47 -107
- package/dist/src/db/queries/projects.d.ts +47 -107
- package/dist/src/db/queries/{workflows.d.ts → provisioning-audit.d.ts} +70 -142
- package/dist/src/db/queries/provisioning-audit.js +40 -0
- package/dist/src/db/queries/provisioning-audit.js.map +10 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +47 -107
- package/dist/src/db/queries/subgraphs.d.ts +47 -108
- package/dist/src/db/queries/subgraphs.js +2 -3
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/{marketplace.d.ts → tenant-compute-addons.d.ts} +66 -159
- package/dist/src/db/queries/tenant-compute-addons.js +47 -0
- package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
- package/dist/src/db/queries/tenants.d.ts +67 -110
- package/dist/src/db/queries/tenants.js +35 -6
- package/dist/src/db/queries/tenants.js.map +3 -3
- package/dist/src/db/queries/usage.d.ts +48 -132
- package/dist/src/db/queries/usage.js +5 -64
- package/dist/src/db/queries/usage.js.map +4 -5
- package/dist/src/db/schema.d.ts +59 -129
- package/dist/src/errors.d.ts +8 -7
- package/dist/src/errors.js +11 -12
- package/dist/src/errors.js.map +3 -3
- package/dist/src/index.d.ts +98 -212
- package/dist/src/index.js +69 -80
- package/dist/src/index.js.map +6 -6
- package/dist/src/mode.d.ts +4 -5
- package/dist/src/mode.js.map +2 -2
- package/dist/src/node/local-client.d.ts +47 -107
- package/dist/src/pricing.d.ts +20 -1
- package/dist/src/pricing.js +58 -1
- package/dist/src/pricing.js.map +3 -3
- package/dist/src/schemas/accounts.d.ts +14 -0
- package/dist/src/schemas/{marketplace.js → accounts.js} +4 -14
- package/dist/src/schemas/accounts.js.map +10 -0
- package/dist/src/schemas/index.d.ts +28 -77
- package/dist/src/schemas/index.js +59 -69
- package/dist/src/schemas/index.js.map +4 -4
- package/migrations/0043_tenant_usage_monthly.ts +36 -0
- package/migrations/0044_provisioning_audit_log.ts +40 -0
- package/migrations/0045_drop_marketplace_columns.ts +47 -0
- package/migrations/0046_tenant_activity_signal.ts +47 -0
- package/migrations/0047_usage_daily_tenant_id.ts +73 -0
- package/migrations/0048_tenant_compute_addons.ts +49 -0
- package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
- package/migrations/0050_account_spend_caps.ts +45 -0
- package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
- package/migrations/0052_sentries.ts +61 -0
- package/migrations/0053_workflow_runtime.ts +88 -0
- package/migrations/0054_accounts_plan_hobby.ts +32 -0
- package/migrations/0055_ai_usage_account_scope.ts +108 -0
- package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
- package/package.json +33 -21
- package/dist/src/db/queries/marketplace.js +0 -139
- package/dist/src/db/queries/marketplace.js.map +0 -10
- package/dist/src/db/queries/workflows.js +0 -260
- package/dist/src/db/queries/workflows.js.map +0 -12
- package/dist/src/lib/plans.d.ts +0 -9
- package/dist/src/lib/plans.js +0 -37
- package/dist/src/lib/plans.js.map +0 -10
- package/dist/src/schemas/marketplace.d.ts +0 -63
- package/dist/src/schemas/marketplace.js.map +0 -10
- package/dist/src/schemas/workflows.d.ts +0 -70
- package/dist/src/schemas/workflows.js +0 -43
- package/dist/src/schemas/workflows.js.map +0 -10
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute add-ons on top of a plan's base spec.
|
|
5
|
+
*
|
|
6
|
+
* The two-axis model (plan × compute) needs somewhere to record "Pro +
|
|
7
|
+
* 4 GB RAM bundle" without mutating the plan. Each row is one add-on;
|
|
8
|
+
* SUM() over active rows for a tenant gives the delta to apply on top
|
|
9
|
+
* of `plans.ts` base compute.
|
|
10
|
+
*
|
|
11
|
+
* Columns kept as *_delta so "zero add-ons" is the identity (no
|
|
12
|
+
* nullables in the sum path). Effective compute = plan base +
|
|
13
|
+
* SUM(active deltas).
|
|
14
|
+
*
|
|
15
|
+
* `effective_until` nullable — open-ended add-on (active until cancelled).
|
|
16
|
+
* Ranges allow future-dated add-ons + mid-cycle cancels cleanly.
|
|
17
|
+
*
|
|
18
|
+
* `stripe_subscription_item_id` nullable — present once Sprint C.2/C.3
|
|
19
|
+
* wire Stripe. For Sprint C.1 the table exists but nothing writes it.
|
|
20
|
+
*/
|
|
21
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
22
|
+
await sql`
|
|
23
|
+
CREATE TABLE tenant_compute_addons (
|
|
24
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
25
|
+
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
26
|
+
memory_mb_delta integer NOT NULL DEFAULT 0,
|
|
27
|
+
cpu_delta numeric(4,2) NOT NULL DEFAULT 0,
|
|
28
|
+
storage_mb_delta integer NOT NULL DEFAULT 0,
|
|
29
|
+
effective_from timestamptz NOT NULL DEFAULT now(),
|
|
30
|
+
effective_until timestamptz,
|
|
31
|
+
stripe_subscription_item_id text,
|
|
32
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
33
|
+
)
|
|
34
|
+
`.execute(db);
|
|
35
|
+
|
|
36
|
+
// Partial index limited to open-ended add-ons (the common case) —
|
|
37
|
+
// `now()` isn't immutable so it can't appear in a WHERE clause here.
|
|
38
|
+
// Closed-end add-ons still get served from a seq scan + date filter,
|
|
39
|
+
// which is cheap at our row counts.
|
|
40
|
+
await sql`
|
|
41
|
+
CREATE INDEX tenant_compute_addons_tenant_idx
|
|
42
|
+
ON tenant_compute_addons (tenant_id)
|
|
43
|
+
WHERE effective_until IS NULL
|
|
44
|
+
`.execute(db);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
48
|
+
await sql`DROP TABLE IF EXISTS tenant_compute_addons`.execute(db);
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Link accounts to Stripe customers.
|
|
5
|
+
*
|
|
6
|
+
* Nullable because we create the Stripe customer lazily — only when an
|
|
7
|
+
* account upgrades past Hobby. Hobby users never show up in Stripe, which
|
|
8
|
+
* keeps the dashboard clean and avoids Stripe-side overhead per free user.
|
|
9
|
+
*
|
|
10
|
+
* Unique index (not constraint) so the column stays nullable but still
|
|
11
|
+
* enforces one-to-one when set.
|
|
12
|
+
*/
|
|
13
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
14
|
+
await sql`
|
|
15
|
+
ALTER TABLE accounts
|
|
16
|
+
ADD COLUMN IF NOT EXISTS stripe_customer_id text
|
|
17
|
+
`.execute(db);
|
|
18
|
+
await sql`
|
|
19
|
+
CREATE UNIQUE INDEX IF NOT EXISTS accounts_stripe_customer_idx
|
|
20
|
+
ON accounts (stripe_customer_id)
|
|
21
|
+
WHERE stripe_customer_id IS NOT NULL
|
|
22
|
+
`.execute(db);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
26
|
+
await sql`DROP INDEX IF EXISTS accounts_stripe_customer_idx`.execute(db);
|
|
27
|
+
await sql`
|
|
28
|
+
ALTER TABLE accounts DROP COLUMN IF EXISTS stripe_customer_id
|
|
29
|
+
`.execute(db);
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-account spending caps + threshold alert state.
|
|
5
|
+
*
|
|
6
|
+
* This is the anti-Supabase-#1-complaint differentiator: soft caps with
|
|
7
|
+
* 80% threshold email alerts, per-line sub-caps, and a clear "frozen"
|
|
8
|
+
* state the user can unfreeze by raising the cap (instead of Supabase's
|
|
9
|
+
* binary cap that blocks certain line items but not compute, producing
|
|
10
|
+
* surprise bills).
|
|
11
|
+
*
|
|
12
|
+
* One row per account (account_id is PK). Null caps mean "no cap" for
|
|
13
|
+
* that line. `frozen_at` is set by the metering worker when a cap is
|
|
14
|
+
* hit; cleared on the next billing cycle's `invoice.paid` webhook. While
|
|
15
|
+
* frozen, meter events stop accumulating for that account.
|
|
16
|
+
*
|
|
17
|
+
* `alert_threshold_pct` default 80 — the Supabase request that's been
|
|
18
|
+
* open since 2023.
|
|
19
|
+
*/
|
|
20
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
21
|
+
await sql`
|
|
22
|
+
CREATE TABLE account_spend_caps (
|
|
23
|
+
account_id uuid PRIMARY KEY REFERENCES accounts(id) ON DELETE CASCADE,
|
|
24
|
+
monthly_cap_cents integer,
|
|
25
|
+
compute_cap_cents integer,
|
|
26
|
+
storage_cap_cents integer,
|
|
27
|
+
ai_cap_cents integer,
|
|
28
|
+
alert_threshold_pct integer NOT NULL DEFAULT 80,
|
|
29
|
+
alert_sent_at timestamptz,
|
|
30
|
+
frozen_at timestamptz,
|
|
31
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
32
|
+
)
|
|
33
|
+
`.execute(db);
|
|
34
|
+
|
|
35
|
+
// Fast lookup for the metering crons: "is this account currently frozen?"
|
|
36
|
+
await sql`
|
|
37
|
+
CREATE INDEX account_spend_caps_frozen_idx
|
|
38
|
+
ON account_spend_caps (account_id)
|
|
39
|
+
WHERE frozen_at IS NOT NULL
|
|
40
|
+
`.execute(db);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
44
|
+
await sql`DROP TABLE IF EXISTS account_spend_caps`.execute(db);
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-tenant-per-day AI eval counter.
|
|
5
|
+
*
|
|
6
|
+
* The workflow-runner bumps this on every `step.ai` / `step.generateText`
|
|
7
|
+
* / `step.generateObject` call. A daily per-tier cap (see `pricing.ts`
|
|
8
|
+
* helpers) gates new AI calls — when the cap is hit, `step.ai` throws
|
|
9
|
+
* and workflows degrade to their condition-only path.
|
|
10
|
+
*
|
|
11
|
+
* The runner package is currently dead (tables dropped in migration
|
|
12
|
+
* 0038); when it returns, these rows are the source of truth for cap
|
|
13
|
+
* enforcement + AI overage metering to Stripe.
|
|
14
|
+
*
|
|
15
|
+
* PK on (tenant_id, day) — one row per tenant per UTC day. `evals` is
|
|
16
|
+
* the call count, `cost_usd_cents` is our internal cost estimate for
|
|
17
|
+
* sanity-checking Stripe meter events later.
|
|
18
|
+
*/
|
|
19
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
20
|
+
await sql`
|
|
21
|
+
CREATE TABLE workflow_ai_usage_daily (
|
|
22
|
+
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
23
|
+
day date NOT NULL,
|
|
24
|
+
evals integer NOT NULL DEFAULT 0,
|
|
25
|
+
cost_usd_cents integer NOT NULL DEFAULT 0,
|
|
26
|
+
first_at timestamptz NOT NULL DEFAULT now(),
|
|
27
|
+
last_at timestamptz NOT NULL DEFAULT now(),
|
|
28
|
+
PRIMARY KEY (tenant_id, day)
|
|
29
|
+
)
|
|
30
|
+
`.execute(db);
|
|
31
|
+
|
|
32
|
+
await sql`
|
|
33
|
+
CREATE INDEX workflow_ai_usage_daily_lookup_idx
|
|
34
|
+
ON workflow_ai_usage_daily (tenant_id, day DESC)
|
|
35
|
+
`.execute(db);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
39
|
+
await sql`DROP TABLE IF EXISTS workflow_ai_usage_daily`.execute(db);
|
|
40
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Protocol Sentry v1 — packaged monitoring product.
|
|
5
|
+
*
|
|
6
|
+
* `sentries` holds per-account enabled sentries. Worker cron ticks every
|
|
7
|
+
* 60s, loads active rows, runs per-kind detect SQL on the shared indexer
|
|
8
|
+
* DB, AI-triages matches, delivers to `delivery_webhook` (Slack-shape).
|
|
9
|
+
*
|
|
10
|
+
* `sentry_alerts` holds one row per delivered alert. UNIQUE on
|
|
11
|
+
* (sentry_id, idempotency_key) dedupes across ticks — key is
|
|
12
|
+
* sha256(txId:eventIndex) for the large-outflow kind.
|
|
13
|
+
*/
|
|
14
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
15
|
+
await sql`
|
|
16
|
+
CREATE TABLE sentries (
|
|
17
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
18
|
+
account_id uuid NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
19
|
+
kind text NOT NULL,
|
|
20
|
+
name text NOT NULL,
|
|
21
|
+
config jsonb NOT NULL,
|
|
22
|
+
active boolean NOT NULL DEFAULT true,
|
|
23
|
+
last_check_at timestamptz,
|
|
24
|
+
delivery_webhook text NOT NULL,
|
|
25
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
26
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
27
|
+
)
|
|
28
|
+
`.execute(db);
|
|
29
|
+
|
|
30
|
+
await sql`CREATE INDEX sentries_account_idx ON sentries (account_id)`.execute(
|
|
31
|
+
db,
|
|
32
|
+
);
|
|
33
|
+
await sql`
|
|
34
|
+
CREATE INDEX sentries_tick_idx
|
|
35
|
+
ON sentries (active, last_check_at NULLS FIRST)
|
|
36
|
+
WHERE active = true
|
|
37
|
+
`.execute(db);
|
|
38
|
+
|
|
39
|
+
await sql`
|
|
40
|
+
CREATE TABLE sentry_alerts (
|
|
41
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
42
|
+
sentry_id uuid NOT NULL REFERENCES sentries(id) ON DELETE CASCADE,
|
|
43
|
+
idempotency_key text NOT NULL,
|
|
44
|
+
fired_at timestamptz NOT NULL DEFAULT now(),
|
|
45
|
+
payload jsonb NOT NULL,
|
|
46
|
+
delivery_status text NOT NULL DEFAULT 'pending',
|
|
47
|
+
delivery_error text,
|
|
48
|
+
UNIQUE (sentry_id, idempotency_key)
|
|
49
|
+
)
|
|
50
|
+
`.execute(db);
|
|
51
|
+
|
|
52
|
+
await sql`
|
|
53
|
+
CREATE INDEX sentry_alerts_sentry_fired_idx
|
|
54
|
+
ON sentry_alerts (sentry_id, fired_at DESC)
|
|
55
|
+
`.execute(db);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
59
|
+
await sql`DROP TABLE IF EXISTS sentry_alerts`.execute(db);
|
|
60
|
+
await sql`DROP TABLE IF EXISTS sentries`.execute(db);
|
|
61
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workflow runtime tables (v3).
|
|
5
|
+
*
|
|
6
|
+
* Minimal revival of the runner's execution substrate after migration
|
|
7
|
+
* 0038 dropped the v2 tables. No `workflow_definitions` — v3 is a
|
|
8
|
+
* zero-sugar TS SDK, definitions live in compiled code + a process-
|
|
9
|
+
* local registry, not the DB. No schedules / cursors / signer secrets
|
|
10
|
+
* / budgets — consumers own their own scheduling; broadcast/budget
|
|
11
|
+
* features land in later migrations when a consumer needs them.
|
|
12
|
+
*
|
|
13
|
+
* workflow_runs — one row per enqueued invocation
|
|
14
|
+
* workflow_steps — memoized `step.*` outputs, keyed by
|
|
15
|
+
* `sha256(stepId + canonicalJSON(inputs))`
|
|
16
|
+
* workflow_queue — SKIP LOCKED dispatch table; one or more rows
|
|
17
|
+
* per run (one per scheduling — sleeps re-enqueue)
|
|
18
|
+
*/
|
|
19
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
20
|
+
await sql`
|
|
21
|
+
CREATE TABLE workflow_runs (
|
|
22
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
23
|
+
workflow_name text NOT NULL,
|
|
24
|
+
input jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
25
|
+
status text NOT NULL DEFAULT 'queued',
|
|
26
|
+
output jsonb,
|
|
27
|
+
error text,
|
|
28
|
+
started_at timestamptz,
|
|
29
|
+
completed_at timestamptz,
|
|
30
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
31
|
+
)
|
|
32
|
+
`.execute(db);
|
|
33
|
+
|
|
34
|
+
await sql`
|
|
35
|
+
CREATE INDEX workflow_runs_name_status_idx
|
|
36
|
+
ON workflow_runs (workflow_name, status, created_at DESC)
|
|
37
|
+
`.execute(db);
|
|
38
|
+
|
|
39
|
+
await sql`
|
|
40
|
+
CREATE TABLE workflow_steps (
|
|
41
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
42
|
+
run_id uuid NOT NULL REFERENCES workflow_runs(id) ON DELETE CASCADE,
|
|
43
|
+
step_id text NOT NULL,
|
|
44
|
+
memo_key text NOT NULL,
|
|
45
|
+
status text NOT NULL DEFAULT 'pending',
|
|
46
|
+
output jsonb,
|
|
47
|
+
error text,
|
|
48
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
49
|
+
started_at timestamptz,
|
|
50
|
+
completed_at timestamptz,
|
|
51
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
52
|
+
UNIQUE (run_id, memo_key)
|
|
53
|
+
)
|
|
54
|
+
`.execute(db);
|
|
55
|
+
|
|
56
|
+
await sql`
|
|
57
|
+
CREATE INDEX workflow_steps_run_idx
|
|
58
|
+
ON workflow_steps (run_id, created_at ASC)
|
|
59
|
+
`.execute(db);
|
|
60
|
+
|
|
61
|
+
await sql`
|
|
62
|
+
CREATE TABLE workflow_queue (
|
|
63
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
64
|
+
run_id uuid NOT NULL REFERENCES workflow_runs(id) ON DELETE CASCADE,
|
|
65
|
+
status text NOT NULL DEFAULT 'pending',
|
|
66
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
67
|
+
max_attempts integer NOT NULL DEFAULT 3,
|
|
68
|
+
scheduled_for timestamptz NOT NULL DEFAULT now(),
|
|
69
|
+
locked_at timestamptz,
|
|
70
|
+
locked_by text,
|
|
71
|
+
error text,
|
|
72
|
+
completed_at timestamptz,
|
|
73
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
74
|
+
)
|
|
75
|
+
`.execute(db);
|
|
76
|
+
|
|
77
|
+
await sql`
|
|
78
|
+
CREATE INDEX workflow_queue_dispatch_idx
|
|
79
|
+
ON workflow_queue (status, scheduled_for)
|
|
80
|
+
WHERE status = 'pending'
|
|
81
|
+
`.execute(db);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
85
|
+
await sql`DROP TABLE IF EXISTS workflow_queue CASCADE`.execute(db);
|
|
86
|
+
await sql`DROP TABLE IF EXISTS workflow_steps CASCADE`.execute(db);
|
|
87
|
+
await sql`DROP TABLE IF EXISTS workflow_runs CASCADE`.execute(db);
|
|
88
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize the legacy `free` plan string to `hobby`.
|
|
5
|
+
*
|
|
6
|
+
* Migration 0004 originally set `accounts.plan` default to `"free"` when
|
|
7
|
+
* the tier vocabulary was free/pro/builder. The pricing model later
|
|
8
|
+
* settled on `hobby/launch/grow/scale/enterprise`. Every lookup table
|
|
9
|
+
* (pricing.ts allowances, TIER_META, webhook handlers) is keyed on
|
|
10
|
+
* `hobby` — but existing signups still carry `plan='free'`, breaking
|
|
11
|
+
* the usage + billing pages' tier mapping.
|
|
12
|
+
*
|
|
13
|
+
* This migration:
|
|
14
|
+
* 1. Backfills existing rows: `free` → `hobby`
|
|
15
|
+
* 2. Flips the column default so new signups default to `hobby`
|
|
16
|
+
*
|
|
17
|
+
* No paid accounts are affected (no existing `plan` value maps to a
|
|
18
|
+
* current tier except `hobby` and whatever a webhook has written).
|
|
19
|
+
*/
|
|
20
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
21
|
+
await sql`UPDATE accounts SET plan = 'hobby' WHERE plan = 'free'`.execute(db);
|
|
22
|
+
await sql`ALTER TABLE accounts ALTER COLUMN plan SET DEFAULT 'hobby'`.execute(
|
|
23
|
+
db,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
28
|
+
await sql`ALTER TABLE accounts ALTER COLUMN plan SET DEFAULT 'free'`.execute(
|
|
29
|
+
db,
|
|
30
|
+
);
|
|
31
|
+
await sql`UPDATE accounts SET plan = 'free' WHERE plan = 'hobby'`.execute(db);
|
|
32
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Re-scope workflow AI usage to the account level.
|
|
5
|
+
*
|
|
6
|
+
* Sentries have no tenant (they're account-level), so the original
|
|
7
|
+
* `workflow_ai_usage_daily` schema keyed on `(tenant_id, day)` can't
|
|
8
|
+
* attribute a sentry's AI calls to an account. This migration:
|
|
9
|
+
*
|
|
10
|
+
* 1. Adds `account_id` (NOT NULL) and makes `tenant_id` nullable on
|
|
11
|
+
* `workflow_ai_usage_daily`.
|
|
12
|
+
* 2. Drops the old PK, replaces with a UNIQUE index using
|
|
13
|
+
* `NULLS NOT DISTINCT` so two NULL-tenant rows for the same
|
|
14
|
+
* account+day conflict correctly (requires PG 15+, matches prod).
|
|
15
|
+
* 3. Adds `account_id` + `tenant_id` nullable columns to
|
|
16
|
+
* `workflow_runs` so the processor can set AsyncLocalStorage
|
|
17
|
+
* context before invoking the handler — AI middleware reads this
|
|
18
|
+
* context to attribute usage back to the caller.
|
|
19
|
+
*
|
|
20
|
+
* Backfill for existing rows joins through `tenants.account_id`.
|
|
21
|
+
* On prod the table has 0 rows (AI middleware was a no-op previously),
|
|
22
|
+
* so backfill is a formality.
|
|
23
|
+
*/
|
|
24
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
25
|
+
// ── workflow_ai_usage_daily ──────────────────────────────────────
|
|
26
|
+
await sql`
|
|
27
|
+
ALTER TABLE workflow_ai_usage_daily
|
|
28
|
+
ADD COLUMN account_id uuid REFERENCES accounts(id) ON DELETE CASCADE
|
|
29
|
+
`.execute(db);
|
|
30
|
+
|
|
31
|
+
await sql`
|
|
32
|
+
UPDATE workflow_ai_usage_daily u
|
|
33
|
+
SET account_id = t.account_id
|
|
34
|
+
FROM tenants t
|
|
35
|
+
WHERE u.tenant_id = t.id AND u.account_id IS NULL
|
|
36
|
+
`.execute(db);
|
|
37
|
+
|
|
38
|
+
// Drop any rows we couldn't backfill (orphans — tenant already deleted).
|
|
39
|
+
await sql`DELETE FROM workflow_ai_usage_daily WHERE account_id IS NULL`.execute(
|
|
40
|
+
db,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Drop the old PK first — it includes tenant_id, which blocks the
|
|
44
|
+
// subsequent DROP NOT NULL.
|
|
45
|
+
await sql`
|
|
46
|
+
ALTER TABLE workflow_ai_usage_daily
|
|
47
|
+
DROP CONSTRAINT workflow_ai_usage_daily_pkey
|
|
48
|
+
`.execute(db);
|
|
49
|
+
|
|
50
|
+
await sql`
|
|
51
|
+
ALTER TABLE workflow_ai_usage_daily
|
|
52
|
+
ALTER COLUMN account_id SET NOT NULL,
|
|
53
|
+
ALTER COLUMN tenant_id DROP NOT NULL
|
|
54
|
+
`.execute(db);
|
|
55
|
+
|
|
56
|
+
await sql`
|
|
57
|
+
CREATE UNIQUE INDEX workflow_ai_usage_daily_account_tenant_day_key
|
|
58
|
+
ON workflow_ai_usage_daily (account_id, tenant_id, day)
|
|
59
|
+
NULLS NOT DISTINCT
|
|
60
|
+
`.execute(db);
|
|
61
|
+
|
|
62
|
+
await sql`DROP INDEX IF EXISTS workflow_ai_usage_daily_lookup_idx`.execute(
|
|
63
|
+
db,
|
|
64
|
+
);
|
|
65
|
+
await sql`
|
|
66
|
+
CREATE INDEX workflow_ai_usage_daily_account_day_idx
|
|
67
|
+
ON workflow_ai_usage_daily (account_id, day DESC)
|
|
68
|
+
`.execute(db);
|
|
69
|
+
|
|
70
|
+
// ── workflow_runs ────────────────────────────────────────────────
|
|
71
|
+
await sql`
|
|
72
|
+
ALTER TABLE workflow_runs
|
|
73
|
+
ADD COLUMN account_id uuid REFERENCES accounts(id) ON DELETE SET NULL,
|
|
74
|
+
ADD COLUMN tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL
|
|
75
|
+
`.execute(db);
|
|
76
|
+
|
|
77
|
+
await sql`
|
|
78
|
+
CREATE INDEX workflow_runs_account_idx
|
|
79
|
+
ON workflow_runs (account_id, created_at DESC)
|
|
80
|
+
WHERE account_id IS NOT NULL
|
|
81
|
+
`.execute(db);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
85
|
+
await sql`DROP INDEX IF EXISTS workflow_runs_account_idx`.execute(db);
|
|
86
|
+
await sql`
|
|
87
|
+
ALTER TABLE workflow_runs
|
|
88
|
+
DROP COLUMN IF EXISTS account_id,
|
|
89
|
+
DROP COLUMN IF EXISTS tenant_id
|
|
90
|
+
`.execute(db);
|
|
91
|
+
|
|
92
|
+
await sql`DROP INDEX IF EXISTS workflow_ai_usage_daily_account_day_idx`.execute(
|
|
93
|
+
db,
|
|
94
|
+
);
|
|
95
|
+
await sql`DROP INDEX IF EXISTS workflow_ai_usage_daily_account_tenant_day_key`.execute(
|
|
96
|
+
db,
|
|
97
|
+
);
|
|
98
|
+
// Best-effort: rows without tenant_id fill would violate NOT NULL on
|
|
99
|
+
// revert. Assume down is dev-only.
|
|
100
|
+
await sql`DELETE FROM workflow_ai_usage_daily WHERE tenant_id IS NULL`.execute(
|
|
101
|
+
db,
|
|
102
|
+
);
|
|
103
|
+
await sql`
|
|
104
|
+
ALTER TABLE workflow_ai_usage_daily
|
|
105
|
+
ALTER COLUMN tenant_id SET NOT NULL,
|
|
106
|
+
DROP COLUMN account_id
|
|
107
|
+
`.execute(db);
|
|
108
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Kysely, sql } from "kysely";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drop all residual workflow + sentry tables after the product pivot.
|
|
5
|
+
* Workflows/runner/sentries packages are gone; these tables have no writers.
|
|
6
|
+
*/
|
|
7
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
8
|
+
await sql`DROP TABLE IF EXISTS sentry_alerts CASCADE`.execute(db);
|
|
9
|
+
await sql`DROP TABLE IF EXISTS sentries CASCADE`.execute(db);
|
|
10
|
+
await sql`DROP TABLE IF EXISTS workflow_queue CASCADE`.execute(db);
|
|
11
|
+
await sql`DROP TABLE IF EXISTS workflow_steps CASCADE`.execute(db);
|
|
12
|
+
await sql`DROP TABLE IF EXISTS workflow_runs CASCADE`.execute(db);
|
|
13
|
+
await sql`DROP TABLE IF EXISTS workflow_ai_usage_daily CASCADE`.execute(db);
|
|
14
|
+
|
|
15
|
+
await sql`DROP TRIGGER IF EXISTS tx_confirmed_trigger ON transactions`.execute(
|
|
16
|
+
db,
|
|
17
|
+
);
|
|
18
|
+
await sql`DROP FUNCTION IF EXISTS tx_confirmed_notify() CASCADE`.execute(db);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function down(_db: Kysely<unknown>): Promise<void> {
|
|
22
|
+
throw new Error("0056 is a one-way demolition; restore from backup");
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@secondlayer/shared",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/src/index.js",
|
|
6
6
|
"types": "./dist/src/index.d.ts",
|
|
@@ -41,10 +41,6 @@
|
|
|
41
41
|
"types": "./dist/src/db/schema.d.ts",
|
|
42
42
|
"import": "./dist/src/db/schema.js"
|
|
43
43
|
},
|
|
44
|
-
"./lib/plans": {
|
|
45
|
-
"types": "./dist/src/lib/plans.d.ts",
|
|
46
|
-
"import": "./dist/src/lib/plans.js"
|
|
47
|
-
},
|
|
48
44
|
"./queue/listener": {
|
|
49
45
|
"types": "./dist/src/queue/listener.d.ts",
|
|
50
46
|
"import": "./dist/src/queue/listener.js"
|
|
@@ -61,30 +57,46 @@
|
|
|
61
57
|
"types": "./dist/src/schemas/subgraphs.d.ts",
|
|
62
58
|
"import": "./dist/src/schemas/subgraphs.js"
|
|
63
59
|
},
|
|
64
|
-
"./schemas/
|
|
65
|
-
"types": "./dist/src/schemas/
|
|
66
|
-
"import": "./dist/src/schemas/
|
|
67
|
-
},
|
|
68
|
-
"./schemas/workflows": {
|
|
69
|
-
"types": "./dist/src/schemas/workflows.d.ts",
|
|
70
|
-
"import": "./dist/src/schemas/workflows.js"
|
|
71
|
-
},
|
|
72
|
-
"./db/queries/workflows": {
|
|
73
|
-
"types": "./dist/src/db/queries/workflows.d.ts",
|
|
74
|
-
"import": "./dist/src/db/queries/workflows.js"
|
|
60
|
+
"./schemas/accounts": {
|
|
61
|
+
"types": "./dist/src/schemas/accounts.d.ts",
|
|
62
|
+
"import": "./dist/src/schemas/accounts.js"
|
|
75
63
|
},
|
|
76
64
|
"./db/queries/tenants": {
|
|
77
65
|
"types": "./dist/src/db/queries/tenants.d.ts",
|
|
78
66
|
"import": "./dist/src/db/queries/tenants.js"
|
|
79
67
|
},
|
|
80
|
-
"./db/queries/marketplace": {
|
|
81
|
-
"types": "./dist/src/db/queries/marketplace.d.ts",
|
|
82
|
-
"import": "./dist/src/db/queries/marketplace.js"
|
|
83
|
-
},
|
|
84
68
|
"./db/queries/projects": {
|
|
85
69
|
"types": "./dist/src/db/queries/projects.d.ts",
|
|
86
70
|
"import": "./dist/src/db/queries/projects.js"
|
|
87
71
|
},
|
|
72
|
+
"./db/queries/provisioning-audit": {
|
|
73
|
+
"types": "./dist/src/db/queries/provisioning-audit.d.ts",
|
|
74
|
+
"import": "./dist/src/db/queries/provisioning-audit.js"
|
|
75
|
+
},
|
|
76
|
+
"./db/queries/tenant-compute-addons": {
|
|
77
|
+
"types": "./dist/src/db/queries/tenant-compute-addons.d.ts",
|
|
78
|
+
"import": "./dist/src/db/queries/tenant-compute-addons.js"
|
|
79
|
+
},
|
|
80
|
+
"./db/queries/account-spend-caps": {
|
|
81
|
+
"types": "./dist/src/db/queries/account-spend-caps.d.ts",
|
|
82
|
+
"import": "./dist/src/db/queries/account-spend-caps.js"
|
|
83
|
+
},
|
|
84
|
+
"./db/queries/sentries": {
|
|
85
|
+
"types": "./dist/src/db/queries/sentries.d.ts",
|
|
86
|
+
"import": "./dist/src/db/queries/sentries.js"
|
|
87
|
+
},
|
|
88
|
+
"./db/queries/account-usage": {
|
|
89
|
+
"types": "./dist/src/db/queries/account-usage.d.ts",
|
|
90
|
+
"import": "./dist/src/db/queries/account-usage.js"
|
|
91
|
+
},
|
|
92
|
+
"./db/queries/workflow-runs": {
|
|
93
|
+
"types": "./dist/src/db/queries/workflow-runs.d.ts",
|
|
94
|
+
"import": "./dist/src/db/queries/workflow-runs.js"
|
|
95
|
+
},
|
|
96
|
+
"./schemas/sentries": {
|
|
97
|
+
"types": "./dist/src/schemas/sentries.d.ts",
|
|
98
|
+
"import": "./dist/src/schemas/sentries.js"
|
|
99
|
+
},
|
|
88
100
|
"./types": {
|
|
89
101
|
"types": "./dist/src/types.d.ts",
|
|
90
102
|
"import": "./dist/src/types.js"
|
|
@@ -164,7 +176,7 @@
|
|
|
164
176
|
"prepublishOnly": "bun run build"
|
|
165
177
|
},
|
|
166
178
|
"dependencies": {
|
|
167
|
-
"@secondlayer/stacks": "^0.
|
|
179
|
+
"@secondlayer/stacks": "^1.0.0-alpha.0",
|
|
168
180
|
"kysely": "0.28.15",
|
|
169
181
|
"kysely-postgres-js": "3.0.0",
|
|
170
182
|
"postgres": "^3.4.6",
|