@secondlayer/shared 2.1.0 → 3.0.0-beta.1

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 (65) hide show
  1. package/README.md +2 -2
  2. package/dist/src/crypto/secrets.js +47 -3
  3. package/dist/src/crypto/secrets.js.map +5 -4
  4. package/dist/src/db/index.d.ts +112 -137
  5. package/dist/src/db/index.js.map +2 -2
  6. package/dist/src/db/jsonb.d.ts +5 -1
  7. package/dist/src/db/jsonb.js.map +2 -2
  8. package/dist/src/db/queries/account-spend-caps.d.ts +444 -0
  9. package/dist/src/db/queries/account-spend-caps.js +60 -0
  10. package/dist/src/db/queries/account-spend-caps.js.map +10 -0
  11. package/dist/src/db/queries/account-usage.d.ts +468 -0
  12. package/dist/src/db/queries/account-usage.js +222 -0
  13. package/dist/src/db/queries/account-usage.js.map +11 -0
  14. package/dist/src/db/queries/accounts.d.ts +100 -109
  15. package/dist/src/db/queries/accounts.js +15 -1
  16. package/dist/src/db/queries/accounts.js.map +3 -3
  17. package/dist/src/db/queries/integrity.d.ts +85 -107
  18. package/dist/src/db/queries/projects.d.ts +87 -109
  19. package/dist/src/db/queries/provisioning-audit.d.ts +85 -107
  20. package/dist/src/db/queries/subgraph-gaps.d.ts +85 -107
  21. package/dist/src/db/queries/subgraphs.d.ts +86 -109
  22. package/dist/src/db/queries/subgraphs.js +2 -3
  23. package/dist/src/db/queries/subgraphs.js.map +4 -4
  24. package/dist/src/db/queries/{workflows.d.ts → tenant-compute-addons.d.ts} +108 -142
  25. package/dist/src/db/queries/tenant-compute-addons.js +47 -0
  26. package/dist/src/db/queries/tenant-compute-addons.js.map +10 -0
  27. package/dist/src/db/queries/tenants.d.ts +98 -110
  28. package/dist/src/db/queries/tenants.js +55 -8
  29. package/dist/src/db/queries/tenants.js.map +6 -5
  30. package/dist/src/db/queries/usage.d.ts +86 -132
  31. package/dist/src/db/queries/usage.js +5 -64
  32. package/dist/src/db/queries/usage.js.map +4 -5
  33. package/dist/src/db/schema.d.ts +107 -136
  34. package/dist/src/errors.d.ts +8 -7
  35. package/dist/src/errors.js +11 -12
  36. package/dist/src/errors.js.map +3 -3
  37. package/dist/src/index.d.ts +119 -143
  38. package/dist/src/index.js +11 -12
  39. package/dist/src/index.js.map +4 -4
  40. package/dist/src/node/local-client.d.ts +85 -107
  41. package/dist/src/pricing.d.ts +20 -1
  42. package/dist/src/pricing.js +58 -1
  43. package/dist/src/pricing.js.map +3 -3
  44. package/migrations/0045_drop_marketplace_columns.ts +47 -0
  45. package/migrations/0046_tenant_activity_signal.ts +47 -0
  46. package/migrations/0047_usage_daily_tenant_id.ts +73 -0
  47. package/migrations/0048_tenant_compute_addons.ts +49 -0
  48. package/migrations/0049_accounts_stripe_customer_id.ts +30 -0
  49. package/migrations/0050_account_spend_caps.ts +45 -0
  50. package/migrations/0051_workflow_ai_usage_daily.ts +40 -0
  51. package/migrations/0052_sentries.ts +61 -0
  52. package/migrations/0053_workflow_runtime.ts +88 -0
  53. package/migrations/0054_accounts_plan_hobby.ts +32 -0
  54. package/migrations/0055_ai_usage_account_scope.ts +108 -0
  55. package/migrations/0056_drop_workflow_sentry_residuals.ts +23 -0
  56. package/migrations/0057_subscriptions.ts +137 -0
  57. package/package.json +26 -14
  58. package/dist/src/db/queries/workflows.js +0 -260
  59. package/dist/src/db/queries/workflows.js.map +0 -12
  60. package/dist/src/lib/plans.d.ts +0 -9
  61. package/dist/src/lib/plans.js +0 -37
  62. package/dist/src/lib/plans.js.map +0 -10
  63. package/dist/src/schemas/workflows.d.ts +0 -70
  64. package/dist/src/schemas/workflows.js +0 -43
  65. package/dist/src/schemas/workflows.js.map +0 -10
@@ -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
+ }
@@ -0,0 +1,137 @@
1
+ import { type Kysely, sql } from "kysely";
2
+
3
+ /**
4
+ * Subgraph event subscriptions — the new core surface after the workflow pivot.
5
+ *
6
+ * Three tables:
7
+ * - `subscriptions`: user-facing configuration. One row per subscription.
8
+ * - `subscription_outbox`: exactly-once emission ledger. Inserted inside the
9
+ * same tx as the row write, drained by the emitter worker.
10
+ * - `subscription_deliveries`: per-attempt HTTP dispatch log. Truncated
11
+ * response bodies + status code for UI + retention.
12
+ *
13
+ * Trigger `subscription_outbox_notify` fires `pg_notify('subscriptions:new_outbox', <sub_id>)`
14
+ * on insert so the emitter can wake without polling.
15
+ */
16
+ export async function up(db: Kysely<unknown>): Promise<void> {
17
+ await sql`
18
+ CREATE TABLE subscriptions (
19
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
20
+ account_id UUID NOT NULL,
21
+ project_id UUID,
22
+ name TEXT NOT NULL,
23
+ status TEXT NOT NULL DEFAULT 'active',
24
+ subgraph_name TEXT NOT NULL,
25
+ table_name TEXT NOT NULL,
26
+ filter JSONB NOT NULL DEFAULT '{}'::jsonb,
27
+ format TEXT NOT NULL DEFAULT 'standard-webhooks',
28
+ runtime TEXT,
29
+ url TEXT NOT NULL,
30
+ signing_secret_enc BYTEA NOT NULL,
31
+ auth_config JSONB NOT NULL DEFAULT '{}'::jsonb,
32
+ max_retries INT NOT NULL DEFAULT 7,
33
+ timeout_ms INT NOT NULL DEFAULT 10000,
34
+ concurrency INT NOT NULL DEFAULT 4,
35
+ circuit_failures INT NOT NULL DEFAULT 0,
36
+ circuit_opened_at TIMESTAMPTZ,
37
+ last_delivery_at TIMESTAMPTZ,
38
+ last_success_at TIMESTAMPTZ,
39
+ last_error TEXT,
40
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
41
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
42
+ UNIQUE (account_id, name)
43
+ )
44
+ `.execute(db);
45
+
46
+ await sql`
47
+ CREATE INDEX subscriptions_matcher_idx
48
+ ON subscriptions (subgraph_name, table_name, status)
49
+ WHERE status = 'active'
50
+ `.execute(db);
51
+
52
+ await sql`
53
+ CREATE INDEX subscriptions_account_idx ON subscriptions (account_id)
54
+ `.execute(db);
55
+
56
+ await sql`
57
+ CREATE TABLE subscription_outbox (
58
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
59
+ subscription_id UUID NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE,
60
+ subgraph_name TEXT NOT NULL,
61
+ table_name TEXT NOT NULL,
62
+ block_height BIGINT NOT NULL,
63
+ tx_id TEXT,
64
+ row_pk JSONB NOT NULL,
65
+ event_type TEXT NOT NULL,
66
+ payload JSONB NOT NULL,
67
+ dedup_key TEXT NOT NULL,
68
+ attempt INT NOT NULL DEFAULT 0,
69
+ next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
70
+ status TEXT NOT NULL DEFAULT 'pending',
71
+ is_replay BOOLEAN NOT NULL DEFAULT FALSE,
72
+ delivered_at TIMESTAMPTZ,
73
+ failed_at TIMESTAMPTZ,
74
+ locked_by TEXT,
75
+ locked_until TIMESTAMPTZ,
76
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
77
+ UNIQUE (subscription_id, dedup_key)
78
+ )
79
+ `.execute(db);
80
+
81
+ await sql`
82
+ CREATE INDEX outbox_dispatch_idx
83
+ ON subscription_outbox (status, next_attempt_at, is_replay)
84
+ WHERE status = 'pending'
85
+ `.execute(db);
86
+
87
+ await sql`
88
+ CREATE INDEX outbox_sub_idx
89
+ ON subscription_outbox (subscription_id, created_at DESC)
90
+ `.execute(db);
91
+
92
+ await sql`
93
+ CREATE TABLE subscription_deliveries (
94
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
95
+ outbox_id UUID NOT NULL REFERENCES subscription_outbox(id) ON DELETE CASCADE,
96
+ subscription_id UUID NOT NULL,
97
+ attempt INT NOT NULL,
98
+ status_code INT,
99
+ response_headers JSONB,
100
+ response_body TEXT,
101
+ error_message TEXT,
102
+ duration_ms INT,
103
+ dispatched_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
104
+ )
105
+ `.execute(db);
106
+
107
+ await sql`
108
+ CREATE INDEX deliveries_sub_idx
109
+ ON subscription_deliveries (subscription_id, dispatched_at DESC)
110
+ `.execute(db);
111
+
112
+ await sql`
113
+ CREATE OR REPLACE FUNCTION notify_new_outbox() RETURNS TRIGGER AS $$
114
+ BEGIN
115
+ PERFORM pg_notify('subscriptions:new_outbox', NEW.subscription_id::text);
116
+ RETURN NEW;
117
+ END;
118
+ $$ LANGUAGE plpgsql
119
+ `.execute(db);
120
+
121
+ await sql`
122
+ CREATE TRIGGER subscription_outbox_notify
123
+ AFTER INSERT ON subscription_outbox
124
+ FOR EACH ROW
125
+ EXECUTE FUNCTION notify_new_outbox()
126
+ `.execute(db);
127
+ }
128
+
129
+ export async function down(db: Kysely<unknown>): Promise<void> {
130
+ await sql`DROP TRIGGER IF EXISTS subscription_outbox_notify ON subscription_outbox`.execute(
131
+ db,
132
+ );
133
+ await sql`DROP FUNCTION IF EXISTS notify_new_outbox() CASCADE`.execute(db);
134
+ await sql`DROP TABLE IF EXISTS subscription_deliveries CASCADE`.execute(db);
135
+ await sql`DROP TABLE IF EXISTS subscription_outbox CASCADE`.execute(db);
136
+ await sql`DROP TABLE IF EXISTS subscriptions CASCADE`.execute(db);
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/shared",
3
- "version": "2.1.0",
3
+ "version": "3.0.0-beta.1",
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"
@@ -65,14 +61,6 @@
65
61
  "types": "./dist/src/schemas/accounts.d.ts",
66
62
  "import": "./dist/src/schemas/accounts.js"
67
63
  },
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"
75
- },
76
64
  "./db/queries/tenants": {
77
65
  "types": "./dist/src/db/queries/tenants.d.ts",
78
66
  "import": "./dist/src/db/queries/tenants.js"
@@ -85,6 +73,30 @@
85
73
  "types": "./dist/src/db/queries/provisioning-audit.d.ts",
86
74
  "import": "./dist/src/db/queries/provisioning-audit.js"
87
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.3.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",